✴️ 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>
✴️ 인터페이스, 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"
✅ insert | delete | update | seleteOne /selectList/selectMap
parameterType (입력) → [ ] → (출력) ResultType
✔️ 별명: 대소문자 구분 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>
✔️ DB 테이블 생성 → MapperXML & DTO 작성 → DAO 인터페이스 작성 → DAO 인터페이스 구현 & 테스트
관심사와 역할에 의해 분리된 계층 간의 데이터를 주고 받기 위해 사용되는 객체, 비지니스 로직을 가지지 않는 순수한 데이터 객체(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내의 특수문자 변환 필요 | 참고링크
ex. <➡️<, >➡️>, &➡️&
❷ 특수문자가 포함된 쿼리를 <![CDATA[~내용~]]>
으로 감쌈
✔️ CDATA=Character data, CDATA 내에서는 xml태그가 작동하지 않고 전부 문자데이터
참고) 자바의 정석 | 남궁성과 끝까지 간다