Java Category/Spring

[Spring DB] MyBatis

ReBugs 2024. 4. 3.

이 글은 인프런 김영한님의 Spring 강의를 바탕으로 개인적인 정리를 위해 작성한 글입니다.


MyBatis 소개와 장점 및 단점

MyBatis는 자바(JAVA) 언어로 작성된 오픈 소스 SQL 매핑 프레임워크이다. JDBC(Java Database Connectivity) 위에 구축되어 데이터베이스와의 상호작용을 추상화하며, 개발자가 SQL 문을 직접 제어할 수 있게 해주는 특징을 가진다. 이는 개발자가 객체와 SQL 문 사이의 매핑을 설정하여, 데이터베이스 작업을 더 쉽고 직관적으로 할 수 있게 돕는다.

MyBatis의 주요 기능

  • SQL 분리: MyBatis는 SQL을 자바 코드에서 분리하여 XML 파일이나 어노테이션에 작성하도록 한다. 이로써, SQL 관리가 용이하고 가독성이 높아진다.
  • 동적 SQL: 조건에 따라 SQL 쿼리를 동적으로 생성할 수 있는 기능을 제공한다. 이는 검색 조건이 다양하고 복잡한 어플리케이션에서 유용하다.
  • 결과 매핑: SQL 쿼리 결과를 자바 객체에 자동으로 매핑한다. 컬럼 이름과 객체 필드 이름이 다른 경우에도 매핑 설정을 통해 쉽게 처리할 수 있다.

 

MyBatis의 장점

  • 직접적인 SQL 제어: MyBatis는 개발자가 SQL을 직접 작성하게 함으로써, 세밀한 쿼리 최적화와 복잡한 쿼리 작성이 가능하다.
  • 학습 곡선: MyBatis의 학습 곡선은 Hibernate나 JPA 같은 다른 ORM 프레임워크에 비해 상대적으로 낮다. SQL을 이미 알고 있다면 쉽게 배울 수 있다.
  • 유연성: 동적 SQL 지원을 통해 다양한 상황에 맞춤형 쿼리를 쉽게 작성할 수 있다는 점에서 유연성이 뛰어나다.
  • 통합성: Spring Framework와 같은 다른 자바 프레임워크와의 통합이 용이하다.

 

MyBatis의 단점

  • SQL 중심적: 모든 SQL 쿼리를 개발자가 직접 관리해야 하므로, SQL에 익숙하지 않은 개발자에게는 단점이 될 수 있다.
  • 복잡한 관계 매핑: 복잡한 객체 관계를 매핑하는 것이 JPA나 Hibernate에 비해 더 어렵고 수작업이 많이 필요하다.
  • 세션 관리: MyBatis에서는 SQL 세션을 직접 관리해야 할 필요가 있는데, 이는 때때로 복잡할 수 있다.
    MyBatis는 SQL을 직접 다루고 싶어하는 개발자에게 매우 유용한 도구이다. 동시에, 프로젝트의 요구 사항이나 팀의 기술 스택에 따라 ORM 프레임워크를 선택해야 한다. 복잡한 도메인 모델이나 객체 관계 매핑이 중요한 프로젝트의 경우, JPA나 Hibernate와 같은 다른 ORM 솔루션이 더 적합할 수 있다.

 

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

기본적으로 JdbcTemplate이 제공하는 대부분의 기능을 제공한다.

JdbcTemplate과 비교해서 MyBatis의 가장 매력적인 점은 SQL을 XML에 편리하게 작성할 수 있고 또 동적 쿼리를 매우 편리하게 작성할 수 있다는 점이다.

 

JdbcTemplate - SQL 여러줄

String sql = "update item " +
         "set item_name=:itemName, price=:price, quantity=:quantity " +
         "where id=:id";

 

MyBatis - SQL 여러줄

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

 

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";
 }
 log.info("sql={}", sql);
 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>

 

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

