Mybatis的分页功能很弱,它是基于内存的分页(查出所有记录再按偏移量和limit取结果),在大数据量的情况下这样的分页基本上是没有用的。本文基于插件,通过拦截StatementHandler重写sql语句,实现数据库的物理分页。本文适配的mybatis版本是3.2.2。
准备
为何在StatementHandler拦截
在深入浅出MyBatis-Sqlsession章节介绍了1次sqlsession的完全履行进程,从中可以知道sql的解析是在StatementHandler里完成的,所以为了重写sql需要拦截StatementHandler。
MetaObject简介
在我的实现里大量使用了MetaObject这个对象,因此有必要先介绍下它。MetaObject是Mybatis提供的1个的工具类,通过它包装1个对象后可以获得或设置该对象的本来不可访问的属性(比如那些私有属性)。它有个3个重要方法常常用到:
1) MetaObject
forObject(Object object,ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory)
2) Object
getValue(String name)
3) void
setValue(String name, Object value)
方法1)用于包装对象;方法2)用于获得属性的值(支持OGNL的方法);方法3)用于设置属性的值(支持OGNL的方法);
插件的原理
参见深入浅出Mybatis-插件原理。
有了上面这些基础知识的准备后,就能够我们的主题了。
拦截器签名
-
@Intercepts({@Signature(type =StatementHandler.class, method = "prepare", args ={Connection.class})})
-
publicclass PageInterceptor implementsInterceptor {
-
...
-
}
从签名里可以看出,要拦截的目标类型是StatementHandler(注意:type只能配置成接口类型),拦截的方法是名称为prepare参数为Connection类型的方法。
intercept的实现
-
public Object intercept(Invocation invocation) throws Throwable {
-
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
-
MetaObject metaStatementHandler = MetaObject.forObject(statementHandler,
-
DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY);
-
-
-
while (metaStatementHandler.hasGetter("h")) {
-
Object object = metaStatementHandler.getValue("h");
-
metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY,
-
DEFAULT_OBJECT_WRAPPER_FACTORY);
-
}
-
-
while (metaStatementHandler.hasGetter("target")) {
-
Object object = metaStatementHandler.getValue("target");
-
metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY,
-
DEFAULT_OBJECT_WRAPPER_FACTORY);
-
}
-
Configuration configuration = (Configuration) metaStatementHandler.
-
getValue("delegate.configuration");
-
dialect = configuration.getVariables().getProperty("dialect");
-
if (null == dialect || "".equals(dialect)) {
-
logger.warn("Property dialect is not setted,use default 'mysql' ");
-
dialect = defaultDialect;
-
}
-
pageSqlId = configuration.getVariables().getProperty("pageSqlId");
-
if (null == pageSqlId || "".equals(pageSqlId)) {
-
logger.warn("Property pageSqlId is not setted,use default '.*Page$' ");
-
pageSqlId = defaultPageSqlId;
-
}
-
MappedStatement mappedStatement = (MappedStatement)
-
metaStatementHandler.getValue("delegate.mappedStatement");
-
-
-
if (mappedStatement.getId().matches(pageSqlId)) {
-
BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
-
Object parameterObject = boundSql.getParameterObject();
-
if (parameterObject == null) {
-
throw new NullPointerException("parameterObject is null!");
-
} else {
-
-
PageParameter page = (PageParameter) metaStatementHandler
-
.getValue("delegate.boundSql.parameterObject.page");
-
String sql = boundSql.getSql();
-
-
String pageSql = buildPageSql(sql, page);
-
metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);
-
-
metaStatementHandler.setValue("delegate.rowBounds.offset",
-
RowBounds.NO_ROW_OFFSET);
-
metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT);
-
Connection connection = (Connection) invocation.getArgs()[0];
-