[Mybatis] 게시판 CRUD 처리하기

윤재열·2022년 6월 10일
1

Spring

목록 보기
61/72
post-custom-banner

그동안 JPA 로만 공부하다가 이번 팀원끼리 하는 사이드 프로젝트에서는 Mybatis를 사용하기로 하여 Mybatis 를 공부해보려고 합니다.

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

CREATE TABLE tb_board (
    idx INT 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 NOT NULL DEFAULT 0 COMMENT '조회 수',
    notice_yn ENUM('Y', 'N') NOT NULL DEFAULT 'N' COMMENT '공지글 여부',
    secret_yn ENUM('Y', 'N') NOT NULL DEFAULT 'N' COMMENT '비밀글 여부',
    delete_yn ENUM('Y', 'N') NOT NULL DEFAULT 'N' COMMENT '삭제 여부',
    insert_time DATETIME NOT NULL DEFAULT NOW() COMMENT '등록일',
    update_time DATETIME NULL COMMENT '수정일',
    delete_time DATETIME NULL COMMENT '삭제일',
    PRIMARY KEY (idx)
)  COMMENT '게시판';
  • 테이블의 생성 결과를 확인하는 방법은
    • SQL 콘솔창에 DESC tb_board ,SHOW TABLES 명령어를 입력합니다.

2. 도메인 클래스 생성하기

  • 게시판 테이블(TB_BOARD)의 구조화 역할을 하는 클래스를 생성합니다.

  • 보통, 테이블 구조화 클래스는 xxxVO,xxxDTO로 네이밍 합니다.

    • VO는 Read Only(읽기 전용)의 특성을 갖습니다.
  • 저는 입력받은 데이터의 저장 및 전송을 의미하는 xxxDTO로 네이밍을 하겠습니다.

  • 우선 com.codej.board 패키지에 controller,domain,mapper,service 네 개의 패키지를 추가해줍니다.

  • 다음으로 domain 패키지에 BoardDTO 클래스를 추가하고,다음의 코드를 작성해줍니다.

@Getter
@Setter
public class BoardDTO {

	/** 번호 (PK) */
	private Long idx;

	/** 제목 */
	private String title;

	/** 내용 */
	private String content;

	/** 작성자 */
	private String writer;

	/** 조회 수 */
	private int viewCnt;

	/** 공지 여부 */
	private String noticeYn;

	/** 비밀 여부 */
	private String secretYn;

	/** 삭제 여부 */
	private String deleteYn;

	/** 등록일 */
	private LocalDateTime insertTime;

	/** 수정일 */
	private LocalDateTime updateTime;

	/** 삭제일 */
	private LocalDateTime deleteTime;

}

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

  • 다음으로 데이터베이스와의 통신 역할을 하는 Mapper 인터페이스를 생성해보도록 합니다.
  • 앞에서 추가한 mapper 패키지에 BoardMapper 인터페이스를 생성하고,다음의 코드를 작성합니다.
import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import com.board.domain.BoardDTO;

@Mapper
public interface BoardMapper {

	public int insertBoard(BoardDTO params);

	public BoardDTO selectBoardDetail(Long idx);

	public int updateBoard(BoardDTO params);

	public int deleteBoard(Long idx);

	public List<BoardDTO> selectBoardList();

	public int selectBoardTotalCount();

}

@Mapper

  • 기존의 스프링은 DAO(Data Access Object)클래스에 @Repository를 선언해서 해당 클래스가 데이터베이스와 통신하는 클래스임을 명시해주었습니다.
  • 하지만 mybatis는 인터페이스에 @Mapper만 지정해주면 XML Mapper 에서 메서드의 이름과 일치하는 SQL문을 찾아 실행합니다.
  • Mapper 영역은 데이터베이스와의 통신, 즉 SQL 쿼리를 호출하는 것이 전부이며, 다른 로직은 필요가 없습니다.

insertBoard

  • 게시글을 생성하는 INSERT 쿼리를 호출하는 메서드입니다.
  • 파라미터로는 BoardDTO 클래스가 params라는 이름으로 지정되었고,params에는 게시글의 정보가 담기게 됩니다.

selectBoardDetail

  • 하나의 게시글을 조회하는 SELECT 쿼리를 호출하는 메서드입니다.
  • SELECT 쿼리가 실행되면, 각 칼럼에 해당하는 결과값이 리턴 타입으로 지정된 BoardDTO 클래스의 멤버 변수에 매핑됩니다.
    파라미터로는 게시글 번호(idx),즉 PK를 전달 받으며,
    WEHRE 조건으로 idx를 사용하여 특정 게시글을 조회합니다.

updateBoard

  • 게시글을 수정하는 UPDATE 쿼리를 호출하는 메서드입니다.
  • 파라미터로는 BoardDTO 클래스가 params라는 이름으로 지정되었고,insertBoard 메서드와 마찬가지로 params 에는 게시글의 정보가 담기게 됩니다.

