Springboot

2021-11-01 (Springboot - JPA - CRUD(9) )

BSYeop 2021. 11. 3. 17:00

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