MyBatis는 JdbcTemplate보다 더 많은 기능을 제공하는 SQL Mapper이고, JdbcTemplate가 제공하는 대부분의 기능을 제공한다. 하지만 MyBatis가 매력적인 점은 SQL을 XML에 편리하게 작성할 수 있고, 동적 쿼리를 매우 편리하게 작성할 수 있다는 점이다.
MyBatis를 사용하기 위해서
//build.gradle에 추가
//MyBatis 추가
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.1'
//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 = hello.itemservice.domain
- MyBatis에서 타입 정보를 사용할 때 패키지 명도 적어주어야 하는데, 이렇게 명시하면 패키지 이름을 생략할 수 있다.
- 지정한 패키지와 그 하위 패키지가 자동으로 인식된다.
- mybatis.configuration.map-underscore-to-camel-case = true
- 언더바를 카멜로 자동 변경해주는 역할을 한다.
- 관계형 DB에서 사용하는 item_name을 자바 객체에서 사용하는 카멜 케이스인 itemName으로 바꾸어준다.
- 이 옵션을 켜면, 컬럼 이름과 객체 이름이 아예 다른 경우에만 SQL의 as 기능을 사용하면 된다.
- logging.level.hello.itemservice.repository.mybatis = trace
- MyBatis에서 실행되는 쿼리 로그를 확인할 수 있다.
MyBatis 적용1 - 기본
xml에 저장한다는 점을 빼면 JDBC의 반복을 줄여준다는 점에서 JDBCTemplate과 유사하다.
@Mapper
public interface ItemMapper {
void save(Item item);
//파라미터가 두개가 넘어가는 경우는 @Param을 넣어줘야함
void update(@Param("id") Long id, @Param("updateParam") ItemUpdateDto updateDto);
List<Item> findAll(ItemSearchCond itemSearchCond);
Optional<Item> findById(Long id);
}
이렇게 MyBatis 매핑 파일을 만들어주어야 한다.
- MyBatis 매핑 XML을 호출해주는 매퍼 인터페이스이다.
- @Mapper를 붙여주어야 MyBatis에서 인식할 수 있다.
- 이 인터페이스의 메서드를 호출하면, xml의 해당 SQL을 실행하고 결과를 돌려준다.
같은 위치에 실행할 SQL이 있는 XML 매핑 파일을 만들어주면 된다. 자바 파일이 아니라서 src/main/resources의 하위에 만들되, 패키지 위치는 맞춰주어야 한다.
ItemMapper.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>
- namespace는 앞에서 만든 Mapper의 위치를 지정한다.
- save()
- <insert> 사용
- id에는 매퍼 인터페이스에 설정한 메서드 이름인 save를 넣는다.
- 파라미터는 #{} 문법을 사용한다. JdbcTemplate의 ?를 치환한다 생각하면 된다.
- useGeneratedKeys는 데이터베이스가 키를 생성해주는 것을 사용할 때 쓰면되고, keyProperty는 생성되는 키의 속성 이름을 지정한다. 그러면 insert가 끝나면 item 객체의 id속성에 생성된 값이 입력된다.
- update()
- <update> 사용
- 인터페이스에서는 파라미터가 2개 이상이면 @Param으로 지정해서 파라미터를 구분한다.
- xml에서는 updateParam의 itemName, price, quantity를 updateParam.itemName 처럼 사용할 수 있다.
- findById()
- <select> 사용
- resultType은 반환 타임을 명시한다.
- 앞에서 application.properties에 'mybatis.type-aliasespackage=hello.itemservice.domain'을 지정해두어서 모든 패키지명을 다 쓰지 않아도 Item만 적어서 사용할 수 있다.
- select의 결과를 객체로 바로 변환해준다.
- 'mybatis.configuration.map-underscore-to-camel-case=true'을 지정해두어서 item_name을 itemName으로 자동으로 처리해준다.
- 자바 코드에서 변환 객체가 하나면 Item, Optional<Item>처럼 사용하면되고, 하나 이상이면 컬렉션을 사용하면된다. 주로 List를 쓴다.
- findAll()
- <select> 사용
- <where>, <if> 동적 쿼리 문법을 사용한다.
- <if>문이 모두 실패하면 SQL은 where절을 만들지 않는다.
- <if>문이 하나라도 성공하면 and를 where로 변환해준다.
- XML 특수 문자
- < : <
- > : &rt;
- & : &
MyBatis 적용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);
}
}
ItemRepository는 ItemMapper에게 기능을 위임하는 식으로 구현하면 된다.
@Configuration
@RequiredArgsConstructor
public class MyBatisConfig {
private final ItemMapper itemMapper;
@Bean
public ItemService itemService() {
return new ItemServiceV1(itemRepository());
}
@Bean
public ItemRepository itemRepository() {
return new MyBatisItemRepository(itemMapper);
}
}
이렇게 하면 원래 테스트도 잘 작동하고 기존 코드도 localhost에서 잘 돌아가는 것을 확인할 수 있다.
- 매퍼 구현체 덕분에 마이바티스를 스프링에 편리하게 통합해서 사용가능하다.
- 매퍼 구현체를 사용하면 스프링 예외 추상화도 함께 적용된다.
- 마이 바티스 스프링 연동 모듈이 데이터베이스 커넥션, 트랜잭션과 관련된 기능 등을 자동으로 설정해준다.
MyBatis 기능 정리
동적 SQL
제공되는 기
- if
- 해당 조건에 따라 값을 추가할지 말지
- 내부의 문법은 OGNL 사용
- choose (when, otherwise)
- 자바의 switch 구문과 유사
- trim (where, set)
- and가 먼저 시작되면 and를 where로 바꿈
- foreach
- 컬렉션을 반복처리할 때 사용
- 파라미터로 List를 전달하면 된다.
//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>
// 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>
기타 기능
- 애노테이션으로 SQL 작성
- @Insert, @Update, @Delete, @Select 기능이 제공
- 아래의 경우에는 XML에 있는 findById에 대한 것은 제거해야한다.
- 동적 SQL이 해결되지 않아서 간단한 경우에만 사용한다.
- 하지만 잘 사용하지 않는다.
@Select("select id, item_name, price, quantity from item where id=#{id}")
Optional<Item> findById(Long id);
- 문자열 대체
- #{} 기능은 ?를 넣고 파라미터를 바인딩하는 PreparedStatement 방식을 사용한다.
- 파라미터 바인딩이 아니라 문자 그대로를 처리하고 싶은 경우에는 ${}를 사용하면 된다.
- SQL 인젝션 공격을 당할 수 있어서 가급적 사용하면 안된다.
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);
- 재사용 가능한 SQL 조각
- <sql>을 사용하면 SQL코드를 재사용할 수 있다.
- 별도로 SQL 문을 정의해서 <include>를 통해서 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>
...
- ResultMap
- 컬럼명과 객체 프로퍼티 명이 다를 때 as를 사용할 수 있다.
- 하지만 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>
728x90
'Spring' 카테고리의 다른 글
[Spring DB2] 데이터 접근 기술 - 스프링 데이터 JPA (0) | 2024.03.21 |
---|---|
[Spring DB2] 데이터 접근 기술 - JPA (0) | 2024.03.19 |
[Spring DB2] 데이터 접근 기술 - 테스트 (3) | 2024.03.17 |
[Spring DB2] 데이터 접근 기술 - 스프링 JdbcTemplate-2 (1) | 2024.03.14 |
[Spring DB2] 데이터 접근 기술 - 스프링 JdbcTemplate-1 (1) | 2024.03.14 |