[Spring DB] Mybatis
JdbcTemplate 보다 더 많은 기능을 제공하는 SQL Mapper
- 장점
- SQL을 xml파일에 편리하게 작성할 수 있다.
- 동적 쿼리를 작성하기 쉽다.
- 단점
- 따로 설정이 필요하다.
공식 사이트 메뉴얼 ( 번역 잘돼있음)
mybatis
설정
- build.gradle에 mybatis 라이브러리 추가
- application.properties 에 다음과 같이 설정추가 **(Main, Test 두 곳 다)
- #MyBatis
- #마이바티스 xml에서 사용할 객체 경로(경로 생략을 위함)
- #하위 패키지와 하위 패키지 자동으로 인식
- mybatis.type-aliases-package=hello.itemservice.domain
- #스네이크 표기법을 카멜표기법으로 치환해줌
- mybatis.configuration.map-underscore-to-camel-case=true
- #로그 단계 설정 (hello.itemservice.repository.mybatis 가 mybatis를 만들 패키지)
- logging.level.hello.itemservice.repository.mybatis=trace
사용 설정
인터페이스 생성 (인터페이스 메서드 호출하면 xml 실행) - 구현체 자동으로 만들어줌
@Mapper
public interface ItemMapper {
void save(Item item);
//1개인 경우는 생략가능 2개 이상 @Param 지정
void update(@Param("id") long id, @Param("updateParam") ItemUpdateDto updateItem);
List<Item> findAll(ItemSearchCondition itemSearch);
Optional<Item> findById(long id);
}
같은 위치에 실행할 SQL이 있는 XML 매핑파일 생성
*참고* 자바코드가 아니기 때문에 resources에 만들되 패키지위치들 동일시켜야한다.
ex) 인터페이스 패키지 :
java -> package hello.itemservice.repository.mybatis;
public interface ItemMapper
xml 디렉토리 :
resources/hello/itemservice/repository/mybatis 에 ItemMapper.xml
참고 - XML 파일 경로 수정하기
XML 파일을 원하는 위치에 두고 싶으면 application.properties 에 다음과 같이 설정하면 된다.
- mybatis.mapper-locations=classpath:mapper/**/*.xml
- 이렇게 하면 resources/mapper 를 포함한 그 하위 폴더에 있는 XML을 XML 매핑 파일로 인식한다.
- 이 경우 파일 이름은 자유롭게 설정해도 된다.
- 참고로 테스트의 application.properties 파일도 함께 수정해야 테스트를 실행할 때 인식할 수 있다
기본 문법
<?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"> <!-- 인터페이스 위치 -->
</mapper>
기본 문법
Insert SQL
<insert id="save" useGeneratedKeys="true" keyProperty="id">
INSERT INTO item (item_name, price, quantity)
VALUES (#{itemName},#{price},#{quantity})
</insert>
- <insert> 사용
- id는 매퍼 인터페이스에 설정한 메서드 이름 지정
- 파라미터는 #{ } 문법 사용하고, 안에는매퍼에서 넘긴 객체의 프로퍼티 이름을 적어주면 된다.
- #{ } 문법을 사용하면 PreparedStatment를 사용한다. (JDBC의 ?를 치환 개념)
- useGeneratedKeys는 데이터베이스가 키를 생성해주는 전략일때 사용
- keyProperty는 키 속성을 지정하고
- insert가 끝나면 item객체의 id속성에 생성된 값이 입력된다.
Update SQL
<!--
Parameter 가 1개인 경우 변수명만 적어주면 자바 프로퍼티를 사용해 값을 꺼내오지만 ex) save
Parameter 가 2개 이상 넘어올 경우 인터페이스에서 보낸
Parameter 의 이름을 지정해야 한다. ex)update
-->
<update id="update">
UPDATE item
SET item_name=#{updateParam.itemName},
price=#{updateParam.price},
quantity=#{updateParam.quantity},
id=#{id}
WHERE id = #{id}
</update>
- <update> 사용
- 여기서는 파라미터가 Long id , ItemUpdateDto updateParam 으로 2개이다.
- 파라미터가 1개만 있으면 @Param 을 지정하지 않아도 되지만,
파라미터가 2개 이상이면 @Param 으로 이름을 지정해서 파라미터를 구분 해야 한다.
Select SQL - 1개
<!-- resultType = 설정 파일에 클래스 객체 패키지를 지정해줬기 때문에 Path 생략가능-->
<select id="findById" resultType="Item">
SELECT id, item_name, price, quantity
FROM item
WHERE id = #{id}
</select>
- <select> 사용
- resultType은 반환 타입 명시
- JdbcTemplate의 BeanPropertyRowMapper 처럼 SELECT SQL의 결과를 편리하게 객체로 바로 변환해준다
- 자바 코드에서 반환 객체가 하나이면 Item , Optional 과 같이 사용하면 되고, 반환 객체가 하나 이상 이면 컬렉션을 사용하면 된다. 주로 List 를 사용한다.
Select SQL - 여러개 + 동적 쿼리
<select id="findAll" resultType="Item">
SELECT id, item_name, price, quantity
from Item
<where>
<if test="itemName != null and itemName != ''">
and item_name like '%' || #{itemName} || '%'
</if> <!-- h2, mysql = cocat('%',#{itemName},'%')-->
<if test="maxPrice != null">
and price $lt;= #{maxPrice}
</if> <!-- <는 xml 태그여서 $lt사용 -->
</where>
</select>
- <where>, <if> 같은 동적 쿼리 문법을 지원
- <if>는 해당 조건이 만족하면 구문을 추가해준다.
- <where>는 적절하게 where문장을 만들어준다.
- 위 경우 <if>가 모두 실패하면 where를 만들지 않고
- 하나라도 성공하면 처음 나타나는 and를 where로 변환해줌
- 특수문자 < 사용불가
- < : <
- > : >
- & : &
- " : "
<![CDATA[ ... ]]>
< , > , & , " 를 쿼리문 안에 포함시켜야 할다면
<![CDATA[ ... ]]> 구문 문법을 사용하면 XML TAG가 단순 문자로 인식된다.
위의 방식으로 치환하거나 Char Data를 상황에 맞게 사용하자
또한, 이 구문 안에서 <if> <where>등이 적용되지 않는다.
예시
<if test="maxPrice != null">
<![CDATA[
and price <= #{maxPrice}
]]>
</if>
Repository 생성
@Repository
@RequiredArgsConstructor
public class MyBatisRepository 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(ItemSearchCondition cond) {
return itemMapper.findAll(cond);
}
}
엄청나게 간결해진 모습을 볼 수 있다. (호출, 매개변수주입)
작동 방식
인터페이스를 주입받아서 사용하는데 어떻게 사용되는 것일까?
- 애플리케이션 로딩시점에 Mybatis 스프링 연동 모듈은 @Mapper가 붙어있는 인터페이스를 조사한다.
- 해당 인터페이스가 발견되면 동적 프록시 기술을 사용해서 구현체를 만든다.
- 생성된 구현체를 스프링 빈으로 등록한다.
- Repository에서 로그를 출력해보면 class com.sun.proxy.$Proxy66을 확인할 수 있다.
- 매퍼 구현체
- 마이바티스 스프링 연동 모듈이 만들어주는 구현체 덕분에 인터페이스 만으로 편리하게 XML의 데이터를 찾아서 호출할 수 있다.
- 매퍼 구현체는 예외 변환까지 처리해준다. MyBatis에서 발생한 예외를 스프링 예외 추상화인
DataAccessException에 맞게 변환해서 반환해준다. JdbcTemplate이 제공하는 예외 변환 기능을 여기서도 제공한다.
- 정리
- 매퍼 구현체 덕분에 마이바티스를 스프링에 편리하게 통합해서 사용할 수 있다.
- 매퍼 구현체를 사용하면 스프링 예외 추상화도 함께 적용된다.
- 마이바티스 스프링 연동 모듈이 많은 부분을 자동으로 설정해주는데, 데이터베이스 커넥션,
트랜잭션과 관련된 기능도 마이바티스와 함께 연동하고, 동기화해준다.
동적 쿼리 기능
- 마이바티스가 동적 쿼리를 위해 제공하는 기능
- if
- choose(when, otherwies)
- trim (where, set)
- foreach
- 내부 문법은 OGNL을 사용한다.
- 예제는 https://mybatis.org/mybatis-3/ko/dynamic-sql.html
기타 기능
- 애너테이션으로 SQL 작성
- xml이 아닌 Mapper 인터페이스 메서드 레벨에 작성할 수 있는
@Insert, @Update, @Delete, @Select 기능이 제공된다. - 이 경우 xml에 정의한 쿼리를 제거해야한다.
- 동적 SQL 사용이 불가하다
- xml이 아닌 Mapper 인터페이스 메서드 레벨에 작성할 수 있는
- 문자열 대체 - ${ }
- 문자열 값 그대로 처리할때
- 보통 컬럼명과 테이블 명을 동적으로 처리하는 기능을 가지고 있다.
- ※주의※ SQL injection 공격을 당할 위험이 있다.
- 재사용 가능한 SQL 조각
- <sql> 을 사용하면 SQL 코드를 재사용 할 수 있다.
- ex) <sql id="userColumns"> ${alias}.id, ${alias}.username </sql>
- <select>
select
<include refid="usercolums"><property name="alias" value ="t1" /></include>
<include refid="userColumns"><property name="alias" value="t2"/></include>
form t1 </select>
<include>를 통해서 <sql> 조각을 찾아서 사용할 수 있다.
- <sql> 을 사용하면 SQL 코드를 재사용 할 수 있다.
- Result Maps
- 결과를 매핑할때 카멜, 스네이크의 차이가 아닌 이름이 완전 다르면 as를 사용해야한다.
이때 as가 아닌 resultMap을 이용해서 처리할 수 있다.
- 결과를 매핑할때 카멜, 스네이크의 차이가 아닌 이름이 완전 다르면 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>
복잡한 결과 매핑
<assciation>, <collection> 등을 사용할 수있다. (이때는 JPA가 자연스러움)
결과 매핑에 대한 자세한 내용은 다음을 참고하자.
https://mybatis.org/mybatis-3/ko/sqlmap-xml.html#Result_Maps
출처: https://tan-sog.tistory.com/90