MyBatis
MyBatis는 SQL Mapper의 하나로 스프링에서 제공해주는 JdbcTemplate보다 더 많은 기능을 제공해준다.
학원 및 회사에서 접했던 MyBatis의 장점을 꼽으면 SQL을 로직과 분리하는 정책을 삼으면 SQL을 따로 관리할 수 있어 관리성에 있어서 매우 편리했고, 동적 쿼리에 대한 문제를 손쉽게 처리하여 작성할 수 있었다.
MyBatis에 대한 깊은 지식 습득이 목표가 아닌 MyBatis의 간단한 사용법, 사용목적, 장단점을 파악하는 용도로만 공부할 예정이다.
간단히 XML에 작성된 쿼리를 살펴보자
<update id = "update">
update item
set item_name = #{itemName},
price = #{price},
quantity = #{quantity}
where id = #{id}
</update>
XML에 작성한다는 것은 기존의 String 형식으로 SQL을 작성했던 것보다 안전성을 챙길 수 있다.
또한 MyBatis가 처리하는 동적 쿼리는 다음과 같다.
<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를 통해 처리하는 동적 쿼리를 보면 기존 jdbcTemplate을 통해 처리하는 동적 쿼리랑 차이가 존재한다.
도움을 주는 태그들을 이용해 더욱 쉽게 sql문을 작성할 수 있기에 가독성이 뛰어나고 로직에 동적 쿼리를 처리하는 로직이 빠짐으로써 DAO 계층의 로직이 더욱 가독성이 좋아진다.
하지만 처음 MyBatis를 사용하는 사람에게는 추가적인 문법을 공부해야 하고 약간의 설정이 필요하다.
이와 반대로 JdbcTemplate 같은 경우에는 스프링에 내장된 기능으로 별도의 설정 없이 편리하게 사용할 수 있다는 장점이 있다.
MyBatis 설정
MyBatis는 spring이 공식적으로 관리하지 않는 라이브러리이기에 적절한 버전의 라이브러리를 추가해야 한다.
org.mybatis.spring.boot:mybatis-spring-boot-stater:2.2.0
- mybatis-spring-boot-starter : MyBatis를 스프링 부트에서 편리하게 사용할 수 있게 시작하는 라이브러리
- mybatis-spring-boot-autoconfigure : MyBatis와 스프링 부트 설정 라이브러리
- mybatis-spring : MyBatis와 스프링을 연동하는 라이브러리
- mybatis : MyBatis 라이브러리
이후 편리하게 사용하기 위해 application.properties에 추가하는 설정이 존재한다.
https://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/
해당 화면을 참고해 필요한 부분의 설정을 application.properties 할 수 있으니 참고 하자.
다만 XML을 통한 셋팅을 진행할 때는 다음 doc를 참고하면 된다.
XML 사용
마이바티스는 XML을 호출해주는 인터페이스가 필요하다.
이 인터페이스에 @Mapper라는 어노테이션을 붙여주어야 하는데 이래야 Mybatis가 인식할 수 있기 때문이다.
@Mapper
public interface ItemMapper {
void save(Item item);
void update(@Param("id") Long id, @Param("updateParam") ItemUpdateDto updateParam);
List<Item> findAll(ItemSearchCond itemSearch);
Optional<Item> findById(Long id);
}
해당 인터페이스의 메서드를 호출하면 다음과 같이 작성된 XML이 해당 sql을 실행하고 결과를 돌려준다.
<?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의 위치는 interface가 존재하는 패키지 명에 맞춘 디렉터리 구조로 resource에 만들어주면 된다.
만약 XML 파일을 원하는 위치에 두고 싶으면 application.properties 에 다음과 같이 설정하면 된다.
mybatis.mapper-locations=classpath:mapper/**/*. xml
이렇게 하면 resources/mapper를 포함한 그 하위 폴더에 있는 XML을 XML 매핑 파일로 인식한다.
이 경우 파일 이름은 자유롭게 설정해도 된다.
참고로 테스트의 application.properties 파일도 함께 수정해야 테스트를 실행할 때 인식할 수 있다.
MyBatis 스프링 연동 모듈이 구현체를 자동으로 만들어주는 부분의 그림이다.
실제로 MyBatis를 사용하면 인터페이스만을 정의하지 실제 구현체를 만들진 않는데, 바로 위와 같은 구조를 띤다.
1. 로딩 시점에 @Mapper가 있는 인터페이스 스캔
2. 해당 인터페이스 발견 시 동적 프록시 기술로 구현체를 생성
3. 생성된 구현체를 빈으로 등록
동적 SQL
1) if
조건에 따라 값을 추가할지 말지 판단한다.
<if test = 조건>
and title like #{title}
</if>
2) 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>
자바의 swich구문과 비슷하다.
3) <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를 지운다.
4) 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>
컬렉션을 반복 처리할 때 사용한다.
where in (1,2,3,4,5,6) 와 같은 문장을 쉽게 완성할 수 있다.
파라미터로 List 를 전달하면 된다.
동적 쿼리에 대한 자세한 내용은 다음을 참고하자.
https://mybatis.org/mybatis-3/ko/dynamic-sql.html
자주 사용하는 기능
1) XML에 쿼리를 작성하는 것 대신 @Select... 등 어노태이션을 사용해 SQL을 작성할 수 있다.
다만 XML을 사용하여 SQL을 분리하는 목적에는 맞지않아 아주 간단한 경우 아니면 사용하지 않는다.
애노테이션으로 SQL 작성에 대한 더 자세한 내용은 다음을 참고하자.
https://mybatis.org/mybatis-3/ko/java-api.html
2) 파라미터의 값을 문자열 그대로 사용하고 싶은경우
#{} 문법 대신 ${}을 통해 문자열 그대로를 사용할 수 있다.
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String
하지만 이 경우에 SQL 인젝션 공격에 취약해진다.
3) Result Maps
결과를 매핑할 때 테이블은 user_id이지만 객체는 id이다.
이 경우 칼럼명과 객체의 프로퍼티 명이 다르다. 그러면 다음과 같이 별칭( as )을 사용하면 된다.
<select id="selectUsers" resultType="User">
select
user_id as "id",
user_name as "userName",
hashed_password as "hashedPassword"
from some_table
where id = #{id}
</select>
이 경우 별칭을 사용하지 않고 해결할 수 있는데, 그 방법이 resultMap을 통한 방법이다.
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="username"/>
<result property="password" column="password"/>
</resultMap>
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
복잡한 결과 매핑 MyBatis도 매우 복잡한 결과에 객체 연관관계를 고려해서 데이터를 조회하는 것이 가능하다.
이때는 <association>, <collection> 등을 사용한다.
이 부분은 성능과 실효성에서 측면에서 많은 고민이 필요하다.
JPA는 객체와 관계형 데이터베이스를 ORM 개념으로 매핑하기 때문에 이런 부분이 자연스럽지만,
MyBatis에서는 들어가는 공수도 많고, 성능을 최적화하기도 어렵다.
따라서 해당 기능을 사용할 때는 신중하게 사용해야 한다. 해당 기능에 대한 자세한 내용은 공식 매뉴얼을 참고하자.
결과 매핑에 대한 자세한 내용은 다음을 참고하자.
https://mybatis.org/mybatis-3/ko/sqlmap-xml.html#Result_Maps
정리
Mapper 구현체 덕분에 기존 번잡한 설정 대신 스프링에 편리하게 통합해서 사용할 수 있다.
이는 스프링 예외 추상화도 함께 적용된다.
MyBatis 스프링 연동 모듈이 많은 부분을 자동으로 설정해주는데, 커넥션, 트랜잭션 관련 기능도 함께 연동되고 동기화해준다.
자동 설정 부분은 MybatisAutoConfiguration 클래스 참고하자.
MyBatis 공식 매뉴얼:https://mybatis.org/mybatis-3/ko/index.html
MyBatis 스프링 공식 매뉴얼:https://mybatis.org/spring/ko/index.html
댓글