[컴][웹] MyBatis-Spring 사용법

MyBatis 세팅

Spring 에서 MyBatis setting 을 하는데 자꾸 error 가 난다. 계속 급한 마음에 설정을 제대로 잡을 수 없어서 오늘은 제대로 MyBatis 를 알아보려 한다.

여기 나온글은 대부분 아래 글에서 나온 이야기가 될 것이다.

MyBatis-Spring

MyBatis 를 Spring 에서 사용할 수 있도록 해주는 것이 MyBatis-Spring 이다. 이 MyBatis-Spring 을 이용하면,

  1. MyBatis mapper 들과 SqlSession 들을 다루고,
  2. 다른 bean 에 inject 해서 다른 bean 에서 사용할 수 있도록 도와주고,
  3. MyBatis 의 Exception 들에 대해서 Spring 의 DataAccessException 을 발생시켜 준다.

원래 MyBatis 는 xml 을 가지고 sql 을 작성하고 그것을 code 에서 사용할 수 있도록 되어 있다. 그런데 이 것을 spring 에서 사용할 수 있도록 (bean 을 만들고, inject 를 하는 등의)wrapper 를 만든 것이 MyBatis-Spring 이라고 할 수 있겠다.

MyBatis 가 어떤 모습을 띠는 지는 아래 페이지를 참고하자.

Installation

Spring 에서 MyBatis 를 이용할 수 있도록 해주는 것이 MyBatis-Spring 인데, 이 녀석을 사용하려면 아래와 같은 녀석들이 있어야 한다.

  • Java 5 이상
  • MyBatis-Spring 의 버전과 맞는 버전의 MyBatis 와 Spring

버전에 대한 정보는 아래 페이지를 참고하자.

이것을 Spring 에서 사용한다면, project 의 build path 에 아래의 library 들을 추가해야 한다.

  • mybatis-spring-x.x.x.jar
  • mybatis-spring 과 dependency 가 있는 library

이것은 개인적으로 번거럽다. maven 설정은 dependency 와 관련된 library 를 해결해 줘서 좋다.

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>1.1.1</version>
</dependency>

그런데 아래에서 mapper 를 자동으로 scan 하기 위해서 mybatis-spring 1.2.2 를 사용했는데, 이때는 maven 에서 자동으로 mybatis 를 가져오지 못하는 듯 했다. 그래서 아래처럼 mybatis 설정으로 추가해 줬다.

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.2.7</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>1.2.2</version>
</dependency>

Spring 에서 MyBatis 사용하기

MyBatis 를 Spring 에서 사용하기 위해서는 Spring Application Context 에 2가지를 정의해야 한다.

  • SqlSessionFactory
  • 1개 이상의 mapper interface

SqlSessionFactory 생성

이것을 MyBatis-Spring 에서는 SqlSessionFactoryBean 을 이용해서 SqlSessionFactory 를 만들 수 있다. 이녀석은 Spring 설정 file 인 .xml 에서 정의해 주면 된다.

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource" />
</bean>

위에서 보다시피 dataSource 가 필요하다. 이 dataSource 는 Spring 에서 사용하던 DataSource 를 이야기 한다. 그리고 원래 Spring 에서 DataSource 를 사용하기 위해서 하는 설정은 해줘야 한다. 예를 들어 내가 사용하던 dataSource 는 아래와 같은 모습이다.

<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="org.springframework.jdbc.datasource.DriverManagerDataSource" />
        <property name="url" value="${jdbc.url}" />
        <property name="username" value="${jdbc.username}" />
        <property name="password" value="${jdbc.password}" />
    </bean>

1개 이상의 mapper interface

아래와 같은 mapper 를 가지고 있다고 해보자.

public interface UserMapper {
  @Select("SELECT * FROM users WHERE id = #{userId}")
  User getUser(@Param("userId") String userId);
} 

이녀석을 사용하려면 MapperFactoryBean 을 사용하면 된다.

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="my.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

scanning mapper

이 방법대신에 자동으로 mapper 를 scan 할 수 있다. 위의 MapperFactoryBean 설정대신에 간단히 아래 설정을 해주면 된다.

<mybatis:scan/> 와 @MapperScan 은 MyBatis-Spring 1.2.0. 부터 가능하고 @MapperScan 을 사용하려면 Spring 3.1+ 을 사용해야 한다. (참고)

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"

    xsi:schemaLocation="http://www.springframework.org/schema/beans 
     http://www.springframework.org/schema/beans/spring-beans.xsd
     http://mybatis.org/schema/mybatis-spring
     http://mybatis.org/schema/mybatis-spring.xsd
     ">
<mybatis:scan base-package="my.sample.mapper" />

