MyBatis는 JdbcTemplate와 유사한 계열의 SQL Mapper이다.
MyBatis의 강력한 강점으로는 JdbcTemplate에서 불편했던 동적 쿼리의 작성에서 나타난다.
그 뿐문 아니라 SQL을 XML에 편리하게 작성함으로써 SQL을 여러줄 작성할때 공백으로 인해서 주의하지 않아도 된다.
JdbcTemplate
String sql = "update item " + "set item_name=:itemName, price=:price, quantity=:quantity " + "where id=:id";
MyBatis
<update id="update"> update item set item_name=#{itemName}, price=#{price}, quantity=#{quantity} where id = #{id} </update>
다만, JdbcTemplate는 스프링에 내장된 기능이고 별도의 설정없이 사용할 수 있다는 장점이 있다. 반면에 MyBatis는 약간의 설정이 필요하다.
정리
프로젝트에 동적 쿼리와 복잡한 쿼리가 많다면 MyBatis를 사용하고 단순한 쿼리들이 많으면 JdbcTemplate를 선택해서 사용하면 된다.
mybatis-spring-boot-starter 라이브러리를 사용하면 MyBatis를 스프링과 통합하고 설정도 아주 간단히 할 수 있다.
build.gradle에 의존관계를 추가한다.
//MyBatis 추가
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'
참고
의존관계 뒤에 버전이 붙는 이유는 스프링ㅂ ㅜ트가 버전을 관리해주는 공식 라이브러리가 아닉 때문이다. 스프링 부트가 관리해주는 경우 버전을 붙이지 않아도 최적의 버전을 자동으로 찾아준다.
ex) springboot-start-xxx 이런건 안넣어줘도됨
참고
MyBatis는 이전에는 iBatis로 불려서 패키지 이름에도 iBatis로 되어 있는 경우가 있으니 신경쓰지 않아도 된다.
의존 관계를 추가하면 아래와 같이 라이브러리가 추가 된다.
application.properties에는 아래의 설정을 추가한다.
#MyBatis
mybatis.type-aliases-package=hello.itemservice.domain
mybatis.configuration.map-underscore-to-camel-case=true
logging.level.hello.itemservice.repository.mybatis=trace
자바 객체에는 주로 카멜 표기법을 사용한다. itemName처럼 중간에 낙타 봉이 올라와 있는 표기법을 말한다.
반면에 관계형 데이터베이스에서는 주로 언더스코어를 사용하는 snake_case 표기법을 사용한다. item_name 처럼 중간에 언더스코어를 사용하는 표기법이다.
이렇게 관례로 많이 사용하다보니 map-underscore-to-camel-case 기능을 활서오하하면 언더스코어 표기법을 카멜로 자동 변환해준다. 따라서 DB에서 select item_name 으로 조회해도 객체의 itemName(setItemName()) 속성에 값이 정상 입력된다.
정리하면 해당 옵션을 켜면 snake_case는 자동으로 해결되니 그냥 두면 되고, 컬럼 이름과 객체 이름이 완전히 다른 경우에는 SQL에서 별칭을 사용하면 된다.
MyBatis를 사용하기 위해서 작성한 Mapper 클래스는 아래와 같다.
import hello.itemservice.domain.Item;
import hello.itemservice.repository.ItemSearchCond;
import hello.itemservice.repository.ItemUpdateDto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import java.util.List;
import java.util.Optional;
@Mapper
public interface ItemMapper {
void save(Item item);
void update(@Param("id") Long id, @Param("updateParam") ItemUpdateDto updateParam);
Optional<Item> findById(Long id);
List<Item> findAll(ItemSearchCond itemSearch);
}
이제 같은 위치에 실행할 SQL이 있는 XML 맵핑 파일을 만들어 주면 된다.
참고로 자바 코드가 아니기 때문에 src/main/resource 하위에 만들되 패키지 위치는 Mapper 인터페이스와 동일해야한다.
src/main/resources/hello/itemservice/repository/mybatis/ItemMapper.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="hello.itemservice.repository.mybatis.ItemMapper">
<insert id="save" useGeneratedKeys="true" keyProperty="id">
insert into item (item_name, price, quantity)
values (#{itemName}, #{price}, #{quantity})
</insert>
<update id="update">
update item
set item_name=#{updateParam.itemName},
price=#{updateParam.price},
quantity=#{updateParam.quantity}
where id = #{id}
</update>
<select id="findById" resultType="Item">
select id, item_name, price, quantity
from item
where id = #{id}
</select>
<select id="findAll" resultType="Item">
select id, item_name, price, quantity
from item
<where>
<if test="itemName != null and itemName != ''">
and item_name like concat('%', #{itemName}, '%')
</if>
<if test="maxPrice != null">
and price <= #{maxPrice}
</if>
</where>
</select>
</mapper>
참고)
XML 파일을 원하는 위치에 두고 싶으면 application.properties에 아래와 같이 설정하면 된다.
mybatis.mapper-locations=classpath:mapper/*/.xml
이렇게 하면 resouces/mapper를 포함한 그 하위 폴더에 있는 XML을 XML 맵핑 파일로 인식한다. 이 경우 파일 이름은 자유롭게 설정해도 된다.
XML에세너느 데이터 영역에 <,>와 같은 특수문자를 사용할 수 없기 때문에
아래와 같이 대체해서 사용한다.
< : <
> : >
& : &
MyBatis가 @Mapper를 보고 구현체를 만들어서 스프링빈에 등록해준다.
특히, MyBatis 모듈이 데이터소스와 트랜잭션 매니저를 알아서 읽어서 사용하므로 따로 주입받을 필요는 없다.
void save(Item item);
<insert id="save" useGeneratedKeys="true" keyProperty="id">
insert into item (item_name, price, quantity)
values (#{itemName}, #{price}, #{quantity})
</insert>
void update(@Param("id") Long id, @Param("updateParam") ItemUpdateDto updateParam);
<update id="update">
update item
set item_name=#{updateParam.itemName},
price=#{updateParam.price},
quantity=#{updateParam.quantity}
where id = #{id}
</update>
Optional<Item> findById(Long id);
<select id="findById" resultType="Item">
select id, item_name, price, quantity
from item
where id = #{id}
</select>
<select
>를 사용하면 된다.List<Item> findAll(ItemSearchCond itemSearch);
<select id="findAll" resultType="Item">
select id, item_name, price, quantity
from item
<where>
<if test="itemName != null and itemName != ''">
and item_name like concat('%',#{itemName},'%')
</if>
<if test="maxPrice != null">
and price <= #{maxPrice}
</if>
</where>
</select>
MyBatis 스프링 연동 모듈에서 구현체를 자동으로 생성해서 스프링 빈으로 등록해준다. 따라서 개발자는 Mapper 인터페이스만 생성해서 SQL 파일과 연결해주면 된다.
정리
- 매퍼 구현체 덕분에 마이바티스를 스프링에 편리하게 통합해서 사용할 수 있다.
- 매퍼 구현체를 사용하면 스프링 예외 추상화도 함께 적용된다
- 마이바티스 스프링 연동 모듈이 많은 부분을 자동으로 설정해주는데, 데이터베이스 커넥션, 트랜잭션과 관련된 기능도 마이바티스와 함께 연동하고 동기화해준다.
참고
MyBatis 공식 메뉴얼: https://mybatis.org/mybatis-3/ko/index.html
MyBatis 스프링 공식 메뉴얼: https://mybatis.org/spring/ko/index.htm
마이바티스에서 동적 쿼리를 위해서 제공되는 기능은 아래와 같다.
// if
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
// choose, when, otherwise
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG WHERE state = ‘ACTIVE’
<choose>
<when test="title != null">
AND title like #{title}
</when>
<when test="author != null and author.name != null">
AND author_name like #{author.name}
</when>
<otherwise>
AND featured = 1
</otherwise>
</choose>
</select>
// trim,where, set
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</select>
위 예제는 문장이 모두 만족하지 않을때 발생한다.
SELECT * FROM BLOG
WHERE
그리고 title만 만족할 때도 문제가 발생한다.
SELECT * FROM BLOG
WHERE
AND title like ‘someTitle’
결국 <where>
문을 언제 넣어야할지 상황에 따라서 동적으로 달라진다.
<where>
를 사용하면 이런 문제를 해결할 수 있다.
<select id="findActiveBlogLike"
resultType="Blog">
SELECT * FROM BLOG
<where>
<if test="state != null">
state = #{state}
</if>
<if test="title != null">
AND title like #{title}
</if>
<if test="author != null and author.name != null">
AND author_name like #{author.name}
</if>
</where>
</select>
<where>
는 문장이 없으면 where을 추가하지 않는다.문장이 있으면 where을 추가한다. 만약 and가 먼저 시작된다면 and를 지운다.
// foreach
<select id="selectPostIn" resultType="domain.blog.Post">
SELECT *
FROM POST P
<where>
<foreach item="item" index="index" collection="list"
open="ID in (" separator="," close=")" nullable="true">
#{item}
</foreach>
</where>
</select>
XML 대신 애노테이션으로 SQL을 작성할 수 도 있다.
@Select("select id, item_name, price, quantity from item where id=#{id}")
Optional<Item> findById(Long id);
<select id="findById">
~</select>
는 제거해야한다.해당 포스팅은 아래의 강의를 공부하여 정리한 내용입니다 .
김영한님의 SpringDB2-MyBatis