마이바티스 공식사이트
https://mybatis.org/mybatis-3/ko/index.html

 

MyBatis 스프링 부트 설정

build.gradle

dependencies {
    //MyBatis 추가
    implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:버전'
}

위와 같이 의존성을 넣어주면 아래와 같이 라이브러리가 추가된다.

버전 정보는 스프링 부트의 버전과 호환되는 버전이 다르기 때문에 직접 입력해야 한다.

  • mybatis-spring-boot-starter : MyBatis를 스프링 부트에서 편리하게 사용할 수 있게 시작하는 라이브러리
  • mybatis-spring-boot-autoconfigure : MyBatis와 스프링 부트 설정 라이브러리 
  • mybatis-spring : MyBatis와 스프링을 연동하는 라이브러리
  • mybatis : MyBatis 라이브러리

 

 

application.properties

#MyBatis
 mybatis.type-aliases-package=hello.itemservice.domain
 mybatis.configuration.map-underscore-to-camel-case=true



mybatis.type-aliases-package : 마이바티스에서 타입 정보를 사용할 때는 패키지 이름을 적어주어야 하는데, 여기에 명시하면 패키지 이름 을 생략할 수 있다. 지정한 패키지와 그 하위 패키지가 자동으로 인식된다. 여러 위치를 지정하려면 ,와 ; 로 구분하면 된다. 

mybatis.configuration.map-underscore-to-camel-case : JdbcTemplate의 BeanPropertyRowMapper 처럼 언더바를 카멜로 자동 변경해주는 기능을 활성화 한다. 

 

관례의 불일치

자바 객체에는 주로 camelCase 표기법을 사용한다. itemName 처럼 중간에 낙타 봉이 올라와 있는 표기법이다.
반면에 관계형 데이터베이스에서는 주로 언더스코어를 사용하는 snake_case 표기법을 사용한다.item_name 처럼 중간에 언더스코어를 사용하는 표기법이다.

이렇게 관례로 많이 사용하다 보니 map-underscore-to-camel-case 기능을 활성화 하면 언더스코어 표기법을 카멜로 자동 변환해준다. 따라서 DB에서 select item_name 으로 조회해도 객체의 itemName (setItemName()) 속성에 값이 정상 입력된다.

정리하면 해당 옵션을 켜면 snake_case는 자동으로 해결되니 그냥 두면 되고, 컬럼 이름과 객체 이름이 완전히 다른 경우에는 조회 SQL에서 별칭을 사용하면 된다.
예)
- DB select item_name
- 객체 name

별칭을 통한 해결방안 : select item_name as name

 

MyBatis 적용

ItemMapper 인터페이스

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에서 인식할 수 있다. 이 인터페이스의 메서드를 호출하면 다음에 보이는 xml 의 해당 SQL을 실행하고 결과를 돌려준다.

 

@Mapper

마이바티스(MyBatis) 프레임워크에서 사용되며, 인터페이스를 마이바티스의 매퍼로 표시한다. 이 애노테이션을 사용함으로써 해당 인터페이스의 메소드들이 SQL 쿼리와 매핑될 수 있게 되며, 마이바티스는 이를 통해 SQL 세션을 관리하고, 쿼리 실행 및 결과 매핑을 처리한다.

 

  • 매퍼 인터페이스 표시: @Mapper 애노테이션은 특정 인터페이스가 마이바티스 매퍼 인터페이스임을 나타낸다. 이는 마이바티스에게 이 인터페이스의 메소드를 데이터베이스 쿼리와 매핑하기 위한 것임을 알린다.
  • SQL 세션 관리: 마이바티스는 @Mapper로 표시된 인터페이스를 사용하여 내부적으로 SQL 세션을 관리한다. 개발자는 SQL 세션을 직접 열고 닫을 필요 없이, 매퍼 인터페이스를 통해 데이터베이스 작업을 수행할 수 있다.
  • 쿼리 실행 및 결과 매핑: 매퍼 인터페이스의 메소드는 SQL 쿼리나 명령어와 직접 연결된다. 마이바티스는 이 메소드들을 호출할 때 적절한 SQL 쿼리를 실행하고, 결과를 자바 객체로 매핑한다.

 