mapper class

위의 code 에서 보다시피 Mapper class 는 반드시 interface 여야 한다. 그리고 sql 문은 annotation 으로 사용하면 된다. 이것 대신에 MyBatis 의 xml 방법을 사용해도 된다.

위의 <bean> 설정으로 만들어진 bean 은 Spring 의 다른 bean 들 처럼 inject 할 수 있다. MapperFactoryBean 은 SqlSession 을 만드는 것과 closing 을 처리 해 준다. Spring transaction 이 진행중이면, SqlSession 은 transaction 이 끝났을 때 commit 을 해주거나 roll back 을 해준다.

주의할 점

내가 이전에 설정에서 계속 실패했던 이유는 MyBatis 와 MyBatis-Spring 을 같은 것이라고 여겼기 때문이다. 그래서 MyBatis 설정 + MyBatis-Spring 설정이 함께 들어가서 그것들이 엉킨듯 하다. 위에서 이야기한 대로 설정을 다시 잡아줬더니 잘 동작한다.

code examples

dynamic sql 에서 아래처럼 여러줄을 사용해서 사용하려면, <script> 를 사용하면 된다.

@Select({"<script>",
         "SELECT *", 
         "FROM blog",
         "WHERE id IN", 
           "<foreach item='item' index='index' collection='list'",
             "open='(' separator=',' close=')'>",
             "#{item}",
           "</foreach>",
         "</script>"}) 
List<Blog> selectBlogs(@Param("list") int[] ids);

kotlin 으로 작성한 mybatis java api 예제

package com.mycom.repository.product2

import org.apache.ibatis.annotations.Insert
import org.apache.ibatis.annotations.Param
import org.apache.ibatis.annotations.Select
import org.apache.ibatis.annotations.SelectProvider
import org.apache.ibatis.builder.annotation.ProviderMethodResolver
import org.apache.ibatis.jdbc.SQL
import org.springframework.stereotype.Repository
import com.mycom.model.Customer


@Repository
interface PurchaseDataSyncRepository {

    // java api
    // @ref https://mybatis.org/mybatis-3/java-api.html
    //
    // type-handler
    // `mybatis.type-handlers-package=com.mycom.converter.mybatis,young.common.converter.mybatis`
    // @see application.properties
    @Select("SELECT id FROM customer AS t1 ORDER BY t1.id DESC LIMIT 1")
    fun getTheLastCustomerId(): Long?

    @Insert(
        "INSERT INTO customer ",
        "(id,name,business_no,",
        "updated_at,created_at)",
        "VALUES (",
        "#{customer.id}, #{customer.name}, #{customer.businessNo}, ",
        "#{customer.updatedAt}, #{customer.createdAt}",
        ")",
    )
    fun insertCustomer(
        @Param("customer") customer: Customer,
    ): Int?


    // @ref: https://mybatis.org/mybatis-3/java-api.html
    @SelectProvider(
        type = UserSqls::class, method = "getUserIdsFromEmailsQuery"
    )
    fun getUserIdsFromEmails(@Param("emails") emails: List<String>): List<HashMap<String, Long>>?


}

class UserSqls {

    // @ref: https://mybatis.org/mybatis-3/statement-builders.html
    fun getUserIdsFromEmailsQuery(emails: List<String>): String {

        // ["myuser1@mycom.com","myuser2@mycom.com",...]
        // --> ('myuser1@mycom.com','myuser2@mycom.com',...)
        val emailsCond = emails.joinToString(
            prefix = "('",
            separator = "','",
            postfix = "')",
        )
        return object : SQL() {
            init {
                SELECT("t1.email, t1.id")
                FROM("user AS t1")
                WHERE("t1.email IN $emailsCond")
            }
        }.toString()
    }

}

상속(inheretance)이용

아래처럼 SQL 을 상속(extend) 해서 사용할 수 있다.

///
package young.pa.persistence.repository.product2.querybuilder

import org.apache.ibatis.jdbc.SQL

open class MySql : SQL() {
    // your custom functions or overrides here
    fun buildSelect(): SQL {
        return this.SELECT("t1.email, t1.id")
    }
}

///

class UserSqls {

    fun getUserIdsFromEmailsQuery(emails: List<String>): String {

        val emailsCond = emails.joinToString(
            prefix = "('",
            separator = "','",
            postfix = "')",
        )
        return object : MySql() {
            init {
                buildSelect()
                FROM("user AS t1")
                WHERE("t1.email IN $emailsCond")
            }
        }.toString()
    }

}

Source code

위에서 설명한 내용을 직접 적용한 소스는 아래에서 다운로드 할 수 있다.

댓글 없음:

댓글 쓰기