본문 바로가기
Spring Boot

스프링 부트(Spring Boot) - 게시판 CRUD 처리하기 [Thymeleaf, MariaDB, IntelliJ, Gradle, MyBatis]

by 도뎡 2023. 3. 23.
반응형

 


  • 본 게시판 프로젝트는 단계별(step by step)로 진행되니, 이전 단계를 진행하시는 것을 권장드립니다.
  • 본 포스팅은 DBeaver를 기준으로 작성된 글이며, 만약 MariaDB가 설치되어 있지 않으시다면, 선행 작업
     MariaDB 설치하기를 꼭! 진행해 주세요.

이전 글에서는 스프링 부트와 데이터베이스(MariaDB + MyBatis)를 연동하고, JUnit을 이용해서 단위 테스트 하는 방법을 알아보았습니다. 이번 글부터는 약속대로 게시판을 구현해 보도록 하겠습니다.

 

대학에서 처음으로 동아리원들과 진행해 본 프로젝트는 게시판이었습니다. "뜬금없이 무슨 게시판이야?"라고 생각하실 수도 있습니다만, 웹 개발에 있어 게시판은 가장 기본적이며, 어떠한 시스템이던 게시판의 특성을 가지고 있습니다. 즉, 대부분의 웹 사이트는 게시판의 연장선이라고 볼 수 있습니다.

 

이번 글의 제목은 "게시판 CRUD 처리하기"입니다. CRUD는 각각 Create(생성), Read(조회), Update(수정), Delete(삭제)를 의미합니다. 쇼핑몰을 예로 들어보겠습니다. 관리자는 상품 정보를 "등록(C)/수정(U)/조회(R)/삭제(D)" 할 수 있어야 하며, 사용자는 상품 정보를 "조회(R)" 또는 상품을 "구매(C - 구매 정보 / U - 재고)" 할 수 있어야 합니다. 즉, 시스템의 특성과 기능에 따라 CRUD를 적절히 활용해주면 됩니다.

 

대부분의 웹 사이트가 게시판의 연장선이라는 말이 조금은 이해가 되셨으리라 믿습니다.

그럼 본격적으로 게시판 만들기, 시작합니다!

 

 

1. 게시글 테이블 생성하기


1-1) 테이블 생성 스크립트 실행하기

가장 먼저, DB에 게시글을 관리할 테이블을 생성해 보도록 하겠습니다. DBMS 툴을 실행하고, SQL 편집기에서 다음의 스크립트를 실행해 주시면 되는데요. 각 칼럼은 뒤에서 설명드리도록 하겠습니다.

 

혹시라도 이전에 board DB를 기본 DB로 설정하지 않으셨다면, 편집기에서 USE board 명령어를 실행한 후에 진행해 주세요.

CREATE TABLE `tb_post` (
    `id`            bigint(20)    NOT NULL AUTO_INCREMENT COMMENT 'PK',
    `title`         varchar(100)  NOT NULL COMMENT '제목',
    `content`       varchar(3000) NOT NULL COMMENT '내용',
    `writer`        varchar(20)   NOT NULL COMMENT '작성자',
    `view_cnt`      int(11)       NOT NULL COMMENT '조회 수',
    `notice_yn`     tinyint(1)    NOT NULL COMMENT '공지글 여부',
    `delete_yn`     tinyint(1)    NOT NULL COMMENT '삭제 여부',
    `created_date`  datetime      NOT NULL DEFAULT current_timestamp() COMMENT '생성일시',
    `modified_date` datetime               DEFAULT NULL COMMENT '최종 수정일시',
    PRIMARY KEY (`id`)
) COMMENT '게시글';

 

 

1-2) 테이블 생성 결과 확인하기

테이블 생성 결과를 확인하는 방법은 대표적으로 두 가지가 있습니다. SQL 편집기에서 명령어를 실행하거나, 커넥션 > Databases > board > Tables에서 확인하실 수 있는데요. 명령어는 대표적으로 세 가지 정도가 있습니다.


  • DESC tb_post -- 테이블 구조 조회
  • SHOW TABLES -- DB(스키마)에 포함된 모든 테이블 조회
  • SHOW FULL COLUMNS FROM tb_post -- 테이블에 포함된 모든 칼럼 조회 (코멘트 포함)

 

