본문 바로가기
인프런/스프링 DB 2편 - 데이터 접근 활용 기술

[인프런] 스프링 DB 2편 - 데이터 접근 기술 활용 / 4. MyBatis

by hxxyeoniii 2024. 7. 28.

MyBatis 소개

MyBatis는 JdbcTemplate보다 더 많은 기능을 제공하는 SQL Mapper

가장 큰 장점은 SQL을 XML에 편리하게 작성할 수 있고 동적 쿼리를 매우 편리하게 작성할 수 있다는 점이다.

 

-> XML에 작성하기 때문에 라인이 길어져도 문자 더하기에 대한 불편함이 없음

-> 동적 쿼리를 매우 편리하게 작성할 수 있는 다양한 기능을 제공해줌

-> JdbcTemplate은 스프링에 내장된 기능이기 때문에 별도 설정 없이 사용할 수 있지만 MyBatis는 약간의 설정이 필요


MyBatis 설정

build.gradle에 mybatis-spring-boot-starter 라이브러리 추가 

-> MyBatis를 스프링과 통합하고, 설정도 아주 간단히 할 수 있음

 

 

application.properties에 설정 추가

mybatis.type-aliases-package

   : 마이바티스에서 타입 정보 사용 시 패키지 이름을 적어주어야 하는데, 여기에 명시하면 패키지 이름 생략이 가능하다. 

   : 지정한 패키지와 하위 패키지가 자동으로 인식됨

   : 여러 위치 지정시 ,나 ;로 구분

- mybatis.configuration.map-underscore-to-camel-case

   : JdbcTemplate의 BeanPropertyRowMapper처럼 언더바를 카멜로 자동 변경해주는 기능 활성화(관례의 불일치 참고)

- logging.level.hello.itemservice.repository.mybatis=trace

   : 마이바티스에서 실행되는 쿼리 로그 확인 가능

 

 

* 관례의 불일치

자바 객체는 주로 카멜 표기법 사용, 관계형 데이터베이스는 주로 언더스코어 표기법 사용

map-underscore-to-camel-case 기능 활성화 시, 언더스코어를 카멜로 자동 변환해줌

-> DB에서 item_name으로 조회해도 객체의 itemName(setItemName()) 속성에 값이 정상 입력됨


MyBatis 적용

ItemMapper.java : 매퍼 인터페이스

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 어노테이션을 붙여야 마이바티스가 인식함

 

 

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

    <!-- 파라미터는 #{} 문법 사용 : PreparedStatement 사용 -->
    <!-- JDBC의 ?를 치환한다고 생각하면 됨 -->
    <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>

    <!-- resultType을 바로 Item에 매핑할 수 있음 -->
    <!-- application.properties에 type-aliases 속성을 지정해줬기 때문 -->
    <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에 매퍼 인터페이스 지정

 

 

* xml 파일을 원하는 위치에 두고 싶으면 application.properties에 다음과 같이 설정하면 된다.

mybatis.mapper-locations=classpath:mapper/**/*.xml 

-> resources/mapper를 포함한 그 하위 폴더에 있는 xml을 xml 매핑 파일로 인식한다. 파일 이름은 자유롭게 설정해도 된다.

 

 

* XML 특수문자

xml에서는 데이터 영역에 <, > 같은 특수 문자를 사용할 수 없음(tag에 <, > 를 사용하기 때문)

< : &lt;

> : &gt;

& : &amp;

 

또는, xml에서 지원하는 CDATA 구문 문법을 사용할 수도 있다.

이 구문 안에서는 특수문자 사용이 가능하다. 하지만 이 구문 안에서는 xml tag가 단순 문자로 인식되기 때문에 <if>, <where> 등이 적용되지 않는다.

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

MyBatis 분석

ItemMapper 매퍼 인터페이스의 구현체가 없는데 어떻게 동작할까?

-> MyBatis 스프링 연동 모듈에서 자동으로 처리해줌

 

 

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

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

3. 생성된 구현체를 스프링 빈으로 등록(JDK 동적 프록시)

 

 

-> 매퍼 구현체 덕분에 마이바티스를 스프링에 편리하게 통합해 사용 가능

-> 매퍼 구현체를 사용하면 스프링 예외 추상화도 함께 적용됨

-> 마이바티스 스프링 연동 모듈이 많은 부분을 자동으로 설정해주는데, 데이터베이스 커넥션, 트랜잭션과 관련된 기능도 마이바티스와 함께 연동하고 동기화해줌


MyBatis 기능 정리

동적 SQL

- if

- choose(when, otherwise)

- trim(where, set)

- foreach

 

 

애노테이션으로 SQL 작성

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

- @Insert, @Update, @Delete, @Select

- <select id="...">  </select>는 제거해야 함

- 동적 SQL이 해결되지 않으므로 간단한 경우에만 사용

 

 

문자열 대체

#{} 문법은 ?를 넣고 파라미터를 바인딩하는 PreparedStatement를 사용한다.

때로는 파라미터 바인딩이 아닌 문자 그대로를 처리하고 싶은 경우가 존재하는데 이때는 ${}를 사용하면 된다.

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

 

* ${}를 사용하면 SQL 인젝션 공격을 당할 수 있기에 가급적 사용하면 안 된다. 

 

 

SQL 코드 재사용

<include>를 통해 <sql> 조각을 찾아서 사용이 가능하다.

 

 

Result Maps

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