分页是WEB程序中常见的功能,mybatis分页实现与hibernate不同,相比hibernate,mybatis实现分页更为麻烦。mybatis实现分页需要自己编写(非逻辑分页RowBounds),以mysql为例,使用分页时需要自己在mapper中的sql语句中添加LIMIT #{xx},#{xx}。这种方式不具备可重用性与可拓展性。
mybatis提供了plugins,plugins实现了拦截器的功能。它可以拦截指定类中的方法,当指定的方法被执行时,mybatis就会自动拦截并完成相应逻辑。通过mybatis的plugins,就可以实现自动分页功能。
实现分析:拦截查询语句,判断该查询语句是否需要添加分页,修改原来的sql语句,查询得到的总记录数
具体源码在https://github.com/yuen666/mybatis-pagination
简要思路
编写Interceptor,拦截StatementHandler中的prapare方法,在mybatis中,执行的是RoutingStatementHandler实现类。该类使用代理的方式来调用BaseStatementHandler。
在每次使用查询语句时,都会调用RoutingStatementHandler.prepare(...),该方法的实现如下:
它调用了delegate的prepare,实现类为BaseStatementHandler,源码如下:
由于每次查询都调用该prepare方法,故可以对RoutingStatementHandler的prepare进行拦截。该方法中拥有connection对象,通过connection对象也可以连接数据库查询总记录数,比较方便。
关于sql语句的修改。在delegate中,即BaseStatementHandler中,拥有如下几个域:
其中,boundSql中包含了sql语句(boundSql.sql),由于是protected类型,所以可以采用反射的方式获取原sql语句并设置新sql语句。
总结下:
- 拦截StatementHandler的prepare方法
- 通过反射获取delegate.boundSql.sql修改并设置
- 获取参数connection并查询中记录数
关于反射
mybaits提供了MetaObject类,该类对反射进行封装。可以通过该类快速的实现获取与设置delegate.boundSql.sql。
private Object getValue(StatementHandler statementHandler, String exp) { MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory()); return metaObject.getValue(exp); }
private void setValue(StatementHandler statementHandler, String exp, Object obj) { MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, new DefaultReflectorFactory()); metaObject.setValue(exp, obj); }
其中的exp为OGNL表达式。