다음은 세 번째 명령어를 실행한 결과입니다. (이미지를 클릭하시면 확대해 보실 수 있습니다!)

생성된 tb_post 테이블 구조

 

 

다음은 Database Navigator에서 생성된 테이블을 확인하는 방법입니다. 만약 테이블이 보이지 않는다면 Tables에서 마우스를 우클릭한 후 새로 고침을 클릭해 주세요.

Database Navigator에서 생성된 테이블 확인하는 방법


 

2. 게시글 요청(Request) 클래스 생성하기

제 경험에 의해 말씀드리자면, 과거(스프링 레거시)에는 테이블을 구조화한 클래스의 접미사로 VO 또는 DTO를 붙여 요청(데이터 등록/수정)과 응답(데이터 조회)에 공통으로 사용하는 경우가 다반사였습니다.

 

최근에는 요청(Request)과 응답(Response)용 클래스를 분리해서 처리하시는 동료분들을 많이 보기도 했고, 분리함으로써 이점이 훨씬 많다는 것을 느꼈는데요. 자세한 내용은 데이터 처리 과정에서 다시 한번 설명드리도록 하겠습니다.


2-1) 요청 클래스 생성 및 소스 코드 작성하기

우선, 게시글 생성(INSERT)과 수정(UPDATE)에 사용할 요청(Request) 클래스를 생성해 보도록 하겠습니다. java 디렉터리에 com.study.domain.post 패키지를 추가하고, 그 안에 PostRequest 클래스를 추가한 후 코드를 작성해 주세요.

package com.study.domain.post;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter
public class PostRequest {

    private Long id;             // PK
    private String title;        // 제목
    private String content;      // 내용
    private String writer;       // 작성자
    private Boolean noticeYn;    // 공지글 여부
    
}

 

1) @Getter / @Settter

클래스 레벨에 선언된 두 어노테이션은 롬복(Lombok) 라이브러리에서 제공해 주는 기능으로, 클래스에 선언된 모든 멤버 변수에 대한 getter와 settter를 생성해 주는 역할을 합니다.

 

인텔리제이(IntelliJ)는 기본적으로 롬복(Lombok) 플러그인이 설치되어 있지만, 이클립스(STS)는 라이브러리를 수동으로 설치해 주어야 합니다. 만약 이클립스(STS)를 사용하신다면, 코드의 간결함과 가독성을 위해 롬복 설치하기를 진행하시는 것을 권장드립니다.

 

 

2) 클래스의 멤버 변수가 왜 칼럼 개수보다 적은 거죠?

게시글(tb_post) 테이블 구조

 

게시글 테이블 구조를 다시 한번 봐주시기 바랍니다. 이 중에서 사용자가 게시판에 글을 작성할 때 입력(선택)하는 필드는 제목(title), 내용(content), 작성자(writer), 공지글 여부(notice_yn)까지 총 네 가지 정도입니다.

 

id는 auto_increment 속성에 의해 자동으로 1씩 증가되므로 게시글 생성(INSERT) 시점에는 필요가 없으나, 게시글을 수정(UPDATE) 할 때 SQL 쿼리의 WHERE 조건으로 id(PK)를 사용하기 때문에 선언해 주었습니다.

조회 수(view_cnt)와 삭제 여부(delete_yn)는 무조건 0으로 생성되어야 하며, 생성일시(created_date)는 현재 시간으로, 수정일시(modified_date)는 NULL로 설정해 주면 됩니다. 삭제 여부(delete_yn) 칼럼의 용도는 뒤에서 설명드리도록 하겠습니다.

 

 

3. 게시글 응답(Response) 클래스 생성하기

다음은 사용자에게 보여줄 데이터를 처리할 응답용 클래스입니다. 응답 클래스에는 테이블의 모든 칼럼을 멤버 변수로 선언해 주시면 됩니다. 요청 클래스와 동일한 경로에 PostResponse 클래스를 추가하고, 다음의 코드를 작성해 주세요.

package com.study.domain.post;

import lombok.Getter;

import java.time.LocalDateTime;

@Getter
public class PostResponse {

    private Long id;                       // PK
    private String title;                  // 제목
    private String content;                // 내용
    private String writer;                 // 작성자
    private int viewCnt;                   // 조회 수
    private Boolean noticeYn;              // 공지글 여부
    private Boolean deleteYn;              // 삭제 여부
    private LocalDateTime createdDate;     // 생성일시
    private LocalDateTime modifiedDate;    // 최종 수정일시

}

 

 

