JAVA/SpringBoot

게시판 프로젝트 - 댓글 CRUD 처리

whyHbr 2023. 11. 6. 20:11
728x90
반응형

댓글 테이블 생성

 

create table tb_comment (
      id bigint not null auto_increment comment '댓글 번호 (PK)'
    , post_id bigint not null comment '게시글 번호 (FK)'
    , content varchar(1000) not null comment '내용'
    , writer varchar(20) not null comment '작성자'
    , delete_yn tinyint(1) not null comment '삭제 여부'
    , created_date datetime not null default CURRENT_TIMESTAMP comment '생성일시'
    , modified_date datetime comment '최종 수정일시'
    , primary key(id)
) comment '댓글';

 

제약 조건 생성

게시글 tb_post과 댓글 tb_comment 은 각각 1:n의 관계까 되어야 하며, 관계를 매핑해주기 위해 FK 제약조건을 추가해준다

alter table tb_comment add constraint fk_post_comment foreign key(post_id) references tb_post(id);

 

테이블 구조 확인

show full columns from tb_comment; -- 테이블 구조 확인 1 (코멘트 포함)
desc tb_comment; -- 테이블 구조 확인 2

 

제약 조건 조회

select *
from information_schema.table_constraints
where table_name = 'tb_comment';

 

- 댓글 요청 클래스 생성

댓글 생성과 수정에 사용할 요청 클래스 생성. 댓글도 게시글과 마찬가지로 request와 response용 클래스를 분리해서 데이터를 처리함

package com.study.domain.comment;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class CommentRequest {

    private Long id;           // 댓글 번호 (PK)
    private Long postId;       // 게시글 번호 (FK)
    private String content;    // 내용
    private String writer;     // 작성자

}

 

@Getter: 롬복이 제공하는 기능, 클래스의 모든 멤버 변수에 대한 get()메서드를 만들어준다

@NOArgsConstructor (access = AccessLevel.PROTECTED) : 클래스의 기본 생성자를 만들어준다. access 속성을 이용해 객체 생성을 protected로 제한한다.

 

- 댓글 응답 response 클래스 생성하기

package com.study.domain.comment;

import lombok.Getter;

import java.time.LocalDateTime;

@Getter
public class CommentResponse {

    private Long id;                       // 댓글 번호 (PK)
    private Long postId;                   // 게시글 번호 (FK)
    private String content;                // 내용
    private String writer;                 // 작성자
    private Boolean deleteYn;              // 삭제 여부
    private LocalDateTime createdDate;     // 생성일시
    private LocalDateTime modifiedDate;    // 최종 수정일시

}

 

 

- 댓글 Mapper 인터페이스 생성하기

package com.study.domain.comment;

import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface CommentMapper {

    /**
     * 댓글 저장
     * @param params - 댓글 정보
     */
    void save(CommentRequest params);

    /**
     * 댓글 상세정보 조회
     * @param id - PK
     * @return 댓글 상세정보
     */
    CommentResponse findById(Long id);

    /**
     * 댓글 수정
     * @param params - 댓글 정보
     */
    void update(CommentRequest params);

    /**
     * 댓글 삭제
     * @param id - PK
     */
    void deleteById(Long id);

    /**
     * 댓글 리스트 조회
     * @param params - search conditions
     * @return 댓글 리스트
     */
    List<CommentResponse> findAll(CommentSearchDto params);

    /**
     * 댓글 수 카운팅
     * @param params - search conditions
     * @return 댓글 수
     */
    int count(CommentSearchDto params);

}

 

@Mapper : 해당 인터페이스가 db와 통신하는 인터페이스임을 의미. mapper인터페이스는 xml mapper에서 메서드명과 동일한 id를 가진 sql쿼리를 찾아 실행한다.

 

save() : 댓글을 생성하는 insert 쿼리를 호출한다. 파라미터로 전달받는params에 저장할 댓글 정보가 담긴다

 

findById() : id(pk) 를 기준으로 특정 댓글의 상세 정보를 조회하는 select 쿼리를 호출한다. 쿼리가 실행되면 응답 xomment response 클래스 객체의 각 멤버 변수에 결괏값이 매핑된다

 

update() : 댓글을 수정하는update 쿼리를 호출한다. save() 와 마찬가지로 요청 클래스의 객체를 이용해 댓글 정보를 업데이트 한다.

 

deleteById() : id(pk)를 기준으로 특정 댓글을 삭제하는 update쿼리를 호출한다. 테이블에서 실제로 데이터를 delete하지 않고 삭제 여부 칼럼의 상태 값을 0 에서 1로 업데이트 한다.

 

