JdbcTemplate - SQL 여러줄
String sql = "update item " +
"set item_name=:itemName, price=:price, quantity=:quantity " +
"where id=:id";
MyBatis - SQL 여러줄
<update id="update">
update item
set item_name=#{itemName},
price=#{price},
quantity=#{quantity}
where id = #{id}
</update>
→ XML에 작성하기 때문에 라인이 길어져도 문자 더하기에 대한 불편함이 없다.
JdbcTemplate - 동적 쿼리
String sql = "select id, item_name, price, quantity from item";
//동적 쿼리
if (StringUtils.hasText(itemName) || maxPrice != null) {
sql += " where";
}
boolean andFlag = false;
if (StringUtils.hasText(itemName)) {
sql += " item_name like concat('%',:itemName,'%')";
andFlag = true;
}
if (maxPrice != null) {
if (andFlag) {
sql += " and";
}
sql += " price <= :maxPrice";
}
log.info("sql={}", sql);
return template.query(sql, param, itemRowMapper());
MyBatis - 동적 쿼리
<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>
설정의 장단점
- JdbcTemplate : 별도의 설정없이 사용 가능 (스프링에 내장된 기능)
- MyBatis는 약간의 설정이 필요
※ 공식 사이트 : https://mybatis.org/mybatis-3/ko/index.html
MyBatis 설정
build.gradle
//MyBatis 추가
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'
#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
▶ JdbcTemplate의 BeanPropertyRowMapper 에서 처럼 언더바를 카멜로 자동 변경해주는 기능을 활성화
logging.level.hello.itemservice.repository.mybatis=trace
▶ MyBatis에서 실행되는 쿼리 로그를 확인할 수 있다.
기본
ItemMapper
@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);
}
- 이 인터페이스에는 @Mapper 애노테이션을 붙여야 MyBatis에서 인식 가능
- 메서드를 호출시 xml 의 해당 SQL을 실행하고 결과를 돌려준다.
◎ 참고 : 자바 코드가 아니기 때문에 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
XML 특수문자
< : <
> : >
& : &
XML에서 지원하는 CDATA 구문 문법을 사용
대신 이 구문 안에서는 XML TAG가 단순 문자로 인식 되기 때문에 <if>, <where> 등이 적용되지 않는다.
XML CDATA 사용
<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">
<![CDATA[
and price <= #{maxPrice}
]]>
</if>
</where>
</select>
분석
ItemMapper 인터페이스
@Mapper
public interface ItemMapper {
void save(Item item);
void update(@Param("id") Long id, @Param("updateParam") ItemUpdateDto updateParam);
List<Item> findAll(ItemSearchCond itemSearch);
Optional<Item> findById(Long id);
}
→ 인터페이스의 구현체가 없어도 MyBatis 스프링 연동 모듈에서 자동으로 처리한다.

- 애플리케이션 로딩 시점에 MyBatis 스프링 연동 모듈은 @Mapper 가 붙어있는 인터페이스를 조사한다.
- 해당 인터페이스가 발견되면 동적 프록시 기술을 사용해서 ItemMapper 인터페이스의 구현체를 만든다.
- 생성된 구현체를 스프링 빈으로 등록한다.
※ 정리
- 매퍼 구현체를 사용하면 스프링 예외 추상화도 함께 적용된다.
- 마이바티스 스프링 연동 모듈이 데이터베이스 커넥션, 트랜잭션과 관련된 기능도 마이바티스와 함께 연동하고, 동기화해준다.
◎ 참고
마이바티스 스프링 연동 모듈이 자동으로 등록 : MybatisAutoConfiguration 클래스를 참고
동적 쿼리
- MyBatis 공식 메뉴얼: https://mybatis.org/mybatis-3/ko/index.html
- MyBatis 스프링 공식 메뉴얼: https://mybatis.org/spring/ko/index.html
동적 SQL
- if
- choose (when, otherwise)
- trim (where, set)
- foreach
if
<select id="findActiveBlogWithTitleLike" resultType="Blog">
SELECT * FROM BLOG
WHERE state = ‘ACTIVE’
<if test="title != null">
AND title like #{title}
</if>
</select>
- 해당 조건에 따라 값을 추가할지 말지 판단한다.
- 내부의 문법은 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>
- 자바의 switch 구문과 유사한 구문도 사용
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 를 추가한다.
- 만약 and 가 먼저 시작된다면 and 를 지운다.
- 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>
- 컬렉션을 반복 처리할 때 사용한다. where in (1,2,3,4,5,6) 와 같은 문장을 쉽게 완성
- 파라미터로 List 를 전달
◎ 참고 : 동적 쿼리에 대한 자세한 내용은 다음을 참고하자.
https://mybatis.org/mybatis-3/ko/dynamic-sql.html
기타 기능
애노테이션으로 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"> ~ </select> 는 제거해야 한다.
- 동적 SQL이 해결되지 않으므로 간단한 경우에만 사용
문자열 대체(String Substitution)
#{} 문법은 ?를 넣고 파라미터를 바인딩하는 PreparedStatement 를 사용한다.
때로는 파라미터 바인딩이 아니라 문자 그대로를 처리하고 싶은 경우도 있다. 이때는 ${} 를 사용하면 된다.
@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);
SQL 인젝션 공격을 당할 수 있다. 따라서 가급적 사용하면 안된다. 사용하더라도 매우 주의깊게 사용해야 한다.
재사용 가능한 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> 조각을 찾아서 사용할 수 있다.
<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
<select id="selectUsers" resultType="User">
select
user_id as "id",
user_name as "userName",
hashed_password as "hashedPassword"
from some_table
where id = #{id}
</select>
- 결과를 매핑할 때 테이블은 user_id 이지만 객체는 id 이다.
- 컬럼명과 객체의 프로퍼티 명이 다르다. 그러면 다음과 같이 별칭( as )을 사용하면 된다.
<resultMap id="userResultMap" type="User">
<id property="id" column="user_id" />
<result property="username" column="username"/>
<result property="password" column="password"/>
</resultMap>
<select id="selectUsers" resultMap="userResultMap">
select user_id, user_name, hashed_password
from some_table
where id = #{id}
</select>
- 별칭을 사용하지 않고도 문제를 해결할 수 있는데, 다음과 같이 resultMap 을 선언해서 사용하면 된다.
◎ 참고 : 결과 매핑에 대한 자세한 내용은 다음을 참고
https://mybatis.org/mybatis-3/ko/sqlmap-xml.html#Result_Maps
'Spring > DB 2편' 카테고리의 다른 글
데이터 접근 기술 - 스프링 데이터 JPA (0) | 2023.08.11 |
---|---|
데이터 접근 기술 - JPA (0) | 2023.08.10 |
데이터 접근 기술 -테스트 (0) | 2023.08.10 |
데이터 접근 기술 - 스프링 JdbcTemplate (0) | 2023.08.10 |
데이터 접근 기술 - 시작 (0) | 2023.08.10 |