스프링 DB 2편 - 데이터 접근 활용 기술 (4)
https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-db-2
데이터 접근 기술 - MyBatis
MyBatis는 JdbcTemplate 보다 더 많은 기능을 제공하는 SQL Mapper 이다.
JdbcTemplate과 비교해서 MyBatis의 장점은 SQL을 XML에서 편리하게 작성할 수 있고 또 동적 쿼리를 매우 편리하게 작성할 수 있다.
JdbcTemaplate은 스프링에 내장된 기능이고, 별도의 설정없이 사용할 수 있다는 장점이 있다.
반면에 MyBatis는 약간의 설정이 필요하다.
MyBatis 설정
mybatis-spring-boot-starter 라이브러리를 사용하면 MyBatis를 스프링과 통합하고, 설정도 아주 간단히 할 수 있다.
build.gradle에 의존관계를 추가한다.
//MyBatis 추가
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'
다음과 같은 라이브러리가 추가 된다.
- mybatis-spring-boot-starter : MyBatis를 스프링 부트에서 편리하게 사용할 수 있게 시작하는 라이브러리
- mybatis-spring-boot-autoconfigure : MyBatis와 스프링 부트 설정 라이브러리
- mybatis-spring : MyBatis와 스프링을 연동하는 라이브러리
- mybatis : MyBatis 라이브러리
설정
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
- mybatis.type-aliases-package : 마이바티스에서 타입 정보를 사용할 때는 패키지 이름을 적어주어야 하는데, 여기에 명시하면 패키지 이름을 생략할 수 있다. 여러 위치를 지정하려면 , ; 로 구분하면 된다.
- mybatis.configuration.map-underscore-to-camel-case : 언더바를 카멜로 자동 변경해주는 기능을 활성화 한다.
- logging.level.hello.itemservice.repository.mybatis=trace : MyBatis에서 실행되는 쿼리 로그를 확인할 수 있다.
MyBatis 적용
@Mapper
public interface ItemMapper {
void save(Item item);
void update(@Param("id") Long id, @Param("updateParam")ItemUpdateDto updateDto) ;
List<Item> findAll(ItemSearchCond itemSearch);
Optional<Item> findById(Long id);
}
마이바티스 매핑 XML을 호출해주는 매퍼 인터페이스이다.
인터페이스에는 @Mapper를 애노테이션을 붙여주어야한다. 그래야 MyBatis에서 인식할 수 있다.
XML 매핑 파일은 자바 코드가 아니기 때문에 src/main/resources 하위에 만들되, 패키지 위치는 맞추어 주어야 한다.
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>
namespace : 앞서 만든 매퍼 인터페이스를 지정하면 된다.
XML파일을 원하는 위치에 두고싶으면 application.properties 에 다음과 같이 설정하면 된다.
mybatis.mapper-locations=classpath:mapper/**/*.xml
이렇게 하면 resources/mapper 를 포함한 하위 폴더에 있는 XML을 XML매핑 파일로 인식한다.
insert 문
void save(Item item);
<insert id="save" useGeneratedKeys="true" keyProperty="id">
insert into item (item_name, price, quantity)
values (#{itemName}, #{price}, #{quantity})
</insert>
useGeneratedKeys는 데이터베이스가 키를 생성해주는 IDENTITY 전략일 때 사용한다.
keyProperty는 생성되는 키의 속성 이름을 지정한다.
update 문
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>
파라미턱 2개 이상이면 @Param 으로 이름을 지정해서 파라미터를 구분해야 한다.
select 문
Optional<Item> findById(Long id);
<select id="findById" resultType="Item">
select id, item_name, price, quantity
from item
where id = #{id}
</select>
resultType은 반환 타입을 명시한다. mybatis.type-aliasespackage=hello.itemservice.domain 을 지정한 덕분에 패키지명을 간단하게 쓸 수 있다.
XML 특수문자
XML 에서는 데이터 영역에 < , > 같은 특수 문자를 사용할 수 없기 때문에 CDATA 구문 문법을 사용해야 한다.
<if test="maxPrice != null">
<![CDATA[
and price <= #{maxPrice}
]]>
</if>
매퍼 인터페이스의 구현체
위의 내용을 보면 ItemMapper 매퍼 인터페이스의 구현체가 없는데 동작한다.
그 이유는 MyBatis 스프링 연동 모듈에서 자동으로 처리해주기 때문이다.
1. 애플리케이션 로딩 시점에 MyBatis 스프링 연동 모듈은 @Mapper 가 붙어있는 인터페이스를 조사한다.
2. 해당 인터페이스가 발견되면 동적 프록시 기술을 사용해서 ItemMapper 인터페이스의 구현체를 만든다.
3. 생성된 구현체를 스프링 빈으로 등록한다.
MyBatis - 동적 쿼리
if
<if test="title != null">
AND title like #{title}
</if>
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>
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>
컬렉션을 반복 처리할 때 사용한다. where in (1,2,3,4,5,6)와 같은 문장을 쉽게 완성할 수 있다.
기타 기능
애노테이션으로 SQL 작성
@Select("select id, item_name, price, quantity from item where id=#{id}")
Optional<Item> findById(Long id);
@Insert , @Update , @Delete , @Select 기능이 제공된다.
이 경우에는 XML 파일에 Select id=findById 는 제거해야한다.
동적 SQL이 해결되지 않으므로 간단한 경우에만 사용한다.
문자열 대체
#{} 문법은 ?를 넣고 파라미터를 바인딩하는 PreparedStatement를 사용한다.
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String
value);
재사용 가능한 SQL조각
<sql id="userColumns"> ${alias}.id,${alias}.username,${alias}.password </sql>
<select id="selectUsers" resultType="map">
select
<include refid="userColumns"><property name="alias" value="t1"/></include>,
<include refid="userColumns"><property name="alias" value="t2"/></include>
from some_table t1
cross join some_table t2
</select>
<include>를 통해서 <sql> 조각을 찾아서 사용할 수 있다.
Result Maps
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="user_name"/>
<result property="password" column="hashed_password"/>
</resultMap>
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
별칭을 사용하지 않고도 resultMap을 선언해서 사용할 수 있다.