카테고리 없음

MyBatis 사용하기 [기초]

leegeonwoo 2024. 7. 27. 19:39

MyBatis에서는 기본적으로 JdbcTemplate이 제공하는 대부분의 기능을 제공한다.
추가적으로 아래와 같은 장점을 가지고있다.

  • SQL을 XML을 통해 편리하게 작성, 관리
  • 동적 쿼리를 태그로 편리하게 작성

설정
JdbcTemplate은 스프링에 내장된 기능으로 별도의 설정없이 사용할 수 있다.
반면에 MyBatis는 약간의 설정이 필요하다.

실무에서는 프로젝트에 동적쿼리와 복잡한 쿼리가 많다면 MyBatis를 사용하고 단순한 쿼리들이 많다면 JdbcTemplate을 선택해서 사용하면된다.
물론 이 둘을 함께 사용할수도 있다.

MyBatis공식 사이트
https://mybatis.org/mybatis-3/ko/index.html
동적 쿼리에 대한 문서
https://mybatis.org/mybatis-3/ko/dynamic-sql.html


설정

Gradle기준 의존성 주입
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.2.0'

스프링 부트 3.0버전 이상일경우
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'

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를 사용할 때 XML파일에서 타입정보를 패키지 이름을 입력해줌으로써 XML에서 간단하게 타입정보를 입력할 수 있다.
  • mybatis.configuration.map-underscore-to-camel-case
    • JdbcTemplateBeanPropertyRowMapper에서처럼 스네이크 케이스를 카멜케이스로 자동변경해주는 기능을 활성화한다.
  • logging.level.hello.itemservice.repository.mybatis=true
    • 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);  
}

먼저 XML파일에서 매핑될 인터페이스를 선언해주어야한다.

  • 매퍼 인터페이스에는 반드시 @Mapper어노테이션을 붙여주어야하며, 이 어노테이션을 기반으로 MyBatis에서 인식한다.
  • 매퍼 인터페이스의 메서드가 호출되면 XML파일에 선언되어있는 해당되는 SQL을 실행하고 결과를 반환받는다.

[!매퍼 인터페이스의 구현체는 내부적으로 생성] Title
Contents

XML파일

실행할 SQL을 담고있는 XML파일을 작성해주어야한다.
참고로 XML파일은 resource하위 디렉터리에 만들어져야하며 패키지는 매퍼 인터페이스와 맞춰주어야한다.

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

MyBatis를 사용하기위한 기본 선언이라고 생각하자

<mapper namespace="hello.itemservice.repository.mybatis.ItemMapper">
</mapper>

사용할 SQL의 범위를 정의하며 해당 태그안에 사용할 SQL들을 작성해 주어야한다.

  • namespace속성은 매퍼 인터페이스의 패키지경로를 지정해주면 된다.
  • 만약 XML을 따로 패키지를 만들어 관리하고싶다면 application.properties파일에 mybatis.mapper-locations=classpath:mapper/**/*.xml로 설정해주면 resources/mapper를 포함한 그 하위 폴더에 있는 XML을 XML매핑 파일로 인식한다.

save()