4. Mapper 인터페이스 생성하기

다음은 데이터베이스와의 통신 역할을 할 Mapper 인터페이스입니다. 마찬가지로 com.study.domain.post 패키지에 PostMapper 인터페이스를 추가하고, 코드를 작성해 주세요.

package com.study.domain.post;

import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface PostMapper {

    /**
     * 게시글 저장
     * @param params - 게시글 정보
     */
    void save(PostRequest params);

    /**
     * 게시글 상세정보 조회
     * @param id - PK
     * @return 게시글 상세정보
     */
    PostResponse findById(Long id);
    
    /**
     * 게시글 수정
     * @param params - 게시글 정보
     */
    void update(PostRequest params);

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

    /**
     * 게시글 리스트 조회
     * @return 게시글 리스트
     */
    List<PostResponse> findAll();

    /**
     * 게시글 수 카운팅
     * @return 게시글 수
     */
    int count();

}

 

1) @Mapper

과거에는 DAO(Data Access Object) 클래스에 @Repository 어노테이션을 선언해서 해당 클래스가 DB와 통신하는 클래스임을 명시하고는 했습니다. MyBatis는 Mapper(Java 인터페이스)와 XML Mapper(실제로 DB에 접근해서 호출할 SQL 쿼리를 작성(선언)하는 파일)를 통해 DB와 통신합니다.

 

쉽게 말해, XML Mapper에 SQL 쿼리를 선언해 두고 Mapper를 통해 SQL 쿼리를 호출하는데요. Mapper는 XML Mapper에 선언된 SQL 중에서 메서드명과 동일한 id를 가진 SQL 쿼리를 찾아 실행합니다. 예를 들어, 메서드명이 "savePost( )"라고 가정했을 때 SQL id는 "savePost"가 되어야 합니다.

 

여기서 꼭 기억해 주셔야 할 게 있는데요. Mapper에는 @Mapper 어노테이션을 필수적으로 선언해 주어야 하며, Mapper와 XML Mapper는 XML Mapper의 namespace라는 속성을 통해 연결된다는 점입니다. 연결 방법은 뒤에서 예제를 통해 알아보도록 하겠습니다.

 

 

2) save( )

게시글을 생성하는 INSERT 쿼리를 호출합니다. 파라미터로 전달받는 params는 요청(PostRequest) 클래스의 객체이며, params에는 저장할 게시글 정보가 담기게 됩니다.

 

 

3) findById( )

특정 게시글을 조회하는 SELECT 쿼리를 호출합니다. 파라미터로 id(PK)를 전달받아 SQL 쿼리의 WHERE 조건으로 사용하며, 쿼리가 실행되면 메서드의 리턴 타입인 응답(PostResponse) 클래스 객체의 각 멤버 변수에 결괏값이 매핑(바인딩)됩니다.

 

 

4) update( )

게시글 정보를 수정하는 UPDATE 쿼리를 호출합니다. save( )와 마찬가지로 요청(PostRequest) 클래스의 객체를 파라미터로 전달받으며, params에는 수정할 게시글 정보가 담기게 됩니다. save( )와 차이가 있다면, UPDATE 쿼리의 WHERE 조건으로 사용되는 id(PK)에도 값이 담긴다는 점입니다.

 

 

5) deleteById( )

게시글을 삭제 처리하는 UPDATE 쿼리를 호출합니다. findById( )와 마찬가지로 id(PK)를 파라미터로 전달받아 SQL 쿼리의 WHERE 조건으로 사용하게 되며, SQL 쿼리가 실행되면 삭제 여부(delete_yn) 칼럼의 상태 값을 0(false)에서 1(true)로 업데이트합니다.

 

삭제 여부(delete_yn)는 칼럼의 상태 값을 기준으로 삭제된 데이터(1)인지, 삭제되지 않은 데이터(0)인지 구분해 주는 역할을 합니다. 사용자에게 데이터를 보여줄 땐 삭제 여부가 0(false)인 데이터만 노출하게 됩니다.

 

실무에서는 데이터가 DELETE(물리적인 삭제) 되어버리면 리스크(손실)가 크기 때문에 논리적인 삭제 방식을 이용합니다.

 

 

6) findAll( )

