1. {{#posts}}
- posts 라는 List를 순회
2. {{id}} 등의 {{변수명}}
- List에서 뽑아낸 객체의 필드를 사용합니다.
그럼 Controller, Service, Repository 코드를 작성하겠습니다.
PostsRepository
import java.util.List;
public interface PostsRepository extends JpaRepository<Posts, Long> {
@Query("SELECT p From Posts p ORDER BY p.id DESC")
List<Posts> findAllDesc();
}
SpringDataJpa에서 제공하지 않는 메소드는 위처럼 쿼리로 작성핻 되는 것으 보여드리고자 @Query를 사용했습니다.
실제로 앞의 코드는 SpringDataJpa에서 제공하는 기본 메소드만으로 해결할 수있습니다. 다만 @Query가 훨씬 가독성이 좋으니 선택해서 사용하면 됩니다.
참고
규모가 있는 프로젝트에서의 데이터 조회는 FK의 조인, 복잡한 조건 등으로 인해 이런 Entity 클래스만으로 처리하기 어려워 조회용 프레임워크를 추가로 사용한다.
대표적인 예로querydsl,jooq, MyBatis등이 있습니다. 조회는 위 3가지 프레임워크 중 하나를 통해 조회하고, 등록/수정/삭제 등은 SpringDataJpa를 통해 진행합니다.
Querydsl을 추천하는 이유
타입 안정성이 보장된다.
단순한 문자열로 쿼리를 생성하는 것이 아니라, 메소드를 기반으로 쿼리를 생성하기 때문에 오타나 존재하지 않는 컬러명을 명시할 경우 IDE에서 자동으로 검출됩니다. 이 장점은 Jooq에서도 지원하는 장점이자만, MyBatis에서는 지원하지 않습니다.
국내 많은 회사에서 사용중입니다.
쿠팡, 배민 등 JPA를 적극적으로 사용하는 회사에서는Querydsl를 적극적으로 사용중입니다.
레퍼런스가 많습니다.
앞 2번의 장점에서 이어지는 것인데, 많은 회사와 개발자들이 사용하다보니 그만큼 국내 자료가 많다.
Repositroy 다음으로 PostsService에 코드를 추가하겠습니다.
PostService
import java.util.List;
import java.util.stream.Collectors;
@RequiredArgsConstructor
@Service
public class PostService {
...
@Transactional(readOnly = true)
public List<PostsListResponseDto> findAllDesc() {
return postsRepository.findAllDesc().stream()
.map(PostsListResponseDto::new)
.collect(Collectors.toList());
}
}
findAllDesc 메소드의 트랜잭션 어노테이션(@Transcational)에 옵셥이 하나 추가되었습니다. (readOnly = true)를 주면트랜잭션 범위는 유지하되, 조회 기능만 남겨두어조회 속도가 개선되기 때문에 등록, 수정, 삭제 기능이 전혀 없는 서비스 메소드에서 사용하는 것을 추천합니다.
아직 PostsListResponseDto 클래스가 없기 때문에 이 클래스 역시 생성합니다.
1. {{post.id}}
- 머스테치는 객체의 필드 접근 시 점(Dot)으로 구분합니다.
- 즉, Post 클래스 id에 대한 접근은 post.id로 사용할 수 있습니다.
2. readonly
- input 태그에 읽기 가능만 허용하는 속성입니다.
- id와 author는 수정할 수 없도록 읽기만 허용하도록 추가합니다.
그리고 btn-update 버튼을 클릭하면 update 기능을 호출할 수 있게 index.js 파일에도 update function을 추가합니다.
index.js
var main = {
init : function () {
var _this = this;
$('#btn-save').on('click', function () {
_this.save();
});
$("#btn-update").on('click', function () {
_this.update();
});
},
save : function () {
...
},
update : function () {
var date = {
title : $("#title").val(),
content : $("#content").val()
};
var id = $("#id").val();
$.ajax({
type : 'PUT',
url : '/api/v1/posts/'+ id,
dataType : 'json',
contentType : 'application/json; charset=utf-8',
data : JSON.stringify(data)
}).done(function () {
alert('글이 수정되었습니다.');
window.location.href = '/';
}).fail(function (error) {
alert(JSON.stringify(error));
});
}
};
main.init();
$("#btn-update").on('click')
btn-update란 id를 가진 HTML 엘리먼트에 click 이벤트가 발생할때 update function을 실행하도록 이벤트를 등록한다.
@RequiredArgsConstructor
@Controller
public class IndexController {
...
@GetMapping("/posts/update/{id}")
public String postsUpdate(@PathVariable Long id, Model model){
PostsResponseDto dto = postService.findById(id);
model.addAttribute("post", dto);
return "posts-update";
}
}
제목과 내용 수정
게시글 삭제
수정 기능이 정상적으로 구현되었으니, 삭제 기능도 구현해 봅시다. 본문을 확인하고 진행해야 하므로, 수정 화면에 추가하겠습니다.
var main = {
init : function () {
var _this = this;
...
$("#btn-delete").on('click', function () {
_this.delete();
});
},
...
delete : function () {
var id = $("#id").val();
$.ajax({
type : 'DELETE',
url : '/api/v1/posts/'+ id,
dataType : 'json',
contentType : 'application/json; charset=utf-8'
}).done(function () {
alert('글이 삭제되었습니다.');
window.location.href = '/';
}).fail(function (error) {
alert(JSON.stringify(error));
});
}
};
main.init();
PostServicedelete 메소드 추가
@RequiredArgsConstructor
@Service
public class PostService {
private final PostsRepository postsRepository;
...
@Transactional
public void delete (Long id) {
Posts posts = postsRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다 id=" + id));
postsRepository.delete(posts);
}
}
postsRepository.delete(posts)
JpaRepository에서 이미 delete 메소드를 지원하고 있으니 이를 활용합니다.
엔티티를 파라미터로 삭제할 수도 있고, deleteById 메소드를 이용하면 id를 삭제할 수도 있습니다.
존재하는 Posts인지 확인을 위해 엔티티 조회 후 그대로 삭제합니다.
PostsApiControllerdelete 메소드 추가
@RequiredArgsConstructor
@RestController
public class PostsApiController {
private final PostService postService;
...
@DeleteMapping("/api/v1/posts/{id}")
public Long delete(@PathVariable Long id) {
postService.delete(id);
return id;
}
}