MyBatis
1. 기본설정
build.gradle에 마이바티스 implementation을 해주면서 시작하자.
//MyBatis 추가
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'
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
xml 작성시 리턴 타입에서 패키지 명을 뺄 수 있는 첫 번째 옵션
DB의 스테이크 케이스를 자동으로 카멜 케이스로 변경해주는 두 번째 옵션
로깅 정보에 대한 세번째 옵션이다.
마이바티스에서 쿼리를 작성하기 위해서는 @Mapper애노테이션이 붙은 인터페이스를 설정해줘야 한다. 그래야 MyBatis에서 인식하고 xml의 SQL을 실행해 결과를 돌려준다.
@Mapper
public interface ItemMapper {
void save(Item item);
void update(@Param("id") Long id, @Param("updateParam")ItemUpdateDto updateDto);
Optional<Item> findById(Long id);
List<Item> findAll(ItemSearchCond itemSearch);
}
xml을 작성해보자. 왜 ORM이 생겼는지 알것같다.
<?xml version="1.0" encoding="UTF-8"?>
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">
select id, item_name, price, quantity
from item
<where>
<if test="itemName != null and itemName != ''">
and item_name like concat('%', #{itemName}, '%')
</if>
<if>
and price $lt;= #{maxPrice}
</if>
</where>
</select>
</mapper>
1. insert - save
- id에는 인터페이스에 설정한 메서드 이름을 지정한다.
- 파라미터는 #{ } 문법을 이용하는데 매퍼에서 넘긴 객체의 프로퍼티 이름을 지정해준다. 만약 파라미터가 하나라면 필드명을 바로 적어줘도 된다.
- #{ } 문법은 preparedStatement를 사용해서 ?를 치환해준다.
- useGeneratedKeys는 데이터베이스 키가 IDENTITY 전략일 때 사용한다. keyProperty는 키의 속성 이름을 지정한다. Insert가 끝나면 item 객체의 id 속성에 생성된 값이 입력된다.
2. update
- 파라미터가 Long id, ItemUpdateDto updateParam으로 2개다. 위의 인터페이스를 보면 파라미터가 2개라 @Param으로 이름을 지정해줘야 한다.
3. select - findById
- resultType은 반환 타입을 명시하면 되는데 여기서는 Item 객체에 매핑한다.
- 앞서 properties에서 aliases-package를 지정해줬기 때문에 패키지 명을 다 적지 않아도 되며 BeanPropertyRowMapper기능처럼 SQL 결과를 객체로 변환해준다.
4. select - findAll
- <if>, <where>등을 이용한 동적쿼리.. 부등호는 xml 문법과 겹치기 때문에 <를 사용해줘야 한다.
2. 설정과 실행
실행 자체는 보기에 꽤나 간단하다.
@Repository
@RequiredArgsConstructor
public class MyBatisItemRepository implements ItemRepository {
private final ItemMapper itemMapper;
@Override
public Item save(Item item) {
itemMapper.save(item);
return item;
}
@Override
public void update(Long itemId, ItemUpdateDto updateParam) {
itemMapper.update(itemId, updateParam);
}
@Override
public Optional<Item> findById(Long id) {
return itemMapper.findById(id);
}
@Override
public List<Item> findAll(ItemSearchCond cond) {
return itemMapper.findAll(cond);
}
}
ItemMapper 인터페이스의 구현체는 없는데 위의 코드는 구현체를 주입받았다. 그 이유가 뭘까.
- 마이바티스 모듈이 @Mapper 어노테이션을 탐색해서 프록시 객체를 생성, 빈으로 등록해주는 과정을 대신해준다.
- 매퍼 구현체는 RuntimeException을 상속받은 DataAccessException 규격에 맞게 예외 반환도 처리해준다.
- 커넥션, 트랜잭션도 마이바티스가 연동하고 동기화해준다.
3. 동적 쿼리
동적 쿼리를 위한 기능은 다음과 같다.
- if
- choose(when, orherwise)
- trim(where, set)
- foreach
if
조건에 따라 추가할지 판단한다. 내부 문법은 OGNL을 사용한다.
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>
</where>
</select>
<where>은 문장이 없으면 where을 추가하지 않는다. 문장이 있다면 where을 추가한다. 물론 1=1 조건을 넣어줘도 같긴 하다.
trim은 where과 같은 기능을 수행하는 문이다.
<trim prefix="WHERE" prefixOverrides="AND |OR ">
...
</trim>
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>
컬렉션을 반복 처리할 때 사용한다.
4. 기타 기능
- 애노테이션으로 SQL 작성
XML 대신 애노테이션으로 SQL을 작성할 수 있는 기능이 있다.
@Select("select id, item_name, price, quantity from item where id=#{id}")
Optional<Item> findById(Long id);
@Insert, @Update, @Delete, @Select 기능이 제고오디는데
XML에서는 해당 쿼리를 제거해야 하며 동적 SQL이 해결되지 않고 일괄적 관리를 위해 가급적 XML을 사용하는 것이 좋겠다.
- 문자열 대체
#{ } 문법은 ?을 넣고 파라미터를 바인딩하는 PreparedStatement를 사용하는데 떄로는 문자 그대로를 처리하고 싶을 때도 있다. 이 때는 ${ }를 사용하면 된다.
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);
하지만 ${ }는 SQL 인젝션을 당할 수 있기 때문에 사용하면 안된다.
- 재사용 가능한 SQL 조각
<sql>을 사용하면 SQL 코드를 재사용 할 수 있다.
<sql id="sometable">
${prefix}Table
</sql>
<sql id="someinclude">
from
<include refid="${include_target}"/>
</sql>
<select id="select" resultType="map">
select
field1, field2, field3
<include refid="someinclude">
<property name="prefix" value="Some"/>
<property name="include_target" value="sometable"/>
</include>
</select>
Result Maps
결과 매핑시 컬럼명과 프로퍼티 명이 다르면 as를 사용하면 되는데 별칭을 사용하지 않고 resultMap을 사용해도 된다.
<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>
'spring' 카테고리의 다른 글
스프링 DB 2편 - 3 (0) | 2023.01.05 |
---|---|
스프링 DB 2편 - 1 (0) | 2022.12.21 |
스프링 DB 1편 - 3 (0) | 2022.12.12 |
스프링 DB 1편 - 2 (0) | 2022.11.30 |
스프링 DB 1편 - 1 (0) | 2022.11.02 |