-void save() 메소드: 새로운 Item 객체를 데이터베이스에 저장한다. 이 작업은 XML 매핑 파일의 <insert> 태그를 통해 구현된다.

-void update() 메소드: 기존의 Item 객체를 업데이트한다. 이 때, @Param 애노테이션을 사용하여 메소드의 파라미터를 SQL 쿼리에 바인딩한다.

-findById() 메소드: 주어진 ID에 해당하는 Item 객체를 찾아 반환한다.

-findAll() 메소드: 조건에 맞는 모든 Item 객체를 찾아 리스트로 반환한다. 이 메소드는 동적 SQL을 사용하여 구현된다.

 

같은 위치에 실행할 SQL이 있는 XML 매핑 파일이 있어야한다.

참고로 자바 코드가 아니기 때문에 스프링 부트에서 src/main/resources 하위에 만들되, 패키지 위치는 맞추어 주어야 한다.

 

@Param

스프링 프레임워크의 일부로, 주로 스프링 데이터 JPA나 마이바티스(MyBatis) 같은 ORM(Object-Relational Mapping) 라이브러리에서 메소드 파라미터를 SQL 쿼리에 바인딩할 때 사용된다.

@Param 어노테이션을 사용하면 메소드 파라미터를 쿼리 내의 명시적인 파라미터로 전달할 수 있으며, 이는 코드의 가독성과 유지보수성을 향상시킨다.

메소드 파라미터를 XML 또는 어노테이션으로 작성된 SQL 쿼리에 바인딩할 수 있다. 마이바티스는 @Param 애노테이션을 통해 여러 파라미터를 쿼리에 전달할 때 특히 유용하다.
@Mapper
public interface UserMapper {
    @Select("SELECT * FROM users WHERE username = #{username} AND age = #{age}")
    User findUserByNameAndAge(@Param("username") String username, @Param("age") int age);
}​
이 예시에서는 @Param 애노테이션을 사용하여 username과 age 두 파라미터를 SQL 쿼리에 바인딩한다.
마이바티스는 이 애노테이션을 통해 메소드 파라미터의 값을 쿼리의 #{username}과 #{age}에 동적으로 삽입한다.

 

 

ItemMapper 인터페이스의 구현체

 

MyBatis를 사용할 때, 일반적으로 인터페이스의 구현체를 직접 작성하지 않는다.
대신, MyBatis가 런타임에 마이바티스의 매퍼 XML 파일이나 어노테이션을 기반으로 자동으로 구현체를 생성한다.

 

매퍼 구현체

  • 마이바티스 스프링 연동 모듈이 만들어주는 temMapper 의 구현체 덕분에 인터페이스 만으로 편리하게 XML 의 데이터를 찾아서 호출할 수 있다.
  • 원래 마이바티스를 사용하려면 더 번잡한 코드를 거쳐야 하는데, 이런 부분을 인터페이스 하나로 매우 깔끔하고 편리하게 사용할 수 있다.
  • 매퍼 구현체는 예외 변환까지 처리해준다. MyBatis에서 발생한 예외를 스프링 예외 추상화인 DataAccessException 에 맞게 변환해서 반환해준다. JdbcTemplate이 제공하는 예외 변환 기능을 여기서도 제공한다고 이해하면 된다.

 

매퍼 구현체 덕분에 마이바티스를 스프링에 편리하게 통합해서 사용할 수 있다.

매퍼 구현체를 사용하면 스프링 예외 추상화도 함께 적용된다.

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

 

 

 

XML 매핑 파일

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

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

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

