Board와 Post 사이 연관관계를 설정했으니 다음으로 Post와 Reply 사이 연관관계를 설정해본다.
먼저 PostEntity와 ReplyEntity 사이 @OneToMany, @ManyToOne을 추가한다.
// PostEntity
@OneToMany(mappedBy = "post")
private List<ReplyEntity> replyList = List.of();
}
// ReplyEntity
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Entity(name="reply")
public class ReplyEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
@ToString.Exclude
@JsonIgnore
private PostEntity post;
private String userName;
private String password;
private String status;
private String title;
private String content;
private LocalDateTime repliedAt;
}
postId가 post로 바뀌어 ReplyService에 오류가 발생한다. ReplyService로 이동해 PostEntity를 받을 수 있게 수정해준다.
@Service
@RequiredArgsConstructor
public class ReplyService {
private final ReplyRepository replyRepository;
private final PostRepository postRepository;
public ReplyEntity create(ReplyRequest replyRequest){
Optional<PostEntity> postEntity = postRepository.findById(replyRequest.getPostId());
if (postEntity.isEmpty()){
throw new RuntimeException("post does not exist: "+replyRequest.getPostId());
}
ReplyEntity entity = ReplyEntity.builder()
.post(postEntity.get())
.userName(replyRequest.getUserName())
.password(replyRequest.getPassword())
.status("REGISTERED")
.title(replyRequest.getTitle())
.content(replyRequest.getContent())
.repliedAt(LocalDateTime.now())
.build();
return replyRepository.save(entity);
연관관계가 설정된 후에는 따로 쿼리메서드를 사용해 /api/post/view에서 포스트를 호출할 때 해당하는 postId를 가진 reply를 반환할 필요가 없어진다. 따라서 ReplyService의 findAllByPostId()과 PostService의 해당 부분을 삭제한다.
이 부분을 삭제해도 replyList를 정상적으로 가져오는 것을 확인할 수 있다.
다만 이렇게 하면 status가 UNREGISTERED인 post도 함께 반환된다.
이 역시 간단하게 애너테이션으로 조건을 걸어줄 수 있다.
BoardEntity로 가서 postList에 @Where 애너테이션에 clause를 준다.
이 @Where 애너테이션은 hibernate 6.3 부터 현재 deprecate 된 애너테이션으로, 앞으로는 @SQLRestriction("status = 'REGISTERED'")라고 작성하면 된다. 참고
조건을 적용하면 REGISTERED인 글만 가져오는 것을 확인할 수 있다. 마찬가지로 PostEntity에도 적용해준다.
자세히 보면 reply가 오름차순으로 정렬되고 있다. 보통 최신 답변이 가장 먼저 보이는 게 편의성이 더 높다. 하여 내림차순으로 정렬을 바꿔본다. 정렬은 hibernate의 @OrderBy 애너테이션으로 해결할 수 있다.
@OrderBy도 deprecate 되었다. 대신 @SQLOrder를 사용한다.
@SQLRestriction("status = 'REGISTERED'")
@SQLOrder(value = "id desc")
여기에 추가로 저장된 게시글이나 답변 수가 100개, 1000개를 넘게 된다면, 이렇게 전부 출력하는 것은 성능면에서 좋지 못하다. 1 페이지에 결과를 10개씩 나눠 출력하는 등의 Pagination을 만들려면, 현재 위치한 페이지, 한 페이지에 들어가는 데이터 개수, 총 페이지 수 등이 필요하다.
board, post, reply와 같은 디렉토리에 common 패키지를 만들고, Api와 Pagination 파일을 생성한다.
우선 PostApiController의 all()에 매개변수로 Pageable(org.springframework.data.domain.Pageable)을 받는다.
// all() 파라미터
@PageableDefault(page=0,size=10)
Pageable pageable
@PageableDefault는 쿼리 파라미터로 페이지와 size가 전달되지 않았을 때 사용할 디폴트를 정해준다.
이 Pageable은 JPA에서 만든 pagination을 구현하기 위한 객체로, 리스트를 받아 현재 위치한 페이지 번호, 총 페이지 수, 한 페이지에 나타낼 데이터 개수, 총 데이터 개수, 현재 페이지의 데이터 개수 등의 정보를 만들어준다.
다시 만들었던 Pagination으로 가서 멤버변수들을 만들어준다.
// Pagination
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Pagination {
// current page number
private Integer page;
// total number of pages
private Integer totalPage;
// size of current page
private Integer size;
// current number of elements in current page
private Integer currentElements;
// total number of elements
private Long totalElements;
}
Api를 완성해준다. Api는 제네릭 타입과 Pagination 객체를 받는다.
// Api
@Getter
@Setter
@ToString
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Api<T> {
private T body;
private Pagination pagination;
}
PostService로 이동하여 all()을 수정한다.
public Api<List<PostEntity>> all(Pageable pageable){
var list = postRepository.findAll(pageable);
Pagination pagination = Pagination.builder()
.page(list.getNumber())
.totalPage(list.getTotalPages())
.size(list.getSize())
.currentElements(list.getNumberOfElements())
.totalElements(list.getTotalElements())
.build();
var response = Api.<List<PostEntity>>builder()
.body(list.toList())
.pagination(pagination)
.build();
return response;
}
1) Pageable을 매개변수로 받고 findAll()에 넘겨준다.
2) findAll(pageable)의 결과로 Page<PostEntity>가 나온다. 이를 list에 저장한다.
3) list를 사용해 pagination 객체를 build한다.
4) List<PostEntity>를 Api에 감싸 build한다. body에는 list를 리스트화한 것을, pagination에는 pagination을 넣는다.
5) response를 반환한다.
마지막으로 PostApiController에서 view의 반환 타입을 Api<List<PostEntity>>로 바꿔준다.
@GetMapping("/all")
public Api<List<PostEntity>> all(
@PageableDefault(page=0,size=10, sort = "id", direction = Sort.Direction.DESC)
Pageable pageable)
{
return postService.all(pageable);
}
+ 최신순으로 정렬하기 위해 direction 옵션을 주고, 정렬 기준은 id로 주었다.
쿼리 파라미터로 post를 5개씩 받아 그 중 0페이지로 요청을 보내면 제대로 결과를 받아오는 것을 확인할 수 있다.