[Spring] 13. MyBatis

Hyeongmin Jung·2023년 8월 22일
0

Spring

목록 보기
12/17

🐣 MyBatis 소개와 설정

🪄 MyBatis

✴️ SQL Mapping Framework: Easy & Simple
✔️ 자바 코드에서 SQL문을 별도의 XML로 분리해서 관리
✔️ 매개변수 설정과 쿼리 결과를 읽어오는 코드 제거
ex. setString(), setInt(), getInt(), getString()
✔️ 작성할 코드가 줄어서 생산성 향상 & 유지 보수 편리

✴️ [Spring 5.0+] - [mybatis-spring 22.0+] - [Mybatis 3.5+]

✴️ maven dependency 추가: mybatis, mybatis-spring

pom.xml

<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis</artifactId>
	<version>3.5.9</version>
</dependency>
<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis-spring</artifactId>
	<version>2.0.7</version>
</dependency>

⚫ SqlSessionFactoryBean과 SqlSessionTemplate

✴️ 인터페이스, mybatis이 제공
SqlSessionFactory : SqlSession을 생성해서 제공
SqlSession : SQL명령을 수행하는데 필요한 메서드 제공

✴️ → 인터페이스 구현, MyBatis Spring이 제공
SqlSessionFactoryBean : SqlSessionFactory를 Spring에서 사용하기 위한 빈
SqlSessionTemplate : SQL 명령을 수행하는데 필요한 메서드 제공, thread-safe
    ✔️ SqlSessionTemplate를 이용해서 Dao(BoardDao, UserDao)를 작성
    ✔️ thread-safe이기 때문에 멀티 쓰레드에 안전하여 Dao들은 SqlSessionTemplate공유 가능

root-content.xml

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<property name="dataSource" ref="dataSource"/>
	<property name="mapperLocations" value="classpath:mapper/*Mapper.xml"/>
    <property name="mapperLocations" value="classpath:mapper/*Mapper.xml"/>
</bean>

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
	<constructor-arg ref="sqlSessionFactory"/>
</bean>

✔️ SqlSessionFactoryBean 등록할 때 Mapper.xml(SQL문서) 위치, 이름, 패턴 지정
ex. "classpath:mapper/*Mapper.xml"

⚫ SqlSession의 주요 메서드

✅ insert | delete | update | seleteOne /selectList/selectMap

⚫ Mapper XML 작성

parameterType (입력) → [       ] → (출력) ResultType

<typeAliases>로 이름 별칭주기

✔️ 별명: 대소문자 구분 X (INT = int)
✔️ 실제이름: 대소문자 구별 O (INT ≠ int)

✔️ com.fastcampus.ch4.domain.BoardDto → BoardDto

mybatis-config.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <typeAliases>
        <typeAlias alias="BoardDto" type="com.fastcampus.ch4.domain.BoardDto"/>
    </typeAliases>
</configuration>

🐥 MyBatis로 DAO 작성

✔️ DB 테이블 생성 → MapperXML & DTO 작성 → DAO 인터페이스 작성 → DAO 인터페이스 구현 & 테스트


🐥 DTO(Data Transfer Object)

관심사와 역할에 의해 분리된 계층 간의 데이터를 주고 받기 위해 사용되는 객체, 비지니스 로직을 가지지 않는 순수한 데이터 객체(getter & setter만 가진 클래스)

  • cf )
    DAO: 데이터베이스의 data에 접근하기 위한 객체
    DTO: 계층 간 데이터 교환을 하기 위해 사용하는 객체
    VO(Value Object): 값 오브젝트, read-Only(사용중 변경 불가, 오직 읽기만 가능)

  • cf ) int로 선언하면 null 입력 시 변환에러 발생, but integer로 선언하면 null 입력 시 변환에러 발생X

private Integer bno;

⚫ 예외처리

✔️ Reository에서는 예외처리하지 않고 보고만
Service에서 처리
Controller에서 처리
❸ 두 곳 모두에서 처리

