Spring/JDBC

[Spring DB] Mybatis

동그리담 2024. 8. 12. 16:42

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

  • 장점
    • SQL을 xml파일에 편리하게 작성할 수 있다.
    • 동적 쿼리를 작성하기 쉽다.
  • 단점
    • 따로 설정이 필요하다.

공식 사이트 메뉴얼 ( 번역 잘돼있음)
mybatis
 

설정

  1. build.gradle에 mybatis 라이브러리 추가
  2. application.properties 에 다음과 같이 설정추가 **(Main, Test 두 곳 다)
  3. #MyBatis
  4. #마이바티스 xml에서 사용할 객체 경로(경로 생략을 위함)
  5. #하위 패키지와 하위 패키지 자동으로 인식
  6. mybatis.type-aliases-package=hello.itemservice.domain
  7.  
  8. #스네이크 표기법을 카멜표기법으로 치환해줌 
  9. mybatis.configuration.map-underscore-to-camel-case=true
  10.  
  11. #로그 단계 설정 (hello.itemservice.repository.mybatis 가 mybatis를 만들 패키지)
  12. logging.level.hello.itemservice.repository.mybatis=trace

사용 설정

인터페이스 생성 (인터페이스 메서드 호출하면 xml 실행) - 구현체 자동으로 만들어줌

@Mapper
public interface ItemMapper {
    void save(Item item);
            //1개인 경우는 생략가능 2개 이상 @Param 지정
    void update(@Param("id") long id, @Param("updateParam") ItemUpdateDto updateItem);

    List<Item> findAll(ItemSearchCondition itemSearch);

    Optional<Item> findById(long id);

}

같은 위치에 실행할 SQL이 있는 XML 매핑파일 생성
*참고* 자바코드가 아니기 때문에 resources에 만들되 패키지위치들 동일시켜야한다.
ex) 인터페이스 패키지 : 

java -> package hello.itemservice.repository.mybatis;
public interface ItemMapper

xml 디렉토리 :
resources/hello/itemservice/repository/mybatis 에 ItemMapper.xml

참고 - XML 파일 경로 수정하기

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

  • mybatis.mapper-locations=classpath:mapper/**/*.xml
  • 이렇게 하면 resources/mapper 를 포함한 그 하위 폴더에 있는 XML을 XML 매핑 파일로 인식한다.
  • 이 경우 파일 이름은 자유롭게 설정해도 된다.
  • 참고로 테스트의 application.properties 파일도 함께 수정해야 테스트를 실행할 때 인식할 수 있다

 
 
 
기본 문법

<?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"> <!-- 인터페이스 위치 -->
    
</mapper>

 

기본 문법

Insert SQL

<insert id="save" useGeneratedKeys="true" keyProperty="id">
    INSERT INTO item (item_name, price, quantity)
    VALUES (#{itemName},#{price},#{quantity})
</insert>
  • <insert> 사용
  • id는 매퍼 인터페이스에 설정한 메서드 이름 지정
  • 파라미터는 #{ } 문법 사용하고, 안에는매퍼에서 넘긴 객체의 프로퍼티 이름을 적어주면 된다.
  • #{ } 문법을 사용하면 PreparedStatment를 사용한다. (JDBC의 ?를 치환 개념)
  • useGeneratedKeys는 데이터베이스가 키를 생성해주는 전략일때 사용
  • keyProperty는 키 속성을 지정하고
  • insert가 끝나면 item객체의 id속성에 생성된 값이 입력된다.

 

Update SQL

<!--
    Parameter 가 1개인 경우 변수명만 적어주면 자바 프로퍼티를 사용해 값을 꺼내오지만 ex) save
    Parameter 가 2개 이상 넘어올 경우 인터페이스에서 보낸
    Parameter 의 이름을 지정해야 한다. ex)update
    -->
<update id="update">
    UPDATE item
    SET item_name=#{updateParam.itemName},
        price=#{updateParam.price},
        quantity=#{updateParam.quantity},
        id=#{id}
    WHERE id = #{id}
</update>
  • <update> 사용
  • 여기서는 파라미터가 Long id , ItemUpdateDto updateParam 으로 2개이다.
  • 파라미터가 1개만 있으면 @Param 을 지정하지 않아도 되지만,
    파라미터가 2개 이상이면 @Param 으로 이름을 지정해서 파라미터를 구분 해야 한다.

 

Select SQL - 1개

<!-- resultType = 설정 파일에 클래스 객체 패키지를 지정해줬기 때문에 Path 생략가능-->
<select id="findById" resultType="Item">
    SELECT id, item_name, price, quantity
    FROM item
    WHERE id = #{id}
</select>
  • <select> 사용
  • resultType은 반환 타입 명시
  • JdbcTemplate의 BeanPropertyRowMapper 처럼 SELECT SQL의 결과를 편리하게 객체로 바로 변환해준다
  • 자바 코드에서 반환 객체가 하나이면 Item , Optional 과 같이 사용하면 되고, 반환 객체가 하나 이상 이면 컬렉션을 사용하면 된다. 주로 List 를 사용한다.

 

