Mybatis自定义轻量级分页组件(易集成,易拓展)
其实github有一个叫做PageHelper的开源分页组件,我也用过,封装的还可以。只是感觉他的量级偏重,其实很多参数,都是我们开发中不需要的参数,而且它的获取分页信息方式是通过构造方法,不是很优雅。所以我在查阅完他的源码后,结合自己的需求,实现了一个轻量级,便携式的分页组件SmallPage。
SmallPage
轻量级分页组件,基于入参类型
核心依赖
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
功能描述
- 自动拦截进行分页
- 基于Dao层方法入参类型
- 自动count总条数
核心类 - PageInterceptorPlugin
- 使用方式,只需将Dao查询方法的入参Bean继承于PageWrapperBean,设定Page对象则可以实现分页,分页完成后的信息也保存在Page对象中
- 入参类型可以parameterType 定义,也可以@Param("")传入,多个入参时,只要其中一个入参类型为PageWrapperBean的上转类型,就可以实现分页
- 入参类型不为PageWrapperBean的上转类型时,直接推进拦截链
实现方式
现在java的持久层框架大多基于Mybatis的框架,所以实现分页的思路,也就拦截待执行的sql进行追加Limt限定,来实现分页。
/**
* @author machenike
*/
@Intercepts(@Signature(type = StatementHandler.class,method = "prepare",args={Connection.class,Integer.class}))
public class PageInterceptorPlugin implements Interceptor {
private static final String META_OBJECT_KEY_SQL_ID = "delegate.mappedStatement.id";
private static final String META_OBJECT_KEY_BOUND_SQL = "delegate.boundSql.sql";
private static final Logger logger = LoggerFactory.getLogger(PageInterceptorPlugin.class);
@Override
public Object intercept(Invocation invocation) throws Throwable {
//获取命令handler取得
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
//获取入参handler
ParameterHandler parameterHandler = statementHandler.getParameterHandler();
//获取参数对象
Object parameterObject = parameterHandler.getParameterObject();
PageBean pageBean = getPageBean(parameterObject);
ThreadLocal threadLocal = new ThreadLocal();
if(pageBean!=null) {
logger.debug("parameter type match,start intercept");
//获取meta对象取得
MetaObject metaObject = MetaObject.forObject(
statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY,SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY,
new DefaultReflectorFactory());
//获取当前sqlId
String sqlId = (String)metaObject.getValue(META_OBJECT_KEY_SQL_ID);
//原来应该执行的sql吧
String sql = statementHandler.getBoundSql().getSql();
logger.debug("-originSql:" + sql);
Connection connection = (Connection) invocation.getArgs()[0];
String countSql = bulidCountSql(sql);
logger.debug("-countSql:" + countSql);
//渲染参数
PreparedStatement preparedStatement = connection.prepareStatement(countSql);
//条件交给mybatis
parameterHandler.setParameters(preparedStatement);
//让mybatis执行这个sql
ResultSet resultSet = preparedStatement.executeQuery();
int count = 0;
if (resultSet.next()) {
count = resultSet.getInt(1);
}
resultSet.close();
preparedStatement.close();
//limit 1 ,10 十条数据 总共可能有100 count 要的是 后面的100
pageBean.setTotal(count);
//拼接分页语句(limit) 并且修改mysql本该执行的语句
String pageSql = buildPageSql(pageBean, sql);
logger.debug("-pageSql:" + pageSql);
//重新绑定分页sql
metaObject.setValue(META_OBJECT_KEY_BOUND_SQL, pageSql);
}
//推进拦截器调用链
return invocation.proceed();
}
/**
* 入参对象中取得分页对象
* @param o
* @return
*/
private PageBean getPageBean(Object o){
PageWrapperBean pageWrapperBean = null;
PageBean pageBean = null;
//类型判断
//入参为Map 上转类型
if(o instanceof Map){
//迭代Map
Map<String,Object> paramMap = (Map<String, Object>) o;
Set<String> keySet = paramMap.keySet();
for(String key:keySet){
Object valueObject = paramMap.get(key);
if(valueObject instanceof PageWrapperBean){
pageWrapperBean = (PageWrapperBean) valueObject;
break;
}
}
//入参为 PageWrapperBean 上转类型
} else if(o instanceof PageWrapperBean){
pageWrapperBean = (PageWrapperBean) o;
}
pageBean = pageWrapperBean.getPage();
return pageBean;
}
/**
* 构建记录条数countSql
* @param originSql
* @return
*/
private String bulidCountSql(String originSql){
//优化一下就是讲select 到from 之间的字符串替换为 count(1) 即可,这里仅为了方便
String countSql = "select count(1) from ("+originSql+") a";
return countSql;
}
/**
* 构建分页sql
* @param pageBean
* @param originSql
* @return
*/
private String buildPageSql(PageBean pageBean,String originSql){
//拼接分页语句(limit) 并且修改mysql本该执行的语句
String pageSql = originSql+" limit "+pageBean.getStart()+","+pageBean.getLimit();
return pageSql;
}
}
所有的分页都是PageInterceptorPlugin,因为传入的分页信息都会封装到pageWrapperBean 当中的page对象中,同时page对象也是前端传输分页信息。本来就请求的当前线程所持有,完全不用考虑线程安全,不需要采用PageHelper中ThreadLocal去保存分页信息。
拦截StatementHandler中从StatementHandler取出原始的查询sql,通过ParameterHandler 获取分页参数。
然后将原始的sql通过上述代码中buildPageSql方法进行构建分页sql
/**
* 自动运算相关数值
*/
public void autoCount(){
if(limit!=null&&limit>0&&total!=null){
pageCount = total/limit+1;
}
if(limit!=null&&limit>0&¤tPage>=1) {
start = (currentPage - 1) * limit;
}
}
我将这个方法放入了,page对象的set方法中,一旦有分页参数变化,分页信息就会有所变化。
satrt的起始的索引的运算公式就是
index = (currentPage - 1) * limit
当前页-1*limit
获取记录总条数的sql通上述的bulidCountSql去构建。
“select count(1) from (”+originSql+") a";
最后使用preparedStatement这个命令对象完成查询,将返回的分页参数,设置到page对象中。
最后在外部使用getPage()方法既可以拿到所有的分页信息。
使用方式
入参对象使用类继承的方式
public class TestEntity extends PageWrapperBean {
private Integer id;
private String text;
}
分页使用方式
TestEntity testEntity = new TestEntity();
PageBean pageBean = new PageBean();
pageBean.setCurrentPage(1);
pageBean.setLimit(10);
//设定分页参数
testEntity.setPage(pageBean);
//开始查询
testMapper.select(testEntity);
//取得分页信息
PageBean pageInfo = testEntity.getPage();
入参对象使用@Param 这种Map形式的
List<TestEntity> query(@Param("page")PageWrapperBean pageWrapperBean);
分页使用方式
PageBean pageBean = new PageBean();
pageBean.setCurrentPage(1);
pageBean.setLimit(10);
//设定分页参数
PageWrapperBean pageWrapperBean = new PageWrapperBean(pageBean);
//开始查询
testMapper.query(pageWrapperBean);
//取得分页信息
PageBean pageInfo = pageWrapperBean.getPage();
}
最后附上源码地址,后续还继续优化更新
https://github.com/DavidLei08/SmallPage