게시글 목록을 조회하는 SELECT 쿼리를 호출합니다. findById( )는 id(PK)를 기준으로 하나의 게시글을 조회한다면, 해당 메서드는 여러 개의 게시글(PostResponse)을 리스트(List)에 담아 리턴해주는 역할을 합니다.

 

 

7) count( )

전체 게시글 수를 조회하는 SELECT 쿼리를 호출합니다. 당장은 사용되지 않지만, 추후에 페이징 기능을 구현하면서 사용하게 됩니다.

 

 

5. MyBatis 플러그인 설치하기

MyBatisX 플러그인을 이용하면 Mapper와 XML Mapper 간의 이동이 쉬워지고, 문법 등 자잘한 오류들을 쉽게 캐치할 수 있습니다. MyBatis를 조금이나마 편리하게 사용할 수 있도록 MyBatisX 플러그인을 설치해 주세요.

 

(인텔리제이에서 Ctrl + Alt + S를 누르면 설정(Settings) 창이 열리고, 창 좌측에 Plugins 탭이 있습니다.)

MyBatisX 플러그인 설치

 

 

6. MyBatis XML Mapper 생성하기

DatabaseConfig의 sqlSessionFactory 빈(Bean)

 

sqlSessionFactory( )에서 주석 처리된 39번 라인은 XML Mapper의 경로를 의미합니다. 주석을 제거하고 애플리케이션을 구동하면 스프링 부트가 context.getResources( )에 선언한 경로에서 XML Mapper를 찾아 읽습니다.

 

전체 경로에서 classpath는 src/main/resources를 의미합니다. 전체 경로를 해석해 보자면 src/main/resources/mappers/{폴더명}/{파일명}Mapper.xml이 됩니다.

 

경로 안의 별(**, * )은 보통 URL과 파일 경로 등의 설정에 주로 사용되는 표현식입니다. 이러한 패턴을 "Ant style pattern"이라고 부르며, 종류는 (**, *, ?) 총 세 가지가 있습니다. 탁구치는 개발자 님의 블로그에 너무 쉽게 정리되어 있으니 한 번쯤은 읽어보시기를 권장드립니다.

 


6-1) mappers 폴더와 XML Mapper 추가하기

이제, PostMapper에 선언한 메서드와 연결할 SQL 쿼리를 작성할 차례입니다. src/main/resources에 mappers 폴더를 추가하고, 그 안에 PostMapper.xml을 추가해 주세요.

mappers 폴더와 XML Mapper가 추가된 디렉터리 구조

 

 

6-2) SQL 쿼리 작성하기

PostMapper.xml에 다음의 코드를 작성해 주세요. 코드 작성이 완료되면 PostMapper.xml의 아이콘이 참새(?)로 변경되는 것이 정상입니다.

<?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.post.PostMapper">

    <!-- tb_post 테이블 전체 컬럼 -->
    <sql id="postColumns">
          id
        , title
        , content
        , writer
        , view_cnt
        , notice_yn
        , delete_yn
        , created_date
        , modified_date
    </sql>


    <!-- 게시글 저장 -->
    <insert id="save" parameterType="com.study.domain.post.PostRequest">
        INSERT INTO tb_post (
            <include refid="postColumns" />
        ) VALUES (
              #{id}
            , #{title}
            , #{content}
            , #{writer}
            , 0
            , #{noticeYn}
            , 0
            , NOW()
            , NULL
        )
    </insert>


    <!-- 게시글 상세정보 조회 -->
    <select id="findById" parameterType="long" resultType="com.study.domain.post.PostResponse">
        SELECT
            <include refid="postColumns" />
        FROM
            tb_post
        WHERE
            id = #{value}
    </select>


    <!-- 게시글 수정 -->
    <update id="update" parameterType="com.study.domain.post.PostRequest">
        UPDATE tb_post
        SET
              modified_date = NOW()
            , title = #{title}
            , content = #{content}
            , writer = #{writer}
            , notice_yn = #{noticeYn}
        WHERE
            id = #{id}
    </update>


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


    <!-- 게시글 리스트 조회 -->
    <select id="findAll" resultType="com.study.domain.post.PostResponse">
        SELECT
            <include refid="postColumns" />
        FROM
            tb_post
        WHERE
            delete_yn = 0
        ORDER BY
            id DESC
    </select>