Select SQL  - 여러개  + 동적 쿼리

<select id="findAll" resultType="Item">
    SELECT id, item_name, price, quantity
    from Item
    <where>
        <if test="itemName != null and itemName != ''">
            and item_name like '%' || #{itemName} || '%'
        </if>        <!-- h2, mysql = cocat('%',#{itemName},'%')-->
        <if test="maxPrice != null">
            and price $lt;= #{maxPrice}
        </if> <!-- <는 xml 태그여서 $lt사용 -->
    </where>
</select>
  • <where>, <if> 같은 동적 쿼리 문법을 지원
  • <if>는 해당 조건이 만족하면 구문을 추가해준다.
  • <where>는 적절하게 where문장을 만들어준다.
    • 위 경우 <if>가 모두 실패하면 where를 만들지 않고
    • 하나라도 성공하면 처음 나타나는 and를 where로 변환해줌
  • 특수문자 < 사용불가
    • < : &lt
    • > : &gt
    • & : &amp
    • " : &quot

<![CDATA[ ... ]]>

< , > , & , " 를 쿼리문 안에 포함시켜야 할다면
<![CDATA[ ... ]]> 구문 문법을 사용하면 XML TAG가 단순 문자로 인식된다.
위의 방식으로 치환하거나 Char Data를 상황에 맞게 사용하자
또한, 이 구문 안에서 <if> <where>등이 적용되지 않는다.
예시

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

 
 

Repository 생성

@Repository
@RequiredArgsConstructor
public class MyBatisRepository 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(ItemSearchCondition cond) {
        return itemMapper.findAll(cond);
    }
}

엄청나게 간결해진 모습을 볼 수 있다. (호출, 매개변수주입)

 

작동 방식

인터페이스를 주입받아서 사용하는데 어떻게 사용되는 것일까?

  1. 애플리케이션 로딩시점에 Mybatis 스프링 연동 모듈은 @Mapper가 붙어있는 인터페이스를 조사한다.
  2. 해당 인터페이스가 발견되면 동적 프록시 기술을 사용해서 구현체를 만든다.
  3. 생성된 구현체를 스프링 빈으로 등록한다.
    • Repository에서 로그를 출력해보면 class com.sun.proxy.$Proxy66을 확인할 수 있다.
  • 매퍼 구현체
    • 마이바티스 스프링 연동 모듈이 만들어주는 구현체 덕분에 인터페이스 만으로 편리하게 XML의 데이터를 찾아서 호출할 수 있다.
    • 매퍼 구현체는 예외 변환까지 처리해준다. MyBatis에서 발생한 예외를 스프링 예외 추상화인
      DataAccessException에 맞게 변환해서 반환해준다. JdbcTemplate이 제공하는 예외 변환 기능을 여기서도 제공한다.
  • 정리
    • 매퍼 구현체 덕분에 마이바티스를 스프링에 편리하게 통합해서 사용할 수 있다.
    • 매퍼 구현체를 사용하면 스프링 예외 추상화도 함께 적용된다.
    • 마이바티스 스프링 연동 모듈이 많은 부분을 자동으로 설정해주는데, 데이터베이스 커넥션,
      트랜잭션과 관련된 기능도 마이바티스와 함께 연동하고, 동기화해준다.

 

동적 쿼리 기능

기타 기능

  • 애너테이션으로 SQL 작성
    • xml이 아닌 Mapper 인터페이스  메서드 레벨에 작성할 수 있는
      @Insert, @Update, @Delete, @Select 기능이 제공된다.
    • 이 경우 xml에 정의한 쿼리를 제거해야한다.
    • 동적 SQL 사용이 불가하다
  • 문자열 대체 - ${ }
    • 문자열 값 그대로 처리할때
    • 보통 컬럼명과 테이블 명을 동적으로 처리하는 기능을 가지고 있다.
    • ※주의※ SQL injection 공격을 당할 위험이 있다.
  • 재사용 가능한 SQL 조각
    • <sql> 을 사용하면 SQL 코드를 재사용 할 수 있다.
      • ex) <sql id="userColumns"> ${alias}.id, ${alias}.username </sql>
      • <select>
        select
        <include refid="usercolums"><property name="alias" value ="t1" /></include>
        <include refid="userColumns"><property name="alias" value="t2"/></include>
        form t1 </select>
        <include>를 통해서 <sql> 조각을 찾아서 사용할 수 있다.
  • Result Maps
    • 결과를 매핑할때 카멜, 스네이크의 차이가 아닌 이름이 완전 다르면 as를 사용해야한다.
      이때 as가 아닌 resultMap을 이용해서 처리할 수 있다.

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>

 

복잡한 결과 매핑
<assciation>, <collection> 등을 사용할 수있다. (이때는 JPA가 자연스러움)

결과 매핑에 대한 자세한 내용은 다음을 참고하자.
https://mybatis.org/mybatis-3/ko/sqlmap-xml.html#Result_Maps
 
 
출처: https://tan-sog.tistory.com/90