⚫ 실습

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.fastcampus.ch4.dao.BoardMapper">
    <select id="select" parameterType="int" resultType="BoardDto">
        select bno, title, content, writer,
               view_cnt, comment_cnt, reg_date, up_date
        from board
        where bno = #{bno};
    </select>
</mapper>

BoardDao.java

package com.fastcampus.ch4.dao;

import com.fastcampus.ch4.domain.BoardDto;
import org.apache.ibatis.session.SqlSession;
import org.springframework.beans.factory.annotation.Autowired;

// public class BoardDao → Refactor → Extract interface → ✔️rename 
@Repository
public class BoardDaoImpl implements BoardDao {
    @Autowired
    SqlSession session;
    
    String namespace = "com.fastcampus.ch4.dao.BoardMapper.";

    BoardDto select(int bno) throws Exception{
        return session.selectOne(namespace+"select",bno);
    }
}

BoardDto.java

package com.fastcampus.ch4.domain;

import java.util.Date;
import java.util.Objects;

public class BoardDto {
    private Integer bno;
    private String  title;
    private String  content;
    private String  writer;
    private int     view_cnt;
    private int     comment_cnt;
    private Date reg_date;

	// constructor
    public BoardDto() {}
    public BoardDto(String title, String content, String writer) {
        this.title = title;
        this.content = content;
        this.writer = writer;
    }

	// tostring
    @Override
    public String toString() {
        return "BoardDto{" +
                "bno=" + bno +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", writer='" + writer + '\'' +
                ", view_cnt=" + view_cnt +
                ", comment_cnt=" + comment_cnt +
                ", reg_date=" + reg_date +
                '}';
    }

	// equals() and hashcode()
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        BoardDto boardDto = (BoardDto) o;
        return Objects.equals(bno, boardDto.bno) && Objects.equals(title, boardDto.title) && Objects.equals(content, boardDto.content) && Objects.equals(writer, boardDto.writer);
    }

    @Override
    public int hashCode() {
        return Objects.hash(bno, title, content, writer);
    }
    
    // getter & setter 생략
}

BoardDaoImpleTest.java

BoardDaoImpl goto → test → ✔️Jnit4

package com.fastcampus.ch4.dao;

import com.fastcampus.ch4.domain.BoardDto;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import static org.junit.Assert.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"file:src/main/webapp/WEB-INF/spring/**/root-context.xml"})
public class BoardDaoImplTest {
    @Autowired
    BoardDao boardDao;

    @Test
    public void select() throws Exception {
        assertTrue(boardDao != null);
        System.out.println("boardDao = " + boardDao);

        BoardDto boardDto = boardDao.select(1);
        System.out.println("boardDto = " + boardDto);
        assertTrue(boardDto.getBno().equals(1));
    }
}

🐤 #{} 와 ${}의 차이

#{}:
✔️ PreparedStatement, 값에만 사용 가능
✔️ 타입에 따라 자동으로 따옴표(') 붙여줌
✔️ 값에만 사용가능하기 때문에 SQL Injection 방지 가능
(#{title}, #{content}. #{writer}) ➡️ (?, ?, ?)

${}:
✔️ 일반 statement를 사용하기 때문에 따옴표(') 직접 붙여야함
✔️ 유연하고 제약이 적음
✔️ 동적 sql을 자유롭게 구현해야할 때 내부적으로 사용(SQL Injection 방지가 안 되므로)
✔️ 값뿐만 아니라 테이블 명도 동적으로 적용 가능 ${tablename}
('${title}', '${content}'. '${writer}') ➡️ " ('"+title+"','"+content+"','"+writer+"') "

🐤 XML의 특수문자 처리

❶ XML내의 특수문자 변환 필요 | 참고링크
ex. &lt;➡️<, &gt;➡️>, &amp;➡️&

❷ 특수문자가 포함된 쿼리를 <![CDATA[~내용~]]>으로 감쌈
✔️ CDATA=Character data, CDATA 내에서는 xml태그가 작동하지 않고 전부 문자데이터


참고) 자바의 정석 | 남궁성과 끝까지 간다

0개의 댓글