deleteBoard

  • 게시글을 삭제하는 DELETE 쿼리를 호출하는 메서드입니다.
  • 우리는 테이블을 생성하면서 delete_yn 칼럼을 추가하였습니다.
  • 해당 컬럼은 실제로 데이터를 삭제하지 않고,컬럼의 상태값을 'Y', 또는 'N'으로 지정하여 상태 값이 'N'으로 지정된 데이터만 노출하게끔 하기 위한 역할을 합니다.
    정말 중요한 데이터가 테이블에서 DELETE가 되어버리면 손실이 크기 때문에 최근에는 이러한 방법을 많이 사용한다고 합니다.
  • 파라미터로는 게시글 조회와 마찬가지로 게시글 번호(idx)를 전달받으며,WHERE 조건으로 idx 를 사용하여 특정 게시글의 삭제(상태 값 변경)합니다.

selectBoardList

  • 게시글 목록을 조회하는 SELECT 쿼리를 호출하는 메서드입니다.
  • 리턴 타입으로 지정된 List<BoardDTO>와 같이 "< >"안에 타입을 파라미터로 갖는 형ㅌ태를 제네릭 타입이라고 합니다.
  • 십게 이야기하면, 리스트 안에 하나의 게시글을 조회하는 selectBoardDetail 메서드를 호출한 결과를 여러개 저장하는 것과 유사합니다.

selectBoardTotalCount

  • 삭제 여부(delete_yn)가 'N'으로 지정된 게시글의 개수를 조회하는 SELECT 쿼리를 호출하는 메서드입니다.
  • 나중에 페이징 처리를 진행하면서 사용됩니다.

리턴 타입

  • 게시글 생성,수정,삭제 메서드의 리턴 타입은 int로 지정되어 있습니다.
  • 보통은 void 를 리턴 타입으로 갖는 경우가 많습니다.
  • 하지만, 서비스 영역에서 Mapper 영역의 메서드를 호출하고, SQL 실행에 대한 결과값을 확실하게 전달받기 위해 int로 처리합니다.
  • 추후에 서비스 영역에서 비지니스 로직을 작성하는 과정에서 다시한번 다루겠습니다.