</mapper>

 

1) <mapper> 태그

XML Mapper는 <mapper>로 시작해서 </mapper>로 끝나며, <mapper> 태그의 namespace 속성에 Mapper 인터페이스의 경로를 선언해 주면 Mapper와 XML Mapper가 연결됩니다.


Mapper 인터페이스는 XML Mapper에서 메서드명과 동일한 id를 가진 SQL 쿼리를 찾아 실행한다는 것을 꼭! 기억해 주셔야 합니다.

 

 

2) <sql> 태그와 <include> 태그

MyBatis는 <sql> 태그와 <include> 태그를 이용해서 공통으로 사용되거나 반복적으로 사용되는 쿼리를 처리할 수 있습니다. 굳이 Java로 비유하자면, 변수로 선언해 두고 필요한 상황에 호출해서 사용하는 개념과 유사한데요. 저는 PostMapper.xml의 "postColumns"와 같이 INSERT 쿼리와 SELECT 쿼리에 주로 사용합니다.


각각의 쿼리에 전체 칼럼을 선언해 줘도 되지만, 해당 태그들을 이용하면 코드 라인을 줄일 수 있습니다.
두 태그의 포인트는 중복 제거이며, 동일한 XML Mapper뿐만 아니라, 다른 XML Mapper에 선언된 SQL 조각도 인클루드(Include) 할 수 있습니다.

 

 

3) parameterType

SQL 쿼리 실행에 필요한 파라미터의 타입을 의미합니다. 단일(하나의) 파라미터가 아닌 경우에는 일반적으로 객체를 전달받아 쿼리를 실행합니다.

 

 

4) resultType

SQL 쿼리의 실행 결과를 매핑할 결과 타입을 의미합니다. Mapper 인터페이스에 선언한 메서드의 리턴 타입과 동일한 타입으로 선언해 주시면 됩니다.

 

 

5) #{ } 표현식

MyBatis는 #{ 변수명 } 표현식을 이용해서 전달받은 파라미터를 기준으로 쿼리를 실행합니다.

 

 

7. SELECT 칼럼과 멤버 변수 매핑(바인딩)하기

MyBatis에서 SELECT 한 결괏값은 응답(Response) 클래스의 멤버 변수와 매핑되어야 합니다. 그러나 DB에서 테이블의 칼럼명은 언더스코어(_)로 연결된 스네이크 케이스를 사용하며, 자바에서 변수명은 소문자로 시작하고, 구분되는 단어의 앞 글자만 대문자로 처리하는 카멜 케이스를 사용합니다. 이럴 때 사용할 수 있는 방법은 대표적으로 세 가지 정도가 있습니다.


1. SELECT 하는 칼럼마다 별칭(Alias)을 지정한다.
2. MyBatis의 ResultMap을 이용한다.
3. properties(application.properties) 또는 mybatis-config 파일에 설정을 추가한다.


솔직히 1번은 효율적인 방법은 아니고, 2번은 XML Mapper가 지저분해지는 느낌이고, 단 한 줄의 코드로 SELECT 하는 모든 칼럼과 멤버 변수를 매핑해 주는 3번 방법을 이용해 볼 건데요. 3번 중에서도 properties를 이용해서 처리해 보도록 하겠습니다. 우선 application.properties에 다음의 설정을 추가해 주세요.

# column name to camel case
mybatis.configuration.map-underscore-to-camel-case=true

 

 

8. DatabaseConfig 클래스 수정하기

스프링이 properties에서 MyBatis 설정을 읽을 수 있도록 빈(Bean)을 선언해 주어야 합니다. DatabaseConfig 클래스를 다음과 같이 변경해 주세요.

package com.study.config;

import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;

@Configuration
@PropertySource("classpath:/application.properties")
public class DatabaseConfig {

    @Autowired
    private ApplicationContext context;

    @Bean
    @ConfigurationProperties(prefix = "spring.datasource.hikari")
    public HikariConfig hikariConfig() {
        return new HikariConfig();
    }

    @Bean
    public DataSource dataSource() {
        return new HikariDataSource(hikariConfig());
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource());
        factoryBean.setMapperLocations(context.getResources("classpath:/mappers/**/*Mapper.xml"));
        factoryBean.setConfiguration(mybatisConfig());
        return factoryBean.getObject();
    }

    @Bean
    public SqlSessionTemplate sqlSession() throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory());
    }

    @Bean
    @ConfigurationProperties(prefix = "mybatis.configuration")
    public org.apache.ibatis.session.Configuration mybatisConfig() {
        return new org.apache.ibatis.session.Configuration();
    }

}

 

 

