안개짱
363
2018-07-11 14:03:53 작성 2018-07-11 19:37:17 수정됨
0
516

Mybatis interceptor를 이용한 페이징 처리 Tips


어느날 xml 파일에 있는 sql 문들을 보며 페이징 쿼리때문에 원 쿼리가 눈에 잘 안들어와서 좋은 방법이 없을까 고민하다. 만들어봤습니다.


package kr.pe.withwind.spring.interceptor;

import java.sql.Connection;
import java.util.Properties;

import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.factory.DefaultObjectFactory;
import org.apache.ibatis.reflection.factory.ObjectFactory;
import org.apache.ibatis.reflection.wrapper.DefaultObjectWrapperFactory;
import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Mybatis Interceptor를 이용하여
 * 쿼리 수행시에 RowBound를 셋팅 할 경우 해당 쿼리를 각각의 DB에 맞도록 Paging 쿼리로 바꾸어 준다.
 * 현재는 Oracle과 Mysql이 셋팅되어 있다.
 * @author phpinclude
 *
 */
@Intercepts({
	 @Signature(type = StatementHandler.class, method = "prepare", args= {Connection.class})
})
public class MybatisInterCeptor implements Interceptor {

	private static final Logger logger = LoggerFactory.getLogger(MybatisInterCeptor.class);
	
	private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
	private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
	
	
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		
		logger.debug("intercept call!!");
		
		Connection conn = (Connection) invocation.getArgs()[0];
		String dbName = conn.getMetaData().getDatabaseProductName().toUpperCase();
		
		logger.debug("dbName : " + dbName);
		
		StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
		
		MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
		String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
		
		RowBounds rb = (RowBounds) metaStatementHandler.getValue("delegate.rowBounds");
		
		if (rb == null || rb == RowBounds.DEFAULT) return invocation.proceed();
		
		logger.debug("rb.getOffset() : "  + rb.getOffset());
		logger.debug("rb.getLimit() : "  + rb.getLimit());

		if (dbName.contains("MYSQL")){
			
			StringBuilder sb = new StringBuilder(originalSql);
			sb.append(" limit ");
			sb.append(rb.getOffset());
			sb.append(',');
			sb.append(rb.getLimit());
			
			metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
	        metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
			metaStatementHandler.setValue("delegate.boundSql.sql", sb.toString());
			
		}else if (dbName.contains("ORACLE")){
			
			StringBuilder sb = new StringBuilder();
			sb.append("SELECT * FROM ( ");
			sb.append("SELECT ROWNUM AS RNUM, WT.* FROM ( ");
			sb.append(originalSql);
			sb.append(") WT WHERE ROWNUM <= " + (rb.getOffset() + rb.getLimit()));
			sb.append(") WHERE RNUM > " + rb.getOffset());
			
			metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET);
	        metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
			metaStatementHandler.setValue("delegate.boundSql.sql", sb.toString());
		}
		
		return invocation.proceed();
	}
	
	

	@Override
	public Object plugin(Object target) {
		logger.debug("plugin call!! " + target.getClass().getName());
		return Plugin.wrap(target, this);
	}

	@Override
	public void setProperties(Properties properties) {
		logger.debug("setProperties call!!");
	}
}

<Object> List<Object> org.apache.ibatis.session.SqlSession.selectList(String statement, Object parameter, RowBounds rowBounds);

위 함수를 사용하시면 됩니다.

기본적으로는 위 함수를 사용하면 db에서 전체 레코드를 가져온다음에 mybatis에서 해당 구간만 가져다 반환 합니다.
당연히 db와 app간 데이터 전송량 많아지고 db에서의 쿼리시간도 오래걸립니다.


아시는 분도 계시겠지만 모르시는 분도 계실듯 하여 올려봅니다.


아래는 mybatis 셋팅 입니다. plugin 부분만 보시면 될듯 합니다.

<?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>
	
	<plugins>
		<plugin interceptor="kr.pe.withwind.spring.interceptor.MybatisInterCeptor">
		</plugin>
	</plugins>
	
	<mappers>
		<package name="kr.pe.withwind.spring.mapper" />
	</mappers>
</configuration>

mybatis 버전별로 조금 틀렸던것 같은데.. 보시면 아실듯 합니다.



PS. 쓰고보니 참 글이 두서가 없네요.. 찰떡같이 알아 보시리라 생각해 봅니다.
1
0
  • 댓글 0

  • 로그인을 하시면 댓글을 등록할 수 있습니다.