JPA를 활용해 CRUD 기능을 만들어보자
7. 답글 기능
BoardJpaController
@PostMapping(value = "/reply")
public ApiResponse<BoardDTO> postBoardReplyContent(@RequestBody BoardDTO boardDTO){
return boardJpaService.postReply(boardDTO);
}
BoardRepository
// JPQL != SQL
@Query("Select b FROM Board b where b.id = ?1") // JPA를 이용하여 쿼리를 날린다.
Board selectBoard(int id);
@Query("SELECT MIN(b.orderNum) FROM Board b" +
" WHERE b.replyRootId = ?1" +
" AND b.orderNum > ?3" +
" AND b.depth <= ?2")
Integer getMinOrderNum(int replyRootId, int depth, int orderNum);
@Query("SELECT MAX(orderNum) + 1 FROM Board" +
" WHERE replyRootId = ?1")
Integer getReplyOrderNum(int replyRootId);
@Modifying
@Query("UPDATE Board SET orderNum = orderNum + 1" +
" WHERE replyRootId = ?1 AND orderNum >= ?2")
void updateOrderNum(int replyRootId, int minOrderNum);
- JPQL(Java Persistence Query Language)
> 테이블이 아닌 엔티티 객체를 대상으로 검색하는 객체지향 쿼리
> sql을 추상화해서 특정 데이터베이스 sql에 의존하지 않는다
> JPA는 JPQL을 분석한 후 적절한 SQL을 만들어 데이터베이스를 조회
> 방언(Dialect)만 변경하면 JPQL을 수정하지 않고 자연스럽게 DB 변경 가능
BoardJpaService
@Transactional
public ApiResponse<BoardDTO> postReply(BoardDTO dto) {
/* JPQL TEST 겸 원글 불러오기 */
Board b = boardRepository.selectBoard(dto.getReplyRootId());
if(b == null){
return new ApiResponse(false, "board id " + dto.getReplyRootId() + " is null");
}
log.debug(b.toString());
/* depth와 orderNum을 정하는 로직 START */
int replyRootId = dto.getReplyRootId();
int depth = dto.getDepth();
int orderNum = dto.getOrderNum();
Integer minOrderNum = boardRepository.getMinOrderNum(replyRootId, depth, orderNum);
if(minOrderNum == null) {
minOrderNum = 0;
}
log.debug("minOrderNum==" + minOrderNum);
// minOrderNum이 0인 경우 : root글에 달린 답글들 사이에 추가되는 답글인지? 바로추가답글 : 사이답글임.
if(minOrderNum == 0) {
log.debug("======root글에 달린 답글들 사이에 추가되는 답글이 아님(바로추가답글)======");
orderNum = boardRepository.getReplyOrderNum(replyRootId);
} else {
log.debug("======root글에 달린 답글들 사이에 추가되는 답글.(사이답글)======");
boardRepository.updateOrderNum(replyRootId, minOrderNum);
orderNum = minOrderNum;
}
int newDepth = depth + 1;
log.debug("newDepth=" + newDepth);
String newSubject = appendPrefixString("RE : ", depth, dto.getSubject());
/* depth와 orderNum을 정하는 로직 END */
/* 새로운 답글 컨텐츠 추가 */
Board newB = Board.builder()
.subject(newSubject)
.author(dto.getAuthor())
.content(dto.getContent())
.password(dto.getPassword())
.replyRootId(replyRootId)
.writeTime(LocalTime.now())
.writeDate(LocalDate.now())
.commentCount(0)
.readCount(0)
.isDel("N")
.depth(newDepth)
.orderNum(orderNum)
.build();
boardRepository.save(newB);
/* 추가한 답글 컨텐츠 결과 리턴 */
// JPA를 사용함에 있어 Entity 객체와 DTO객체를 일부러 분리한 이유, Entity <-> DTO의 변환 참고
// https://dbbymoon.tistory.com/4
dto.setSubject(newSubject);
dto.setReplyRootId(replyRootId);
dto.setDepth(newDepth);
dto.setOrderNum(orderNum);
return new ApiResponse(true, dto);
}
// 답글의 깊이 depth의 값만큼 "RE:"를 제목에 붙여주는 역할
private String appendPrefixString(String appendPrefix, int loop, String target) {
StringBuilder builder = new StringBuilder();
for(int i=0; i<=loop; i++){
builder.append(appendPrefix);
}
builder.append(target);
return builder.toString();
}
6.1 로직 설명
6.1.1 Board 테이블의 ID 값 확인
- 클라이언트가 요청한 ReplyRootId에 해당하는 ID 값이 있는지 확인하고 있으면 해당하는 원글을 불러옴
- 해당하는 ID 값이 null이면 "board id ? is null" 출력
BoardJpaService
public ApiResponse<BoardDTO> postReply(BoardDTO dto) {
/* JPQL TEST 겸 원글 불러오기 */
Board b = boardRepository.selectBoard(dto.getReplyRootId());
if(b == null){
return new ApiResponse(false, "board id " + dto.getReplyRootId() + " is null");
}
log.debug(b.toString());
BoardRepository
@Query("Select b FROM Board b where b.id = ?1") // JPA를 이용하여 쿼리를 날린다.
Board selectBoard(int id);
6.1.2 orderNum 값 구하기
BoardJpaService
- ID 값에 해당하는 글의 replyRootId(원글번호), depth(답글의 깊이), orederNum(답글의 순서) 값을 들고온다
- minOrderNum은 답글 사이에 추가되는 글인지, 맨 밑에 달리는 글인지 확인하는 역할
- JPQL에서는 NVL이 지원이 안되므로 minOrderNum = null이면 0으로 처리
- minOrderNum의 값이 0인 경우 (입력한)원글의 제일 큰 orderNum값에 + 1을 해준다
- minOrderNum의 값이 0이 아닌 경우 (입력한)원글에서 조회한 minOrderNum의 값과 같거나 큰 oderNum값을 모두 + 1해준다
/* depth와 orderNum을 정하는 로직 START */
int replyRootId = dto.getReplyRootId();
int depth = dto.getDepth();
int orderNum = dto.getOrderNum();
Integer minOrderNum = boardRepository.getMinOrderNum(replyRootId, depth, orderNum);
if(minOrderNum == null) {
minOrderNum = 0;
}
log.debug("minOrderNum==" + minOrderNum);
// minOrderNum이 0인 경우 : root글에 달린 답글들 사이에 추가되는 답글인지? 바로추가답글 : 사이답글임.
if(minOrderNum == 0) {
log.debug("======root글에 달린 답글들 사이에 추가되는 답글이 아님(바로추가답글)======");
orderNum = boardRepository.getReplyOrderNum(replyRootId);
} else {
log.debug("======root글에 달린 답글들 사이에 추가되는 답글.(사이답글)======");
boardRepository.updateOrderNum(replyRootId, minOrderNum);
orderNum = minOrderNum;
}
BoardRepository
@Query("SELECT MIN(b.orderNum) FROM Board b" +
" WHERE b.replyRootId = ?1" +
" AND b.orderNum > ?3" +
" AND b.depth <= ?2")
Integer getMinOrderNum(int replyRootId, int depth, int orderNum);
@Query("SELECT MAX(orderNum) + 1 FROM Board" +
" WHERE replyRootId = ?1")
Integer getReplyOrderNum(int replyRootId);
@Modifying
@Query("UPDATE Board SET orderNum = orderNum + 1" +
" WHERE replyRootId = ?1 AND orderNum >= ?2")
void updateOrderNum(int replyRootId, int minOrderNum);
- @Modifying
> JPQL에서 Update문과 Delete문에는 @Modifying 어노테이션을 붙여야한다
6.1.3 depth 값 구하기
BoardJpaService
- 답글의 깊이를 +1 해주고 글 제목 앞에 "RE: "를 붙여준다
int newDepth = depth + 1;
log.debug("newDepth=" + newDepth);
String newSubject = appendPrefixString("RE : ", depth, dto.getSubject());
// 답글의 깊이 depth의 값만큼 "RE:"를 제목에 붙여주는 역할
private String appendPrefixString(String appendPrefix, int loop, String target) {
StringBuilder builder = new StringBuilder();
for(int i=0; i<=loop; i++){
builder.append(appendPrefix);
}
builder.append(target);
return builder.toString();
Postman
'Springboot' 카테고리의 다른 글
2021-11-04 (Springboot - 공공데이터 api 활용(2)) (0) | 2021.11.04 |
---|---|
2021-11-01 (Springboot - JPA - CRUD(10) ) (0) | 2021.11.03 |
2021-10-27 (Springboot - JPA - CRUD(8) ) (0) | 2021.11.02 |
2021-10-27 (Springboot - JPA - CRUD(7) ) (0) | 2021.10.27 |
2021-10-26 (Springboot - JPA - CRUD(6) ) (0) | 2021.10.26 |