<insert id="save" useGeneratedKeys="true" keyProperty="id">  
    insert into item (item_name, price, quantity)  
    values (#{itemName}, #{price}, #{quantity})
</insert>
  • id속성은 매퍼 인터페이스에 정의되어있는 실행할 메서드의 이름을 지정해준다.
  • 파라미터 값은 #{}문법을 통하여 작성해주며 매퍼 인터페이스에서 넘긴 객체에 맞추어준다.
  • userGenerateKeys속성은 데이터베이스가 키를 생성해주는 IDENTITY전략일 때 사용한다.
  • keyProperty는 생성되는 키의 속성 이름을 지정하므로 Item객체의 id필드에 생성된 값이 입력된다.

update()

Mapper Interface

 void update(@Param("id") Long id, @Param("updateParam") ItemUpdateDto
 updateParam);

XML

<update id="update">  
    update item  
    set item_name=#{updateParam.itemName}, 
        price=#{updateParam.price},
        quantity=#{updateParam.quantity}    
        where id=#{id}
</update>

매퍼 인터페이스에서 update()메서드의 파라미터는 id값과 UpdateDto두 개의 값을 받았다.
파라미터가 두 개 이상일 경우에는 @Param으로 XML에서 사용될 파라미터의 이름을 지정해주어서 파라미터를 구분해주어야한다.

findById()

<select id="findById" resultType="Item">  
    select id, item_name, price, quantity  
    from item    
    where id = #{id}
</select>
  • resultType속성은 반환 타입을 명시한다. 해당되는 SQL의 실행결과를 resultType에 맞게 매핑해준다. 위 예시에서는 Item객체에 매핑해준다.
    • application.properties에서 mybatis.type-aliases-package설정을 설정해준 덕분에 resultType속성 값에 Item객체를 명시하는 것만으로 간단하게 사용할 수 있다.
    • JdbcTemplateBeanPropertyRowMapper처럼 SQL의 결과를 편리하게 객체로 바로 변경해준다(ResultSet)
    • application.properties에서 map-underscore-to-camler-case=true로 설정해줬기 때문에 item_name컬럼을 itemName으로 자동으로 치환해준다.

findAll()
Mapper 인터페이스

List<Item> findAll(ItemSearchCond itemSearch);

findAll의 실행결과는 여러 Row가 반환될 수 있기때문에 List컬렉션으로 반환타입을 받아준다.
XML

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

MyBatis를 사용하는 이유인 동적쿼리를 처리하는 기능이다.
<where>, <if>와 같은 태그형태로 동적 쿼리 문법을 편리하게 제공할 수 있다.

  • \ <if>는 해당 조건이 만족한다면 바디 부분의 구문을 추가한다.
  • \ <where>는 조건에 맞게 동적으로 where절을 처리한다.
    • 만약 <if>가 모두 실패하게 되면 SQL where를 추가하지 않는다. (추가되면 SQL문법오류)
    • 만약 <if>가 하나라도 성공하면 처음나타나는 andwhere로 변환해준다.
  • 추가로 and price &lt;= #{maxPrice}에서 <= 를 직접사용할 경우 태그로 인식되기 때문에 <와 같이 특수문자를 변형해서 사용해야한다.

[!resultType에 기재한 객체와 매핑 되지않을경우]

  1. SQLException또는 TypeMismatchException예외가 발생할 수 있다.
  2. 예상치 못한 데이터가 반환될 수 있다.
    즉, SQL결과가 resultType에 기재한 객체와 제대로 매핑되어야하며 다음 사항을 준수해야한다
  3. SQL결과 셋의 컬럼 이름이 resultType 객체의 필드이름과 일치해야한다.
  4. SQL결과 셋의 데이터 타입이 resultType 객체의 필드 타입과 호환되도록 해야한다
  5. 매핑을 명시적으로 설정하는 어노테이션을 제공하기도한다 아래 코드와 같다.

@Select("select id, item_name, price, quantity from item where id = #{id}")
@Results({
    @Result(property = "id", column = "id"),
    @Result(property = "itemName", column = "item_name"),
    @Result(property = "price", column = "price"),
    @Result(property = "quantity", column = "quantity")
})
Item findById(Long id);

되도록이면 사용하지 말고, 참고만 해두자


MyBatis Repository계층 구현

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

MyBatis를 사용함으로써 Repository계층에서는 단순히 매퍼 인터페이스를 구현한 구현 클래스(MyBatis가 내부적으로 생성한 구현클래스)에 기능을 위임한다.
결과적으로는 Repository계층에 SQL이 들어가지 않게된다.


매퍼 인터페이스의 구현체

지금까지 우리가 매퍼 인터페이스인 ItemMapper를 사용할 때 이에 대한 구현 클래스가 없음에도 마치 구현체가 있는듯이 사용되었던 것을 확인할 수 있었다.
이에 대해 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);  
}
  1. 먼저 의존성 주입을 통해 받은 라이브러리인 MyBatis스프링 연동 모듈에서 @Mapper를 조회한다.
  2. @Mapper가 사용된 인터페이스를 발견하면 동적 프록시 기술을 사용해서 ItemMapper인터페이스의 구현체를 생성한다.
  3. 생성된 구현체를 스프링 빈으로 등록한다.
  • 마이바티스 스프링 연동 모듈덕분에 매퍼 인터페이스 만으로도 편리하게 XML을 사용할 수 있다.
  • 매퍼 구현체는 예외 변한까지 처리해준다.
  • 마이바티스 스프링 연동 모듈은 데이터베이스 커넥션, 트랜잭션과 관련된 기능도 마이바티스와 함께 연동하고 동기화해준다.

MyBatis의 기능

MyBatis 공식 메뉴얼: https://mybatis.org/mybatis-3/ko/index.html
MyBatis 스프링 공식 메뉴얼: https://mybatis.org/spring/ko/index.html

동적 쿼리

마이바티스는 XML의 특징을 살려 동적쿼리를 편리하게 사용할수 있도록 한다.
동적 쿼리를 위해 제공되는 기능(태그)은 다음과 같다

  • if
  • choose(when, otherwise) - java의 switch와 유사
  • trim(where, set)
  • foreach

<where>

<mapper namespace="ItemMapper">
    <select id="findByCondition" resultType="Item">
        select id, item_name, price, quantity
        from item
        <where>
            <if test="itemName != null and itemName != ''">
                and item_name = #{itemName}
            </if>
            <if test="price != null">
                and price = #{price}
            </if>
        </where>
    </select>
</mapper>
  • 사용자가 필터검색란에서 상품명과 가격제한을 두지 않았을 경우
    • select id, item_name, price, quantity from item SQL이 실행
    • 모든 item데이터베이스를 출력한다
  • 사용자가 상품명에 대한 입력이 있을 경우
    • select Id, item_name, price, quantity from item에 추가로 and item_name = #{itemName}이 들어와 상품명에 대한 조건을 추가한다.
    • 만약 이 조건이 처음으로 들어올 경우 and가 where로 변환된다.
  • 사용자가 가격에 대한 입력이 있을 경우
    • select Id, item_name, price, quantity from item에 추가로 and price = #{price}가 추가된다.
    • 이 조건도 마찬가지로 처음 조건으로 들어갈 경우 and가 where로 변환된다.
  • 상품명, 가격에 대한 입력이 모두 있을 경우 아래와 같이 동적SQL이 만들어진다
    select id, item_name, price, quantity
    from item
    where item_name = #{itemName}
    and price = #{price}

<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)과 같은 문장을 쉽게 완성한다.

<sql>
<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**
결과를 매핑할 때 테이블은 `user_id` 이지만 객체는 `id` 이다.  
이 경우 컬럼명과 객체의 프로퍼티 명이 다르다. 그러면 다음과 같이 별칭( `as` )을 사용하면 된다.
```xml
<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>

위와 같이 별칭을 사용할수도있지만, 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>

결과매핑에 대한 문서
https://mybatis.org/mybatis-3/ko/sqlmap-xml.html#Result_Maps

728x90