详解Mybatis极其(最)简(好)单(用)的一个分页插件
注意:这篇博客已经和当前的分页插件完全不一样了,所以建议大家通过上面项目地址查看最新的源码和文档来了解。
以前为Mybatis分页查询发愁过,而且在网上搜过很多相关的文章,最后一个都没采用。在分页的地方完全都是手写分页SQL和count的sql,总之很麻烦。
后来有一段时间想从Mybatis内部写一个分页的实现,我对LanguageDriver写过一个实现,自动分页是没问题了,但是查询总数(count)仍然没法一次性解决,最后不了了之。
最近又要用到分页,为了方便必须地写个通用的分页类,因此又再次参考网上大多数的Mybatis分页代码。
实际上在很早之前,有人在github上开源过一个实现,支持MySQL,Oracle,sqlserver的,和上面这个参考的比较类似,考虑的更全面。但是我觉得太多类太麻烦了,所以自己实现了一个只有一个拦截器的类,实际上可以分为两个类,其中一个类被我写成静态类放在了拦截器中,你也可以将Page类提取出来,方便使用Page。
先说实现方法,该插件只有一个类:PageHelper.Java
拦截器签名为:
@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class}),
@Signature(type=ResultSetHandler.class,method="handleResultSets",args={Statement.class})})
这里的签名对整个实现和思想至关重要,首先我拦截prepare方法来改分页SQL,来做count查询。然后我拦截handleResultSets方法来获取最后的处理结果,将结果放到Page对象中。
下面是修改分页的代码,是针对Oracle数据进行的修改,如果有用其他数据库的,自己修改这里的代码就可以。
/**
*修改原SQL为分页SQL
*@paramsql
*@parampage
*@return
*/
privateStringbuildPageSql(Stringsql,Pagepage){
StringBuilderpageSql=newStringBuilder(200);
pageSql.append("select*from(selecttemp.*,rownumrow_idfrom(");
pageSql.append(sql);
pageSql.append(")tempwhererownum<=").append(page.getEndRow());
pageSql.append(")whererow_id>").append(page.getStartRow());
returnpageSql.toString();
}
之后在下面的setPageParameter方法中一个selelctcount语句,这里也需要根据数据库类型进行修改:
//记录总记录数
StringcountSql="selectcount(0)from("+sql+")";
为什么我不提供对各种数据库的支持呢,我觉得没必要,还有些数据库不支持分页,而且这个插件越简单对使用的开发人员来说越容易理解,越容易修改。修改成自己需要的分页查询肯定不是问题。
最后上完整代码(继续看下去,下面还有使用方法):(点击下载)
packagecom.mybatis.util;
importorg.apache.ibatis.executor.parameter.ParameterHandler;
importorg.apache.ibatis.executor.resultset.ResultSetHandler;
importorg.apache.ibatis.executor.statement.StatementHandler;
importorg.apache.ibatis.mapping.BoundSql;
importorg.apache.ibatis.mapping.MappedStatement;
importorg.apache.ibatis.plugin.*;
importorg.apache.ibatis.reflection.MetaObject;
importorg.apache.ibatis.reflection.SystemMetaObject;
importorg.apache.ibatis.scripting.defaults.DefaultParameterHandler;
importorg.apache.log4j.Logger;
importjava.sql.*;
importjava.util.List;
importjava.util.Properties;
/**
*Mybatis-通用分页拦截器
*@authorliuzh/abel533/isea
*Createdbyliuzhon14-4-15.
*/
@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class}),
@Signature(type=ResultSetHandler.class,method="handleResultSets",args={Statement.class})})
publicclassPageHelperimplementsInterceptor{
privatestaticfinalLoggerlogger=Logger.getLogger(PageHelper.class);
publicstaticfinalThreadLocal<Page>localPage=newThreadLocal<Page>();
/**
*开始分页
*@parampageNum
*@parampageSize
*/
publicstaticvoidstartPage(intpageNum,intpageSize){
localPage.set(newPage(pageNum,pageSize));
}
/**
*结束分页并返回结果,该方法必须被调用,否则localPage会一直保存下去,直到下一次startPage
*@return
*/
publicstaticPageendPage(){
Pagepage=localPage.get();
localPage.remove();
returnpage;
}
@Override
publicObjectintercept(Invocationinvocation)throwsThrowable{
if(localPage.get()==null){
returninvocation.proceed();
}
if(invocation.getTarget()instanceofStatementHandler){
StatementHandlerstatementHandler=(StatementHandler)invocation.getTarget();
MetaObjectmetaStatementHandler=SystemMetaObject.forObject(statementHandler);
//分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环
//可以分离出最原始的的目标类)
while(metaStatementHandler.hasGetter("h")){
Objectobject=metaStatementHandler.getValue("h");
metaStatementHandler=SystemMetaObject.forObject(object);
}
//分离最后一个代理对象的目标类
while(metaStatementHandler.hasGetter("target")){
Objectobject=metaStatementHandler.getValue("target");
metaStatementHandler=SystemMetaObject.forObject(object);
}
MappedStatementmappedStatement=(MappedStatement)metaStatementHandler.getValue("delegate.mappedStatement");
//分页信息if(localPage.get()!=null){
Pagepage=localPage.get();
BoundSqlboundSql=(BoundSql)metaStatementHandler.getValue("delegate.boundSql");
//分页参数作为参数对象parameterObject的一个属性
Stringsql=boundSql.getSql();
//重写sql
StringpageSql=buildPageSql(sql,page);
//重写分页sql
metaStatementHandler.setValue("delegate.boundSql.sql",pageSql);
Connectionconnection=(Connection)invocation.getArgs()[0];
//重设分页参数里的总页数等
setPageParameter(sql,connection,mappedStatement,boundSql,page);
//将执行权交给下一个拦截器
returninvocation.proceed();
}elseif(invocation.getTarget()instanceofResultSetHandler){
Objectresult=invocation.proceed();
Pagepage=localPage.get();
page.setResult((List)result);
returnresult;
}
returnnull;
}
/**
*只拦截这两种类型的
*StatementHandler
*ResultSetHandler
*@paramtarget
*@return
*/
@Override
publicObjectplugin(Objecttarget){
if(targetinstanceofStatementHandler||targetinstanceofResultSetHandler){
returnPlugin.wrap(target,this);
}else{
returntarget;
}
}
@Override
publicvoidsetProperties(Propertiesproperties){
}
/**
*修改原SQL为分页SQL
*@paramsql
*@parampage
*@return
*/
privateStringbuildPageSql(Stringsql,Pagepage){
StringBuilderpageSql=newStringBuilder(200);
pageSql.append("select*from(selecttemp.*,rownumrow_idfrom(");
pageSql.append(sql);
pageSql.append(")tempwhererownum<=").append(page.getEndRow());
pageSql.append(")whererow_id>").append(page.getStartRow());
returnpageSql.toString();
}
/**
*获取总记录数
*@paramsql
*@paramconnection
*@parammappedStatement
*@paramboundSql
*@parampage
*/
privatevoidsetPageParameter(Stringsql,Connectionconnection,MappedStatementmappedStatement,
BoundSqlboundSql,Pagepage){
//记录总记录数
StringcountSql="selectcount(0)from("+sql+")";
PreparedStatementcountStmt=null;
ResultSetrs=null;
try{
countStmt=connection.prepareStatement(countSql);
BoundSqlcountBS=newBoundSql(mappedStatement.getConfiguration(),countSql,
boundSql.getParameterMappings(),boundSql.getParameterObject());
setParameters(countStmt,mappedStatement,countBS,boundSql.getParameterObject());
rs=countStmt.executeQuery();
inttotalCount=0;
if(rs.next()){
totalCount=rs.getInt(1);
}
page.setTotal(totalCount);
inttotalPage=totalCount/page.getPageSize()+((totalCount%page.getPageSize()==0)?0:1);
page.setPages(totalPage);
}catch(SQLExceptione){
logger.error("Ignorethisexception",e);
}finally{
try{
rs.close();
}catch(SQLExceptione){
logger.error("Ignorethisexception",e);
}
try{
countStmt.close();
}catch(SQLExceptione){
logger.error("Ignorethisexception",e);
}
}
}
/**
*代入参数值
*@paramps
*@parammappedStatement
*@paramboundSql
*@paramparameterObject
*@throwsSQLException
*/
privatevoidsetParameters(PreparedStatementps,MappedStatementmappedStatement,BoundSqlboundSql,
ObjectparameterObject)throwsSQLException{
ParameterHandlerparameterHandler=newDefaultParameterHandler(mappedStatement,parameterObject,boundSql);
parameterHandler.setParameters(ps);
}
/**
*Description:分页
*Author:liuzh
*Update:liuzh(2014-04-1610:56)
*/
publicstaticclassPage<E>{
privateintpageNum;
privateintpageSize;
privateintstartRow;
privateintendRow;
privatelongtotal;
privateintpages;
privateList<E>result;
publicPage(intpageNum,intpageSize){
this.pageNum=pageNum;
this.pageSize=pageSize;
this.startRow=pageNum>0?(pageNum-1)*pageSize:0;
this.endRow=pageNum*pageSize;
}
publicList<E>getResult(){
returnresult;
}
publicvoidsetResult(List<E>result){
this.result=result;
}
publicintgetPages(){
returnpages;
}
publicvoidsetPages(intpages){
this.pages=pages;
}
publicintgetEndRow(){
returnendRow;
}
publicvoidsetEndRow(intendRow){
this.endRow=endRow;
}
publicintgetPageNum(){
returnpageNum;
}
publicvoidsetPageNum(intpageNum){
this.pageNum=pageNum;
}
publicintgetPageSize(){
returnpageSize;
}
publicvoidsetPageSize(intpageSize){
this.pageSize=pageSize;
}
publicintgetStartRow(){
returnstartRow;
}
publicvoidsetStartRow(intstartRow){
this.startRow=startRow;
}
publiclonggetTotal(){
returntotal;
}
publicvoidsetTotal(longtotal){
this.total=total;
}
@Override
publicStringtoString(){
return"Page{"+
"pageNum="+pageNum+
",pageSize="+pageSize+
",startRow="+startRow+
",endRow="+endRow+
",total="+total+
",pages="+pages+
'}';
}
}
}
使用该拦截器首先需要在Mybatis配置中配置该拦截器:
<plugins> <plugininterceptor="com.mybatis.util.PageHelper"></plugin> </plugins>
配置拦截器的时候需要注意plugins的位置,plugins位置顺序如下:
properties?,settings?,typeAliases?,typeHandlers?,objectFactory?,objectWrapperFactory?,plugins?,environments?,databaseIdProvider?,mappers?
最后是调用该方法的例子代码(Service层):
@Override
publicPageHelper.Page<SysLoginLog>findSysLoginLog(StringloginIp,
Stringusername,
StringloginDate,
StringexitDate,
Stringlogerr,
intpageNumber,
intpageSize)throwsBusinessException{
PageHelper.startPage(pageNumber,pageSize);
sysLoginLogMapper.findSysLoginLog(loginIp,username,loginDate,exitDate,logerr);
returnPageHelper.endPage();
}
从上面可以看到使用该插件使用起来是很简单的,只需要在查询前后使用PageHelper的startPage和endPage方法即可,中间代码的调用结果已经存在于Page的result中,如果你在一个返回一个结果的地方调用PageHelper,返回的结果仍然是一个List,取第一个值即可(我想没人会在这种地方这么用,当然这样也不出错)。
另外在startPage和endPage中间的所有mybatis代码都会被分页,而且PageHelper只会保留最后一次的结果,因而使用时需要保证每次只在其中执行一个mybatis查询,如果有多个分页,请多次使用startPage和endPage。
由于这里只提供了Oracle的实现,所以我希望参考该分页插件实现的其他数据库的读者也能将相应的代码开源。
项目地址:http://xiazai.jb51.net/201612/yuanma/Mybatis_PageHelper_jb51.zip
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。