💠프로그래밍 언어/Java

[Spring] MyBatis 사용법 익히기 !

2025. 5. 1. 21:57
728x90

 

MyBatis 소개

- JdbcTemplate 보다 많은 기능을 제공하는 SQL Mapper 중 하나이다.

- SQL 을 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";
}

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 &lt;= #{maxPrice}
    </if>
  </where>
</select>

 

초기 설정

build.gradle 에 다음 설정을 추가해 준다.

implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'

(MyBatis 는 스프링 부트가 버전 관리해 주는 공식 라이브러리가 아니기 때문에, 뒤에 버전 정보가 붙는다.)

 

`main` 과 `test` 의 application.properties 에 다음 설정을 추가해 준다.

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` : snake_case 와 camelCase 표시법을 자동 변경하는 기능을 활성화

`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 updateParam);
  Optional<Item> findById(Long id);
  List<Item> findAll(ItemSearchCond itemSearch);
}

- MyBatis 매핑 XML 을 호출해 주는 매퍼 인터페이스이다.

- 파라미터가 2개 이상이면 @Param 을 붙여야 한다.

- `@Mapper` 애노테이션을 붙여야 MyBatis 에서 인식할 수 있다.

- 메서드를 호출하면 매핑 XML 의 해당 SQL 을 실행하고 결과를 돌려준다.

 

> 참고 <

- 매퍼 인터페이스의 구현체는 Mybatis 스프링 모듈이 자동으로 인식하여 생성하고, 빈으로 등록해 준다.

 

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 &lt;= #{maxPrice}
      </if>
    </where>
  </select>
  
</mapper>

- src/main/resources 하위에 패키지 위치를 맞추어 만들어 준다.

- `namespace` 에 매퍼 인터페이스를 지정한다.

- `#{}` : `PreparedStatement` 를 사용한다.

 

Insert

- `useGeneratedKeys` : 데이터베이스가 키를 생성해 주는 `IDENTITY` 전략일 때 사용한다.

- `keyProperty` : 생성되는 키의 속성 이름을 지정한다. Insert 가 끝나면, id 속성에 생성된 값이 입력된다.

Select

- `resultType` : 결과를 해당 반환 타입의 객체로 매핑하여 반환한다.

  (`mybatis.type-aliases-package` 속성으로 패키지 명을 생략할 수 있다.)

  (`mybatis.configuration.map-underscore-to-camel-case` 속성으로 표기법 간의 차이를 자동으로 처리해 준다.)

 

XML 특수문자

데이터 영역에 특수 문자를 사용할 수 없기 때문에, `&lt;`, `&gt;` 같은 문자로 치환하여 사용한다.

 

하지만 CDATA 구문 문법을 사용하면 해당 구문 안에서는 특수문자를 사용할 수 있다.

<if test="maxPrice != null">
  <![CDATA[
  and price <= #{maxPrice}
  ]]>
</if>

 

리포지토리

@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);
  }
}

매퍼 인터페이스의 @Mapper 를 통해 Mybatis 스프링 연동 모듈에서 매퍼 인터페이스(ItemMapper) 를 자동으로 인식한다.

그리고 매퍼 구현체로 동적 프록시 객체를 자동으로 만들고 빈으로 등록해 주기 때문에, 리포지토리에서 매퍼 인터페이스의 구현체를 의존관계 주입받아 사용할 수 있다.

 

매퍼 구현체

MyBatis 스프링 연동 모듈이 만들어주는 구현체 덕분에 인터페이스만으로 편리하게 XML 데이터를 찾아 호출할 수 있다.

 

또한 스프링 예외 추상화 기능도 제공하여 DataAccessException 에 맞게 변환하여 반환해 준다.

 

MyBatis 스프링 연동 모듈

- 데이터베이스 커넥션

- 트랜잭션

등 수많은 부분을 자동 설정해 준다.

 

MyBatis 동적 SQL 기능

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 구문과 유사하게 사용할 수 있다.

 

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 __ and __ / __ / where __ 같은 경우를 동적으로 처리해 준다. 

 

trim

<trim prefix="WHERE" prefixOverrides="AND |OR ">
 ...
</trim>

- 다음과 같이 정의하면, <where> 과 같은 기능을 수행한다.

 

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 를 전달하면 된다.

 

MyBatis 기타 기능

@Insert, @Update, @Delete, @Select

@Select("select id, item_name, price, quantity from item where id=#{id}")
Optional<Item> findById(Long id);

- XML 대신 애노테이션에 직접 SQL 을 작성할 수 있다.

- 간단한 경우에만 사용한다.

 

문자열 대체 ${ }

@Select("select * from user where ${column} = #{value}")
User findByColumn(@Param("column") String column, @Param("value") String value);

 

- 파라미터 바인딩이 아닌 문자 그대로 처리하고 싶은 경우에 사용한다.

- SQL 인젝션 공격에 주의해야 한다. (가급적 사용 X)

 

재사용 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>

- 프로퍼티 값을 전달할 수도 있고, 해당 값은 내부에서 사용할 수 있다.

(<sql> 의 SQL 문에서 ${ } 값이, <property> 의 name 값과 같으면 value 값을 전달한다.)

 

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>

- 칼럼명과 객체의 프로퍼티 명이 다른 경우 별칭 (as) 를 사용한다.

  (snake_case 와 camelCase 표시법 간의 변환으로 해결되지 않을 때)

<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>

- 별칭을 사용하지 않고 해결할 수 있다.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

출처 | 스프링 DB 2(김영한) - 인프런

 
728x90