다음은 기존 클래스에서 변경된 부분입니다.

기존 클래스에서 변경된 부분

 

 

1) factoryBean.setMapperLocations( ) - (39번 라인)

기존에 주석 처리되어 있던 코드입니다. 해당 메서드에 XML Mapper의 경로를 선언해 주어야 스프링이 XML Mapper를 인식할 수 있습니다. 기존에는 XML Mapper가 없었으나, PostMapper.xml이 추가되었으니 주석을 해제해 주어야 합니다.

 

 

2) mybatisConfig( ) - (49~53번 라인)

application.properties에서 mybatis.configuration으로 시작하는 모든 설정을 읽어 스프링 컨테이너에 빈(Bean)으로 등록합니다.

 

 

3) factoryBean.setConfiguration( ) - (40번 라인)

mybatisConfig( ) 빈(Bean)을 이용해서 MyBatis 옵션을 설정합니다.

 

 

9. CRUD 테스트해보기

기본적인 CRUD와 관련된 모든 SQL 쿼리 작성이 완료되었으니 테스트를 진행해 볼 차례입니다.


9-1) 테스트 클래스 추가 & 코드 작성하기

src/test/java의 com.study 패키지에 PostMapperTest 클래스를 추가하고, 다음의 코드를 작성해 주세요.

package com.study;

import com.study.domain.post.PostMapper;
import com.study.domain.post.PostRequest;
import com.study.domain.post.PostResponse;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import java.util.List;

@SpringBootTest
public class PostMapperTest {

    @Autowired
    PostMapper postMapper;

    @Test
    void save() {
        PostRequest params = new PostRequest();
        params.setTitle("1번 게시글 제목");
        params.setContent("1번 게시글 내용");
        params.setWriter("테스터");
        params.setNoticeYn(false);
        postMapper.save(params);

        List<PostResponse> posts = postMapper.findAll();
        System.out.println("전체 게시글 개수는 : " + posts.size() + "개입니다.");
    }

}

 

1) postMapper

@Autowired를 이용해서 스프링 컨테이너에 등록된 PostMapper 빈(Bean)을 클래스에 주입합니다.

 

 

2) save( )

게시글을 생성하는 메서드입니다. PostRequest 객체를 생성하고, set( ) 메서드를 이용해 값을 세팅한 후 PostMapper의 save( )를 호출합니다. 메서드가 호출되면 PostMapper.xml의 save 쿼리가 실행되며, #{ 변수명 } 표현식을 통해 PostRequest 객체의 멤버 변수에 접근하게 됩니다.


테이블의 PK인 id는 auto_increment에 의해 자동 증가되므로, 사실상 #{ id }는 무시된다고 생각해 주시면 됩니다.


<TIP> 오라클 등 auto_increment 기능이 없는 DB의 경우에는 시퀀스(Sequence)와 <selectKey> 태그를 이용해서 처리할 수 있습니다.


 

9-2) save( ) 테스트 & 테이블 SELECT 하기

로직을 다 작성하셨다면,  이전 글에서와 마찬가지로 메서드명을 더블 클릭해서 JUnit Test를 진행해 주세요.

save( ) 메서드 실행 결과

 

 

다음은 tb_post 테이블을 SELECT 한 결과입니다.

tb_post 테이블 SELECT 결과

 

 

9-3) findById( ) 테스트하기

다음은 테이블의 PK인 id를 WHERE 조건으로 특정 게시글을 조회하는 findById( )입니다.

    @Test
    void findById() {
        PostResponse post = postMapper.findById(1L);
        try {
            String postJson = new ObjectMapper().registerModule(new JavaTimeModule()).writeValueAsString(post);
            System.out.println(postJson);

        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

 

1) post 객체

앞에서 생성된 게시글의 PK인 '1'을 인자로 전달해서 게시글 상세정보를 조회합니다.

post 객체 (디버깅 모드)

 

 

2) postJson

