본문 바로가기
공부/DB

[Spring DB](5) 데이터 접근 기술 - MyBatis

by 다음에바꿔야지 2024. 2. 27.

JdbcTemplate과 비교점은 MyBatis는 SQL을 XML에 편리하게 작성할 수 있고 동적 쿼리를 매우 편리하게 작성할 수 있다.

 

MyBatis 적용

package hello.itemservice.repository.mybatis;

import hello.itemservice.domain.Item;
import hello.itemservice.repository.ItemSearchCond;
import hello.itemservice.repository.ItemUpdateDto;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;

import java.util.List;
import java.util.Optional;

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

마이바티스 매핑 XML을 호출해주는 매퍼 인터페이스이다.

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

 

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" resultMap="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>

namespace: 앞서 만든 매퍼 인터페이스를 지정하면 된다.

 

save()

id = 매퍼 인터페이스에서 설정한 메서드 이름

userGeneratedKeys = IDENTITY 전략일때 사용

keyProperty = 생성되는 키의 속성 이름

 

update()

파라미터가 1개만 있으면 @Param을 지정하지 않아도 되지만, 2개 이상이면 @Param으로 이름을 지정해서 파라미터를 구분해야 한다.

 

findById()

resultType = 반환 타입 명시

 

findAll()

<where>, <if>같은 동적 쿼리 문법을 통해 편리한 동적 쿼리를 지원한다.

<if>는 해당 조건이 만족하면 구문을 추가한다.

 

 

MyBatisItemRepository

package hello.itemservice.repository.mybatis;

import hello.itemservice.domain.Item;
import hello.itemservice.repository.ItemRepository;
import hello.itemservice.repository.ItemSearchCond;
import hello.itemservice.repository.ItemUpdateDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Slf4j
@Repository
@RequiredArgsConstructor
public class MyBatisItemRepository implements ItemRepository {
    private final ItemMapper itemMapper;

    @Override
    public Item save(Item item) {
        log.info("itemMapper class={}", itemMapper.getClass());
        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에 기능을 위임한다.

 

MyBatisConfig

package hello.itemservice.config;


import hello.itemservice.repository.ItemRepository;
import hello.itemservice.repository.mybatis.ItemMapper;
import hello.itemservice.repository.mybatis.MyBatisItemRepository;
import hello.itemservice.service.ItemService;
import hello.itemservice.service.ItemServiceV1;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

ItemMapper를 주입받고, 필요한 의존관계를 만든다.

 

ItemServiceApplication의 @Import도 변경해준 후 실행해본다.

 

 

ItemMapper는 구현체가 없는데 어떻게 동작한 것일까?

MyBatis 스프링 연동 모듈에서 자동으로 처리해준다.

1. 애플리케이션 로딩 시점에 MyBatis 스피링 연동 모듈은 @Mapper가 붙어있는 인터페이스를 조사한다.

2. 해당 인터페이스가 발견되면 동적 프록시 기술을 사용해서 ItemMapper 인터페이스 구현체를 프록시로 만든다.

3. 생성된 구현체를 스프링 빈으로 등록한다.

이런 작동 방식 덕분에 구현체 없이 사용이 가능했다.

 

 

MyBatis 기능 - 동적 쿼리

*if

<select id="findActiveBlogWithTitleLike" resultType="Blog">
   SELECT * FROM BLOG
   WHERE state = ‘ACTIVE’
   <if test="title != null">
     AND title like #{title}
   </if>
</select>

조건에 따라 값을 추가할지 말지 판단한다.

 

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

 

*sql, include
<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> 조각을 찾아서 사용할 수 있다

 

정리

프로젝트에서 동적 쿼리와 복잡한 쿼리가 많다면 MyBatis를 사용하고, 단순한 쿼리들이 많으면 JdbcTemplate을 선택해서 사용하면 된다.

@Mapper를 통해 구현체 없이, 인터페이스만으로도 편리하게 MyBatis를 사용할 수 있다.

XML을 사용해서 동적 쿼리를 다루기 편하다.