XML 파일을 원하는 위치에 두고 싶으면 application.properties 에 다음과 같이 설정하면 된다.
mybatis.mapper-locations=classpath:mapper/**/*.xml 이렇게 하면 resources/mapper 를 포함한 그 하위 폴더에 있는 XML을 XML 매핑 파일로 인식한다. 이 경우 파일 이름은 자유롭게 설정해도 된다.

 

 

<insert> 태그

void save(Item item);

 <insert id="save" useGeneratedKeys="true" keyProperty="id">
     insert into item (item_name, price, quantity)
     values (#{itemName}, #{price}, #{quantity})
</insert>


save 메소드에 해당하는 SQL 쿼리를 정의한다. useGeneratedKeys="true"와 keyProperty="id"를 설정함으로써, 데이터베이스에 새로운 레코드가 삽입될 때 생성된 키(예: auto-increment ID)를 Item 객체의 id 필드에 자동으로 할당한다.

 

Insert SQL은 <insert> 를 사용하면 된다.
id 에는 매퍼 인터페이스에 설정한 메서드 이름을 지정하면 된다. 여기서는 메서드 이름이 save() 이므로 save 로 지정하면 된다.

파라미터는 #{} 문법을 사용하면 된다. 그리고 매퍼에서 넘긴 객체의 프로퍼티 이름을 적어주면 된다. #{} 문법을 사용하면PreparedStatement 를 사용한다. JDBC의 ? 를 치환한다 생각하면 된다.

useGeneratedKeys 는 데이터베이스가 키를 생성해 주는 IDENTITY 전략일 때 사용한다. keyProperty 는 생성되는 키의 속성 이름을 지정한다. Insert가 끝나면 item 객체의 id 속성에 생성된 값이 입력된다.

 

 

<update> 태그

import org.apache.ibatis.annotations.Param;
 void update(@Param("id") Long id, @Param("updateParam") ItemUpdateDto updateParam);
 
 <update id="update">
     update item
     set item_name=#{updateParam.itemName},
         price=#{updateParam.price},
         quantity=#{updateParam.quantity}
     where id = #{id}
 </update>


update 메소드의 SQL 쿼리를 정의한다. 이 쿼리는 updateParam 객체의 필드 값을 사용하여 특정 id를 가진 레코드를 업데이트한다.

Update SQL은 <update> 를 사용하면 된다.
여기서는 파라미터가 Long id , ItemUpdateDto updateParam 으로 2개이다. 파라미터가 1개만 있으면 @Param 을 지정하지 않아도 되지만, 파라미터가 2개 이상이면 @Param 으로 이름을 지정해서 파라미터를 구분해야 한다.

 

<select> 태그

Optional<Item> findById(Long id);

 <select id="findById" resultType="Item">
     select id, item_name, price, quantity
     from item
     where id = #{id}
</select>



findById와 findAll 메소드에 해당하는 SQL 쿼리를 정의한다. findById는 단일 Item 객체를 반환하는 반면, findAll은 조건에 따라 여러 Item 객체를 리스트로 반환한다. findAll 메소드에서는 <where> 및 <if> 태그를 사용한 동적 SQL을 통해, itemName이나 maxPrice 같은 조건에 따라 다양한 검색 쿼리를 실행할 수 있다.

 

Select SQL은 <select> 를 사용하면 된다.

 

resultType 은 반환 타입을 명시하면 된다. 여기서는 결과를 Item 객체에 매핑한다.

application.properties 에 mybatis.type-aliases- package=hello.itemservice.domain 속성을 지정한 덕분에 모든 패키지 명을 다 적지는 않아도 된다. 그렇지 않으면 모든 패키지 명을 다 적어야 한다. JdbcTemplate의 BeanPropertyRowMapper 처럼 SELECT SQL의 결과를 편리하게 객체로 바로 변환해준다.

 

mybatis.configuration.map-underscore-to-camel-case=true 속성을 지정하면 언더스코어를 카멜 표기법으로 자동으로 처리해준다. ( item_name → itemName )

 

자바 코드에서 반환 객체가 하나이면 Item , Optional<Item> 과 같이 사용하면 되고, 반환 객체가 하나 이상 이면 컬렉션을 사용하면 된다. 주로 List 를 사용한다.

 

findAll - 동적 SQL

List<Item> findAll(ItemSearchCond itemSearch); 

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

findAll 메소드의 SQL 쿼리에서 와 를 사용하여 조건에 따라 다르게 실행되는 SQL을 구현한다. 예를 들어, itemName 조건이 주어지면 item_name 컬럼에 대해 LIKE 검색을 수행하고, maxPrice 조건이 주어지면 가격에 대한 비교 조건을 적용한다. 

 

이와 같은 동적 SQL은 검색 조건이 유연하게 변경될 수 있는 경우에 매우 유용하다.

 

Mybatis는 <where> , <if> 같은 동적 쿼리 문법을 통해 편리한 동적 쿼리를 지원한다. <if> 는 해당 조건이 만족하면 구문을 추가한다.
<where> 은 적절하게 where 문장을 만들어준다.

  • 예제에서 <if> 가 모두 실패하게 되면 SQL where 를 만들지 않는다.
  • 예제에서 <if> 가 하나라도 성공하면 처음 나타나는 and 를 where 로 변환해준다.

 

XML 특수문자

→and price &lt;= #{maxPrice}

여기에보면 <= 를사용하지않고 &lt;= 를사용한것을확인할수있다.그 이유는 XML에서는 데이터 영역에 <, > 같은 특수문자를 사용할 수 없기 때문이다.

이유는 간단한데, XML에서 TAG가 시작하거나 종료할때 <, >와 같은 특수문자를 사용하기 때문이다. 

  • < : &lt;
  • > : &gt;
  • & : &amp;

 

동적 쿼리

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

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

마이바티스 동적쿼리 메뉴얼 : https://mybatis.org/mybatis-3/ko/dynamic-sql.html

 

마이바티스가 제공하는 최고의 기능이자 마이바티스를 사용하는 이유는 바로 동적 SQL 기능 때문이다.

동적 쿼리를 위해 제공되는 기능은 다음과 같다.

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

 

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

 

trim, 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>
</select>

이 예제의 문제점은 문장을 모두 만족하지 않을 때 발생한다.

모두 만족하지 않게 되면 아래와 같은 SQL이 완성된다. 이는 문법 오류다.

SELECT * FROM BLOG
 WHERE

 

title 만 만족할 때도 문제가 발생한다.

SELECT * FROM BLOG
 WHERE
AND title like ‘someTitle’

 

결국 WHERE 문을 언제 넣어야 할지 상황에 따라서 동적으로 달라지는 문제가 있다. <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> 는 문장이 없으면 where 를 추가하지 않는다. 문장이 있으면 where 를 추가한다. 만약 and 가 먼저 시작 된다면 and 를 지운다.


다음과 같이 trim 이라는 기능으로 사용해도 된다. 이렇게 정의하면 <where> 와 같은 기능을 수행한다.

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

 

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

 

기타 기능

어노테이션으로  SQL 작성

@Select("select id, item_name, price, quantity from item where id=#{id}")
Optional<Item> findById(Long id);
  • @Insert , @Update, @Delete, @Select 기능이 제공된다.
  • 동적 SQL이 해결되지 않으므로 간단한 경우에만 사용한다.

 

문자열 대체(String Substitution)

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

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

 

ORDER BY ${columnName}

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

${} 를 사용하면 SQL 인젝션 공격을 당할 수 있다. 따라서 가급적 사용하면 안된다. 사용하더라도 매우 주의깊게 사용해야 한다.

 

재사용 가능한 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)을 사용하면 된다.

<select id="selectUsers" resultType="User">
  select
    user_id
    user_name
    hashed_password
  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>

 

 

댓글