스프링 부트에 기본으로 내장되어 있는 Jackson 라이브러리를 이용해서, 조회한 1번 게시글의 응답 객체를 JSON 문자열로 변환한 결과입니다. 객체는 디버깅을 해보지 않는 이상 확인이 까다롭기에 JSON 문자열로 변경해서 콘솔에 출력해 보았습니다.

 

JSON은 키(Key) - 밸류(Value) 쌍으로 이루어진 데이터 포맷입니다. 추후에 진행하는 REST API 방식의 댓글 처리 과정에서 자세히 설명드리도록 하겠습니다.

findById( ) 메서드 실행 결과

 

 

9-4) update( ) 테스트하기

다음은 기존에 등록된 게시글 정보를 수정하는 update( )입니다.

    @Test
    void update() {
        // 1. 게시글 수정
        PostRequest params = new PostRequest();
        params.setId(1L);
        params.setTitle("1번 게시글 제목 수정합니다.");
        params.setContent("1번 게시글 내용 수정합니다.");
        params.setWriter("도뎡이");
        params.setNoticeYn(true);
        postMapper.update(params);

        // 2. 게시글 상세정보 조회
        PostResponse post = postMapper.findById(1L);
        try {
            String postJson = new ObjectMapper().registerModule(new JavaTimeModule()).writeValueAsString(post);
            System.out.println(postJson);

        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }

 

update( )

게시글 수정은 생성과 마찬가지로 테이블에 데이터를 저장(Save)하는 개념입니다. 단지, 없었던 데이터를 생성하는 것인지, 기존 데이터를 수정하는 것인지의 차이만을 가집니다.

 

이 미묘한 차이는 PK인 id를 통해 구분할 수 있는데요. 테이블에 새로 생성되는 게시글은 auto_increment에 의해 자동 생성되지만, 게시글을 수정하기 위해서는 수정할 게시글의 PK인 id를 파라미터로 전달해 주어야 합니다.

 

만약 WHERE 조건 없이 UPDATE 쿼리가 실행된다면 모든 데이터가 동일한 값으로 UPDATE 되어버릴 테고, 실무에서 이런 일이 발생한다면 정말 커다란 사고가 됩니다. (5년 전의 제가 이 이야기를 싫어합니다.)

update 후 findById( ) 메서드 실행 결과

 

 

9-5) delete( ) 테스트하기

마지막으로 게시글을 삭제 처리하는 delete( )입니다.

    @Test
    void delete() {
        System.out.println("삭제 이전의 전체 게시글 개수는 : " + postMapper.findAll().size() + "개입니다.");
        postMapper.deleteById(1L);
        System.out.println("삭제 이후의 전체 게시글 개수는 : " + postMapper.findAll().size() + "개입니다.");
    }

 

delete( )

앞에서 말씀드렸듯이, 테이블에서 물리적으로 데이터를 삭제하지 않고, 삭제 여부(delete_yn) 상태 값을 0(false)에서 1(true)로 UPDATE 합니다. 추후에 게시글 리스트 페이지에는 delete_yn이 0(false)인 데이터만 사용자에게 노출합니다.

deleteById( ) 메서드 실행 결과

 

 

마치며

이번에는 게시글을 관리할 tb_post 테이블을 생성하고, MyBatis를 이용해서 기본적인 CRUD 쿼리를 작성하고, JUnit을 이용해서 단위 테스트를 해보았습니다. 다음 글부터는 MVC 패턴의 모든 영역을 처리하게 되는데요. 개발 순서는 다음과 같습니다.


  • 사용자의 요구를 처리하는 비즈니스 로직이 작성되는 모델(Model) 영역
  • 사용자가 보는 화면을 의미하는 뷰(View) 영역
  • 모델(Model)과 뷰(View)의 연결고리 역할을 해주는 컨트롤러(Controller) 영역

 

모델(Model) 영역은 일반적으로 서비스 레이어(Service Layer)라고 표현하며, 뷰(View) 영역은 프레젠테이션 레이어(Presentation Layer) 또는 UI(User Interface)라고 표현합니다. 각 영역별 처리 방법은 다음 글에서 자세히 알아보도록 하겠습니다.

 

추가적으로 이번 글부터는 작업한 프로젝트를 압축해서 첨부하도록 하겠습니다.
오늘도 글을 읽어주신 여러분께 진심으로 감사의 말씀을 전합니다. 좋은 하루 보내세요 :)

Board.zip
0.26MB

반응형

댓글