https://github.com/antop-dev/example/tree/master/mybatis-rowbounds-example
Mybatis에 RowBounds 클래스를 이용해서 페이징 처리를 할 수 있다.
package org.antop.mybatis.mapper;
import org.antop.mybatis.model.Employee;
import org.apache.ibatis.session.RowBounds;
import java.util.List;
public interface EmployeeMapper {
List<Employee> select(RowBounds rowBounds);
}
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.antop.mybatis.mapper.EmployeeMapper">
<resultMap id="BaseResultMap" type="org.antop.mybatis.model.Employee">
<id property="no" column="emp_no" />
<result property="gender" column="gender" typeHandler="org.antop.mybatis.handler.GenderTypeHandler" />
<association property="name" javaType="org.antop.mybatis.model.Name">
<result property="first" column="first_name" />
<result property="last" column="last_name" />
</association>
</resultMap>
<select id="select" resultMap="BaseResultMap">
select
*
from
employees
order by
emp_no asc
</select>
</mapper>
위와 같이 맵퍼와 XML 이 있다.
RowBounds rowBounds = new RowBounds(5, 10);
// 15건을 가져와서 앞에 5건 건너띔
List select = mapper.select(rowBounds);
결과는 10건이 나왔지만 어떻게 동작할까?
여기저기 찾아보고 소스도 대충(?) 보면 offset + limit 만큼 가져와서 offset 만큼 건너띤다.
페이징이 뒤로 갈 수록 느려지게 된다.
RowBounds rowBounds = new RowBounds(29990, 10);
// 30000건을 가져와서 앞에 29990건 건너띔
List select = mapper.select(rowBounds);
데이터의 양이 적다면 쿼리에서 페이징을 하지 않고 RowBouns를 이용하면 빠르게 개발할 수 있을 것이다.
하긴 요즘 수천만건의 데이터를 페이징에서 마지막 페이지를 볼 일이 많을까? -_-/
XML 쿼리문에 페이징을 넣지 않고 RowBounds 클래스를 사용하면 자동으로 페이징 쿼리가 실행되게 할 수 없을까?
방법이 있다! Mybatis Intercepter를 이용하면 된다. (인터셉터의 자세한 사용법은 다루지 않겠다 ㅠㅠ)
- Mybatis에서 쿼리를 날리기 전에 가로챈다.
- RowBounds가 있으면 쿼리에 페이징 문장를 적용한다.
- RowBounds를 제거하여 Mybatis에서 페이징 처리를 하지 않도록 한다.
아래 소스는 쿼리에 MySQL용으로 limit 문을 붙여준다.
package org.antop.mybatis.intercepter;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.DefaultReflectorFactory;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.ReflectorFactory;
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;
import java.sql.Connection;
import java.util.Properties;
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class MysqlRowBoundsInterceptor implements Interceptor {
private static final Logger logger = LoggerFactory.getLogger(MysqlRowBoundsInterceptor.class);
private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
private static final ReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);
String originalSql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
RowBounds rb = (RowBounds) metaStatementHandler.getValue("delegate.rowBounds");
logger.debug("originalSql = {}", originalSql);
logger.debug("RowBounds = {}", rb);
if (rb == null || rb == RowBounds.DEFAULT) { // RowBounds가 없으면 그냥 실행
return invocation.proceed();
}
// RowBounds가 있다!
// 원래 쿼리에 limit 문을 붙여준다.
StringBuffer sb = new StringBuffer();
sb.append(originalSql);
sb.append(" limit ");
sb.append(rb.getOffset());
sb.append(", ");
sb.append(rb.getLimit());
logger.debug("sql = {}", sb.toString());
// RowBounds 정보 제거
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();
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
}
}
SqlSessionFactory 설정하는 부분에 인터셉터를 적용하면 된다.
<?xml version="1.0" encoding="UTF-8"?>
<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="org.antop.mybatis"/>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="mapperLocations" value="classpath*:mybatis/*.xml"/>
<property name="plugins">
<list>
<!-- 인터셉터 설정 -->
<bean class="org.antop.mybatis.intercepter.MysqlRowBoundsInterceptor"/>
</list>
</property>
</bean>
<bean id="dataSource" ... />
</beans>
실행시켜 보면 쿼리문에 페이징이 적용되서 쿼리가 날라가는 것을 확인할 수 있다.
select * from employees order by emp_no asc limit 29990, 10
출처