4. Mybatis XML Mapper 생성하기

  • 다음으로 BoardMapper 인터페이스와 SQL 문의 연결을 위해 마이바티스의 XML Mapper 에 SQL 문을 작성해 보도록 합니다.
  • 우선, src/main/resources 디렉토리에 mappers 폴더를 생성해줍니다.
  • mapper 폴더 하위에 BoardMapper.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.board.mapper.BoardMapper">

    <sql id="boardColumns">
            idx,
            title,
            content,
            writer,
            view_cnt,
            notice_yn,
            secret_yn,
            delte_yn,
            insert_time,
            update_time,
            delte_time
    </sql>

    <insert id="insertBoard" parameterType="BoardDTO">
        INSERT INTO tb_board(
                <include refid="boardColumns"/>
        )VALUES (
                 #{idx},
                 #{title},
                 #{content},
                 #{writer},
                 0,
                 IFNULL(#{notice_yn},'N'),
                 IFNULL(#{secret_yn},'N'),
                 'N',
                 NOW(),
                 NULL,
                 NULL
        )
    </insert>

    <select id="selectBoardDetail" parameterType="Long" resultType="BoardDTO">
        SELECT 
            <include refid="boardColumns"/>
        FROM
            tb_board
        WHERE 
            delete_yn = 'N'
        AND
            idx = #{idx};
    </select>
    
    <update id="updateBoard" parameterType="BoardDTO">
        UPDATE tb_board
        SET
            update_time = NOW(),
            title = #{title},
            content = #{content},
            writer = #{writer},
            notice_yn = IFNULL(#{noticeYn},'N'),
            secret_yn = IFNULL(#{secretYn},'N')
        WHERE 
            idx =#{idx}
    </update>
    
    <update id="deleteBoard">
        UPDATE tb_board
        SET
            delete_yn = 'Y',
            delete_time = NOW()
        WEHRE 
            idx = #{idx}
    </update>
    
    <select id="selectBoardList" parameterType="BoardDTO" resultType="BoardDTO">
        SELECT
            <include refid="boardColumns"/>
        FROM
            tb_board
        WEHRE 
            delete_yn = 'N'
        ORDER BY
            notice_yn ASC,
            idx DESC,
            insert_time DESC
    </select>
    
    <select id="selectBoardToTalCount" parameterType="BoardDTO" resultType="int">
        SELECT
            COUNT(*)
        FROM
            tb_board
        WHERE 
            delete_yn = 'N'
    </select>
</mapper>

mapper 태그

  • 마이바티스에서 SQL 쿼리 문이 정의되어 있는 파일을 XML Mapper 라고 이야기 합니다.
  • 태그의 시작과 끝 사이에는 쿼리문과 관련된 여러 개의 태그가 존재합니다.
  • 여기서, <mapper> 태그를 여는 부분의 네임스페이스 속성에는 BoardMapper 인터페이스의 경로가 지정되어 있습니다.
  • 네임스페이스는 XML Mapper 의 SQL 쿼리 문과 Mapper 인터페이스의 메서드를 매핑하기 위해 지정하는 속성입니다.

sql 태그

  • 마이바티스는 <sql> 태그를 이용하여 태그의 시작과 끝 사이에 공통으로 사용되거나, 반복적으로 사용되는 SQL 조각을 정의할 수 있습니다.
  • 예를 들어 SELECT 절에 에스터리스크("*")를 지정하여 전체 칼럼을 조회할 수 있지만, 서브 쿼리나 조인을 이용하면, 칼럼을 하나씩 SELECT 절에 지정해야 하는 경우가 발생합니다.
  • 만약 칼럼이 30개 이상이라면 30개의 칼럼을 하나씩 SELECT 절에 지정해야 하고,
    얘기치 못하게 테이블의 칼럼 구조가 변경된다면,
    변경될 떄마다 XML Mapper를 수정해야 하는데,
    빈번한 수정 작업은 결코 쉽지 않은 일입니다.
  • 그래서 이러한 문제를 사전에 방지하고,
    더욱 효율적으로 SQL 문을 처리할 수 있도록 게시판 테이블의 전체 칼럼을 SQL 조각으로 정의하여 boardColumns 라는 이름으로 사용합니다.

include 태그

  • <sql>태그에 정의한 boardColumns SQL 조각의 인클루드에 사용되는 태그입니다.
  • refid에 <sql> 태그의 id를 적어줍니다.

parameterType

  • 쿼리의 실행에 필요한 파라미터의 타입을 해당 속성에 지정합니다.

resultType

  • 쿼리의 실행 결과를 매핑할 타입을 지정합니다.

파라미터 표현식

  • 전달받은 파라미터는 #{} 표현식을 사용하여 처리합니다.

IF,IFNULL,NULLIF

  • IF(ex1,ex2,ex3)
    • ex1 이 true 이면, ex2 리턴, false 라면, ex3 리턴
  • IFNULL(ex1,ex2)
    • ex1 이 NULL 이라면, ex2 리턴, NULL이 아니라면 ex1리턴
  • NULLIF(ex1,ex2)
    • ex1 = ex2 가 true 라면 NULL,
      false라면 ex1리턴
    • CASE WHEN ex1 = ex2 THEN NULL ELSE ex1 END 와 같습니다.

5.MyBatis SELECT 컬럼과 DTO 멤버 변수 매핑하기

  • 마이바티스에서 SELECT 쿼리의 결과 칼럼은 DTO 클래스의 멤버 변수와 매핑됩니다.
  • 하지만, XML Mapper의 boardColumns SQL 조각은 notice_yn AS noticeYn 와 같이 별칭처리를 하지 않고, 테이블의 컬럼명과 같이 언더바(_)로 연결하는 스네이크 케이스를 사용합니다.
    하지만, 자바에서 변수의 이름은 소문자로 시작하고,구분되는 단어는 앞글자만 대문자로 처리하는 카멜 케이스를 사용합니다.
  • 이러한 경우에 마이바티스의 map-underscore-to-comel-case 설정을 사용하면 자동으로 매핑되도록 처리할 수있습니다.
#MyBatis
mybatis.configuration.map-underscore-to-camel-case=true

6.DBConfiguration 클래스 처리하기

  • application.properties에 마이바티스 설정이 추가되었으니, 해당 설정을 처리할 빈(Bean)을 정의해야 합니다.
  • DBConfiguration 클래스를 변경해줍니다.
mport javax.sql.DataSource;

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 com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;

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

	@Autowired
	private ApplicationContext applicationContext;

	@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(applicationContext.getResources("classpath:/mappers/**/*Mapper.xml"));
		factoryBean.setTypeAliasesPackage("com.board.domain");
		factoryBean.setConfiguration(mybatisConfg());
		return factoryBean.getObject();
	}

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

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

}

7. CRUD 테스트하기

  • src/test/java 디렉토리에 board 패키지를 만들고 MapperTests 클래스를 생성합니다.

insert 테스트

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import com.board.domain.BoardDTO;
import com.board.mapper.BoardMapper;

@SpringBootTest
class MapperTests {

	@Autowired
	private BoardMapper boardMapper;

	@Test
	public void testOfInsert() {
		BoardDTO params = new BoardDTO();
		params.setTitle("1번 게시글 제목");
		params.setContent("1번 게시글 내용");
		params.setWriter("테스터");

		int result = boardMapper.insertBoard(params);
		System.out.println("결과는 " + result + "입니다.");
	}

}

하나의 게시글을 조회하는 기능

	@Test
	public void testOfSelectDetail() {
		BoardDTO board = boardMapper.selectBoardDetail((long) 1);
		try {
			//String boardJson = new ObjectMapper().writeValueAsString(board);
            String boardJson = new ObjectMapper().registerModule(new JavaTimeModule()).writeValueAsString(board);

			System.out.println("=========================");
			System.out.println(boardJson);
			System.out.println("=========================");

		} catch (JsonProcessingException e) {
			e.printStackTrace();
		}
	}
profile
블로그 이전합니다! https://jyyoun1022.tistory.com/
post-custom-banner

0개의 댓글