지금까지 토이 프로젝트에서는 JPA와 QueryDSL을 활용하여 개발했지만, 이번에 입사하게 된 첫 회사에서는 MyBatis를 주로 사용하고 있었다.
처음에는 JPA와 QueryDSL을 사용하지 않아 조금 아쉬웠지만, "로마에서는 로마법을 따르라"라는 말과 같이 새로운 환경에 맞춰 공부하고 적응하는 것이 필요하다고 생각했다.
따라서 기본적인 MyBatis 사용법과 동적 쿼리 사용법을 기록하고자 한다.
1. MyBatis
- JdbcTemplate 보다 더 많은 기능 제공
- SQL을 XML에 편리하게 작성하고 동적 쿼리를 매우 편하게 작성 가능
- 약간의 설정이 필요, Spring Boot를 사용하면 편하게 설정 가능
2. MyBatis 사용법
1) 마이바티스 매핑 XML을 호출해주는 Mapper Interface 생성
@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);
}
- 구현체는 자동으로 만들어준다.
- 애플리케이션 로딩 시점에 MyBatis 스프링 연동 모듈은 @Mapper가 붙어있는 인터페이스를 조회한 후, 동적 프록시 기술을 사용하여 ItemMapper 인터페이스의 구현체를 생성하여 스프링 빈에 등록한다.
- 매퍼 구현체는 예외 변환까지 처리해준다, MyBatis에서 발생한 예외를 스프링 예외 추상화인 DatatAccessException에 맞게 변환해서 반환한다.
2) src/main/resourecs 하위에 패키지 경로를 맞추어 xml 파일 생성
- 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>
NOTE !
(1) Sql에 맞게 <select>, <insert>, <update> 사용
(2) id : 매퍼 인터페이스에 설정한 메서드 이름을 지정
(3) #{} : 매퍼에서 넘긴 객체의 프로퍼티 이름을 지정하여 사용
- ${} 는 SQL 인젝션 공격을 당할 수 있기 때문에 가급적 사용하면 안된다.
(4) useGeneratedKeys : 데이터베이스가 키를 생성해주는 IDENETITY 전략 사용
(5) 파라미터 값이 1개라면 @Param을 사용하지 않아도 되지만, 2개 이상이라면 @Param을 지정해서 파라미터를 구분
(6) resultType : 반환 타입 명시 (mybatis.type-aliasespackage를 명시한 경우, 패키지 명을 모두 적지 않아도 된다.
- 반환 객체가 하나면 Optional을 사용하고, 둘 이상이라면 List를 사용한다.
(7) XML에서는 <, > 같은 특수 문자를 사용할 수 없어, < > 로 대체한다.
- 혹은 CDATA 구문 문법을 사용하여 특수 문자를 사용할 수 있다.
<![CDATA[
and price <= #{maxPrice}
]]>
3. MyBatis 동적 쿼리
1) if : 해당 조건에 따라 조건절을 추가할지 말지가 결정된다.
<select id="findActiveBlogWithTitleLike"
resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
2) choose, when, otherwise : 자바의 switch 문과 동일
<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>
3) trime (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>
위 코드에서 (1)모든 if 가 만족하지 않거나, (2)두번째 if 절만 만족할 때 완성되는 SQL은 부적절하다.
(1) Select * From BLOG Where
(2) Select * From BLOG Where AND title like 'someTitle'
아래와 같이 <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>
4) foreach : 반복처리가 가능하며, 종종 IN 조건을 사용한다.
<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>
4. MyBatis 설정
- builde.gradle
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'
Spring이 MyBatis 의존성은 공식적으로 관리하지 않기 때문에 버전을 명시해야 한다.
- application.properties
mybatis.type-aliases-package=hello.itemservice.domain
mybatis.configuration.map-underscore-to-camel-case=true
logging.level.hello.itemservice.repository.mybatis=trace
mybatis.type-aliases-package
- 타입 정보를 사용할 때는 패키지 이름을 적어주어야 하는데, 여기에 명시하면 패키지 이름을 생략할 수 있다.
- 지정한 패키지와 그 하위 패키지도 자동으로 인식한다.
mybatis.configuration.map-underscore-to-camel-case
- 언더바를 카멜로 자동 변경해주는 기능을 활성화
참고 레퍼런스
https://mybatis.org/mybatis-3/ko/dynamic-sql.html
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2/dashboard
'개발 > Spring' 카테고리의 다른 글
[Spring] 커스텀 Annotation 대상 AOP 적용하기 (0) | 2024.04.13 |
---|---|
[MSA] 서킷브레이커 적용 (Resilience4j) (4) | 2024.01.23 |
[Spring] @Cacheable를 통해 성능 개선하기 (캐시) (2) | 2023.09.04 |
[Spring] @Async를 사용하여 비동기 처리 (+ 쓰기, 읽기 서비스 분리) (0) | 2023.08.12 |
[Spring] JPA Fetch Join 사용시, MultipleBagFetchException 발생 (3) | 2023.08.03 |