- 본 게시판 프로젝트는 단계별(step by step)로 진행되니, 이전 단계를 진행하시는 것을 권장드립니다.
- DBMS 툴은 DBeaver를 이용하며, DB는 MariaDB를 이용합니다. (MariaDB 설치하기)
- 화면 처리는 HTML5 기반의 자바 템플릿 엔진인 타임리프(Thymeleaf)를 사용합니다.
이전 글에서는 REST API란 무엇이며, 어떤 방식으로 데이터에 접근해야 하는지 가볍게 알아보았습니다. 지금부터 게시판에 댓글 기능을 구현해 볼 건데요. 이번 글에서는 매퍼(Mapper)와 서비스(Service)에 댓글 CRUD 로직을 작성해 두고, 다음 글부터 @RestController와 jQuery의 Ajax를 이용해서 비동기 방식의 화면(HTML) 처리를 진행합니다.
1. 댓글 테이블 구조
칼럼 | 설명 |
id | 댓글 테이블의 PK(Primary Key)로 댓글 번호를 의미합니다. |
post_id | 댓글 테이블의 FK(Foreign Key)로 댓글과 연결되는 게시글 번호를 의미합니다. |
content | 댓글 내용을 의미합니다. |
writer | 댓글 작성자를 의미합니다. |
delete_yn | 댓글 삭제 여부를 의미합니다. |
created_date | 댓글 생성일시를 의미합니다. |
modified_date | 댓글 최종 수정일시를 의미합니다. |
2. 댓글 테이블 생성하기
DB에 댓글 데이터를 관리할 테이블을 생성해 보겠습니다. DBMS 툴을 실행하고, tb_comment 테이블을 생성하는 다음의 스크립트를 실행해 주세요.
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 '댓글';
3. 제약 조건(Constraint) 추가하기
게시글(tb_post)과 댓글(tb_comment)은 각각 1:N의 관계가 되어야 하며, 관계를 매핑해 주기 위해 테이블에 FK(Foreign Key) 제약 조건을 추가해야 합니다.
3-1) 제약 조건 생성
댓글 테이블의 FK(post_id)가 게시글 테이블의 PK(id)를 참조할 수 있도록 DBMS 툴에서 다음의 스크립트를 실행해 주세요.
alter table tb_comment add constraint fk_post_comment foreign key(post_id) references tb_post(id);
3-2) 테이블 구조 확인
이제, 테이블 구조를 확인해 보면 post_id에 추가된 제약 조건을 확인할 수 있습니다. 테이블 구조 확인은 다음의 명령어들을 이용하시면 됩니다.
show full columns from tb_comment; -- 테이블 구조 확인 1 (코멘트 포함)
desc tb_comment; -- 테이블 구조 확인 2
3-3) 제약 조건 조회
마지막으로 다음의 스크립트를 실행해서 DB(스키마)에 제약 조건이 정상적으로 추가되었는지 확인해 주세요.
select *
from information_schema.table_constraints
where table_name = 'tb_comment';
추가적으로, 특정 테이블이 아닌 DB(스키마)의 모든 제약 조건을 확인하고 싶을 때는 조건절에 table_schema = '스키마 이름'을 입력해 주시면 됩니다.
4. 댓글 요청(Request) 클래스 생성하기
다음으로 댓글 생성(INSERT)과 수정(UPDATE)에 사용할 요청 클래스를 생성해 볼 건데요. 댓글도 게시글과 마찬가지로 요청(Request)과 응답(Response)용 클래스를 분리해서 데이터를 처리합니다.
이클립스(STS)를 사용하신다면, 코드의 간결함과 가독성을 위해 롬복 설치하기를 진행하시는 것을 권장드립니다.
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 | 롬복(Lombok)이 제공해주는 기능으로, 클래스의 모든 멤버 변수에 대한 get( ) 메서드를 만들어 줍니다. |
@NoArgsConstructor(access = AccessLevel.PROTECTED) | 클래스의 기본 생성자를 만들어 줍니다. access 속성을 이용해서 객체 생성을 protected로 제한합니다. 게시글 요청 클래스인 PostRequest에는 @Getter와 @Setter를 선언해서 사용했는데요. 이전에 말씀드렸듯이 요청 클래스의 각 멤버 변수는 HTML의 폼(form) 태그에 선언된 필드(input, textarea 등)의 name 값을 기준으로 파라미터를 전송하며, 전송된 파라미터는 요청 클래스의 set( ) 메서드에 의해 값이 매핑됩니다. 하지만, 일반적인 REST API 방식에서는 데이터를 등록/수정 할 때 폼(form) 자체를 전송하지 않고, key-value 구조로 이루어진 JSON이라는 문자열 포맷으로 데이터를 전송하기 때문에 set( ) 메서드가 필요하지 않습니다. (파일을 전송하는 경우는 제외) 자세한 내용은 컨트롤러를 처리하는 과정에서 설명드리겠습니다. 지금은 "JSON이라는 포맷으로 데이터를 전달하는구나!" 정도로 이해해 주시면 되겠습니다. |
5. 댓글 응답(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; // 최종 수정일시
}
6. 댓글 Mapper 인터페이스 생성하기
다음은 DB와의 통신 역할을 할 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 postId - 게시글 번호 (FK)
* @return 댓글 리스트
*/
List<CommentResponse> findAll(Long postId);
/**
* 댓글 수 카운팅
* @param postId - 게시글 번호 (FK)
* @return 댓글 수
*/
int count(Long postId);
}
구성 요소 | 설명 |
@Mapper | 해당 인터페이스가 DB와 통신하는 인터페이스임을 의미합니다. Mapper 인터페이스는 XML Mapper에서 메서드명과 동일한 ID를 가진 SQL 쿼리를 찾아 실행합니다. |
save( ) | 댓글을 생성하는 INSERT 쿼리를 호출합니다. 파라미터로 전달받는 params에는 저장할 댓글 정보가 담깁니다. |
findById( ) | id(PK)를 기준으로 특정 댓글의 상세정보를 조회하는 SELECT 쿼리를 호출합니다. 쿼리가 실행되면 응답(CommentResponse) 클래스 객체의 각 멤버 변수에 결괏값이 매핑(바인딩)됩니다. |
update( ) | 댓글을 수정하는 UPDATE 쿼리를 호출합니다. save( )와 마찬가지로 요청 클래스의 객체를 이용해서 댓글 정보를 업데이트합니다. |
deleteById( ) | id(PK)를 기준으로 특정 댓글을 삭제하는 UPDATE 쿼리를 호출합니다. 우리는 테이블에서 실제로 데이터를 DELETE 하지 않고, 삭제 여부(delete_yn) 칼럼의 상태 값을 0(false)에서 1(true)로 업데이트합니다. SELECT 쿼리의 조건절에 delete_yn = 0 조건을 주게 되면, 삭제된(delete_yn = 1) 데이터는 조회되지 않습니다. 즉, 물리 삭제가 아닌 논리 삭제 방식을 이용합니다. |
findAll( ) | 게시글 번호(post_id)를 기준으로 특정 게시글에 등록된 댓글 목록을 조회하는 SELECT 쿼리를 호출합니다. |
count( | 게시글 번호(post_id)를 기준으로 특정 게시글에 등록된 댓글 수를 조회하는 SELECT 쿼리를 호출합니다. 당장은 사용되지 않으며, 게시글 때와 마찬가지로 추후에 페이징을 적용하면서 사용합니다. |
7. 댓글 MyBatis XML Mapper 생성하기
다음은 Mapper 인터페이스와 연결할 XML Mapper입니다. src/main/resources/mappers 폴더에 CommentMapper.xml을 추가하고, 다음의 코드를 작성해 주세요.
XML Mapper는 게시글 CRUD 처리하기에서 설명드렸으니 자세한 설명은 생략하겠습니다.
<?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="long" resultType="com.study.domain.comment.CommentResponse">
SELECT
<include refid="commentColumns" />
FROM
tb_comment
WHERE
delete_yn = 0
AND post_id = #{value}
ORDER BY
id DESC
</select>
</mapper>
8. 댓글 서비스(Service) 클래스 생성하기
다음은 비즈니스 로직을 담당해 주는 서비스 레이어(Service Layer)입니다. 해당 클래스의 각 어노테이션도 게시글 등록 구현하기에서 설명드렸었고, 로직 자체도 심플하기 때문에 설명은 생략하겠습니다.
package com.study.domain.comment;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import javax.transaction.Transactional;
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 postId - 게시글 번호 (FK)
* @return 특정 게시글에 등록된 댓글 리스트
*/
public List<CommentResponse> findAllComment(final Long postId) {
return commentMapper.findAll(postId);
}
}
마치며
이번에는 서비스(Service) 영역까지 댓글 CRUD 기능을 구현해 보았습니다.
다음 글에서는 @RestController와 jQuery의 Ajax를 이용해서 댓글을 등록하는 방법을 알아보겠습니다.
오늘도 방문해 주신 여러분께 진심으로 감사드립니다. 좋은 하루 보내세요 :)
댓글