findAll(0 : 게시글 번호 post id 를 기준으로 특정 게시글에 등록된 댓글 목록을 조회하는 select 쿼리를 호출한다.

 

count(): 게시글 번호post_ id 를 기준으로 특정 게시글에 등록된 댓글 수를 조회하는 select 쿼리를 호출한다. 페이징을 적용하면서 사용한다.

 

- 댓글 XML mapper생성

mapper 인터페이스에 연결할 xml mapper 생성.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.study.domain.comment.CommentMapper">

    <!-- tb_comment 테이블 전체 컬럼 -->
    <sql id="commentColumns">
        id
        , post_id
        , content
        , writer
        , delete_yn
        , created_date
        , modified_date
    </sql>


    <!-- 댓글 저장 -->
    <insert id="save" parameterType="com.study.domain.comment.CommentRequest" useGeneratedKeys="true" keyProperty="id">
        INSERT INTO tb_comment (
        <include refid="commentColumns" />
        ) VALUES (
        #{id}
        , #{postId}
        , #{content}
        , #{writer}
        , 0
        , NOW()
        , NULL
        )
    </insert>


    <!-- 댓글 상세정보 조회 -->
    <select id="findById" parameterType="long" resultType="com.study.domain.comment.CommentResponse">
        SELECT
        <include refid="commentColumns" />
        FROM
        tb_comment
        WHERE
        id = #{value}
    </select>


    <!-- 댓글 수정 -->
    <update id="update" parameterType="com.study.domain.comment.CommentRequest">
        UPDATE tb_comment
        SET
        modified_date = NOW()
        , content = #{content}
        , writer = #{writer}
        WHERE
        id = #{id}
    </update>


    <!-- 댓글 삭제 -->
    <delete id="deleteById" parameterType="long">
        UPDATE tb_comment
        SET
        delete_yn = 1
        WHERE
        id = #{id}
    </delete>


    <!-- 댓글 리스트 조회 -->
    <select id="findAll" parameterType="com.study.domain.comment.CommentSearchDto" resultType="com.study.domain.comment.CommentResponse">
        SELECT
        <include refid="commentColumns" />
        FROM
        tb_comment
        WHERE
        delete_yn = 0
        AND post_id = #{postId}
        ORDER BY
        id DESC
        LIMIT #{pagination.limitStart}, #{recordSize}
    </select>


    <!-- 댓글 수 카운팅 -->
    <select id="count" parameterType="com.study.domain.comment.CommentSearchDto" resultType="int">
        SELECT
        COUNT(*)
        FROM
        tb_comment
        WHERE
        delete_yn = 0
        AND post_id = #{postId}
    </select>

</mapper>

 

 

- 댓글 서비스 클래스 생성

비즈니스 로직을 담당하는 서비스 레이어. 

package com.study.domain.comment;

import com.study.common.paging.Pagination;
import com.study.common.paging.PagingResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;
import java.util.Collections;
import java.util.List;

@Service
@RequiredArgsConstructor
public class CommentService {

    private final CommentMapper commentMapper;

    /**
     * 댓글 저장
     * @param params - 댓글 정보
     * @return Generated PK
     */
    @Transactional
    public Long saveComment(final CommentRequest params) {
        commentMapper.save(params);
        return params.getId();
    }

    /**
     * 댓글 상세정보 조회
     * @param id - PK
     * @return 댓글 상세정보
     */
    public CommentResponse findCommentById(final Long id) {
        return commentMapper.findById(id);
    }

    /**
     * 댓글 수정
     * @param params - 댓글 정보
     * @return PK
     */
    @Transactional
    public Long updateComment(final CommentRequest params) {
        commentMapper.update(params);
        return params.getId();
    }

    /**
     * 댓글 삭제
     * @param id - PK
     * @return PK
     */
    @Transactional
    public Long deleteComment(final Long id) {
        commentMapper.deleteById(id);
        return id;
    }

    /**
     * 댓글 리스트 조회
     * @param params - search conditions
     * @return list & pagination information
     */
    public PagingResponse<CommentResponse> findAllComment(final CommentSearchDto params) {

        int count = commentMapper.count(params);
        if (count < 1) {
            return new PagingResponse<>(Collections.emptyList(), null);
        }

        Pagination pagination = new Pagination(count, params);
        List<CommentResponse> list = commentMapper.findAll(params);
        return new PagingResponse<>(list, pagination);
    }

}
728x90