본문 바로가기

Database/Mybatis

[Mybatis] Mybatis interceptor 활용하기

Mybatis로 DB mapper를 수행하는데 interceptor를 등록하면 DB 쿼리 실행 전후로 등록한 interceptor를 수행해서 사전 작업을 할 수 있다.

 

예를 들어 조회 쿼리 수행시 Paging 처리를 일괄적으로 interceptor에서 할 수 도 있고 Mybatis 쿼리 인자로 세션 정보를 전역에서 설정해서 모든 쿼리마다 해당 세션 값을 넣을 필요 없이 만들 수도 있다.

 

Mybatis interceptor 설정 방법

 

mybatis-config.xml 생성

 

아래와 같이 mybatis-config.xml 설정 파일을 생성한다. 기존에 이미 사용중이라면 plugins 밑에 interceptor class를 등록해준다.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <setting name="mapUnderscoreToCamelCase" value="true" />
        <setting name="callSettersOnNulls" value="true" />
        <setting name="jdbcTypeForNull" value="NULL" />
    </settings>
    <typeAliases>
        <typeAlias alias="sql" type="java.util.HashMap"/>
    </typeAliases>
    <typeHandlers>
        <typeHandler javaType="java.lang.String" jdbcType="CLOB" handler="org.apache.ibatis.type.ClobTypeHandler" />
    </typeHandlers>
    <plugins>
        <plugin interceptor="com.sdp.common.interceptor.MybatisExecuteInterceptor"/>
    </plugins>
</configuration>

 

Interceptor Class 정의

 

Interceptor Class를 만들기 위해서 ibatis 패키지의 Interceptor 인터페이스를 구현하고 @Intercepts, @Signature 어노테이션을 붙여준다.

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.util.Properties;

@Slf4j
@Intercepts({
    @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
    @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})
})
public class MybatisExecuteInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {

    }
}

 

@Signature 에는 Mybatis 에서 제공하는 Executor와 Intercept 하고자 하는 메소드 명을 명시 한다. 아래 Interface 들이 라이브러리에서 제공하는 메소드 들이다. 메소드 명에 맞게 SQL수행전에 멈출수도 있고 수행 후 ResultSet을 확인할 수 도 있고 메소드 동작에 따라 Intercept 메소드 전달 인자와 동작 시점이 다르다.

 

package org.apache.ibatis.executor.resultset;

...

public interface ResultSetHandler {
    <E> List<E> handleResultSets(Statement var1) throws SQLException;

    <E> Cursor<E> handleCursorResultSets(Statement var1) throws SQLException;

    void handleOutputParameters(CallableStatement var1) throws SQLException;
}
package org.apache.ibatis.executor.statement;

...

public interface StatementHandler {
    Statement prepare(Connection var1, Integer var2) throws SQLException;

    void parameterize(Statement var1) throws SQLException;

    void batch(Statement var1) throws SQLException;

    int update(Statement var1) throws SQLException;

    <E> List<E> query(Statement var1, ResultHandler var2) throws SQLException;

    <E> Cursor<E> queryCursor(Statement var1) throws SQLException;

    BoundSql getBoundSql();

    ParameterHandler getParameterHandler();
}
package org.apache.ibatis.executor;

...

public interface Executor {
    ResultHandler NO_RESULT_HANDLER = null;

    int update(MappedStatement var1, Object var2) throws SQLException;

    <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4, CacheKey var5, BoundSql var6) throws SQLException;

    <E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;

    <E> Cursor<E> queryCursor(MappedStatement var1, Object var2, RowBounds var3) throws SQLException;

    List<BatchResult> flushStatements() throws SQLException;

    void commit(boolean var1) throws SQLException;

    void rollback(boolean var1) throws SQLException;

    CacheKey createCacheKey(MappedStatement var1, Object var2, RowBounds var3, BoundSql var4);

    boolean isCached(MappedStatement var1, CacheKey var2);

    void clearLocalCache();

    void deferLoad(MappedStatement var1, MetaObject var2, String var3, CacheKey var4, Class<?> var5);

    Transaction getTransaction();

    void close(boolean var1);

    boolean isClosed();

    void setExecutorWrapper(Executor var1);
}

 

Interceptor Class 생성 시 오버라이드 되는 메소드에서 주의할 것은 아래 와 같이 Proceed 와  Object.wrap을 꼭 선언해주어야 한다. Null을 리턴하면 바로 NullPointException으로 죽어버린다.

 

public class MybatisExecuteInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    
    	// 필요한 로직 작성 후
        // 아래와 같이 invocation.proceed()로 다음 동작으로 진행시킨다.
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
    
    	// 아래와 같이 plugin에 등록한다.
        return Plugin.wrap(target, this);
    }

 

물론, 위 mybatis-config.xml을 따로 파일로 작성하였다면 SqlSessionFactory에서 setConfigLocation으로 해당 파일 위치를 등록해주는것을 잊지 말자.

 

@Configuration
@MapperScan(basePackages = {"com.sdp.common", "com.sdp.mapper"}, sqlSessionFactoryRef="sdpSqlSessionFactory" )
public class SdpDataSourceConfig {

   @Value("${spring.mybatis.common.config}")
   private String mybatisConfig;

   @Value("${spring.mybatis.common.mapper}")
   private String mybatisMapper;

   @Autowired
   ApplicationContext applicationContext;

   @Bean(name="dataSource", destroyMethod="close")
   @Primary
   @ConfigurationProperties(prefix="spring.sdp.datasource")
   public DataSource sdpDataSource() {
      return DataSourceBuilder.create().build();
   }

   @Bean(name="sdpSqlSessionFactory")
   @Primary
   public SqlSessionFactory sdpSqlSessionFacotry(@Qualifier("dataSource") DataSource sdpDataSource) throws Exception {
      SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
      sqlSessionFactoryBean.setDataSource(sdpDataSource);
      sqlSessionFactoryBean.setConfigLocation(applicationContext.getResource(mybatisConfig));
      sqlSessionFactoryBean.setMapperLocations(applicationContext.getResources(mybatisMapper));

      return sqlSessionFactoryBean.getObject();
   }

   @Bean
   @Primary
   public SqlSessionTemplate sdpSqlSessionTemplate(SqlSessionFactory sdpSqlSessionFactory) throws Exception {
      SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sdpSqlSessionFactory);
      sqlSessionTemplate.getConfiguration().setMapUnderscoreToCamelCase(true);

      return sqlSessionTemplate;
   }
}

 

-- The End --