一聚教程网:一个值得你收藏的教程网站

最新下载

热门教程

Mybatis分页查询java公用类及完整的Mybatis分页解决方案

时间:2015-08-16 编辑:简简单单 来源:一聚教程网

Mybatis封装分页查询的java公用类

分页----对于数据量很大的查询中,是必不可少的。mybatis底层的分页sql语句由于需要我们自己去手动写。而实现分页显示的时候我们需要根据分页查询条件查询符合条件的总记录数和记录的详细情况。因此,若是不去实现封装一下的话,我们需要写两条SQL语句去实现它。一次用于查询记录数目。一次用于查询分页显示的详细记录。当项目中碰到很多需要分页的时候,我们便对于每一个Mapper.xml文件都需要去写两条SQL语句。极其麻烦,代码重用----必须重用。所以,一个公共方法的分页需求应运而生。

直接上分页公共代码,其实现的原理是使用了拦截器的拦截作用。拦截一类分页查询的请求。我们根据传进来的参数是否是需要interceptor()方法中拦截的参数,是的话则拦截,并执行相应的SQL追加,否则,不进行追加。直接放行。视作普通查询。

需要在Mybatis的配置文件中配置加载服务器的时候加载该公共类:mybatis-config.xml


   
   
            
           
                   
          
  
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Properties;    
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;    
import org.apache.ibatis.mapping.BoundSql;    
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.Interceptor;    
import org.apache.ibatis.plugin.Intercepts;    
import org.apache.ibatis.plugin.Invocation;    
import org.apache.ibatis.plugin.Plugin;    
import org.apache.ibatis.plugin.Signature;    
import org.apache.ibatis.reflection.MetaObject;    
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.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.RowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
 * 分页拦截器
 * @since 10.20.2014
 */
@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class})})    
public class PaginationInterceptor implements Interceptor {
    
    private final Logger logger = LoggerFactory.getLogger(PaginationInterceptor.class);
    private static final ObjectFactory DEFAULT_OBJECT_FACTORY = new DefaultObjectFactory();
    private static final ObjectWrapperFactory DEFAULT_OBJECT_WRAPPER_FACTORY = new DefaultObjectWrapperFactory();
        
    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);  
         }
         
         MappedStatement mappedStatement = (MappedStatement)   
         metaStatementHandler.getValue("delegate.mappedStatement");  
         // 只重写需要分页的sql语句。通过MappedStatement的ID匹配,默认重写以Page结尾的  
         //  MappedStatement的sql  
         BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");  
         Object parameterObject = boundSql.getParameterObject();  
         
         if(parameterObject instanceof  PageParam){
             if (parameterObject == null) {
                 throw new NullPointerException("parameterObject is null!");  
             } else {
                 PageParam page = (PageParam)parameterObject;  
                 String sql = boundSql.getSql();  
                 // 重写sql  
                 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];  
                 // 重设分页参数里的总页数等  
                 setPageParameter(sql, connection, mappedStatement, boundSql, page);  
             }
         }  
         // 将执行权交给下一个拦截器  
         return invocation.proceed();  
     }  
    
    private String buildPageSql(String sql, PageParam page) {  
        if (page != null) {  
            StringBuilder pageSql = new StringBuilder();  
                pageSql = buildPageSqlForOracle(sql, page);  
            return pageSql.toString();  
        } else {
            return sql;  
        }  
    }  
    
    public StringBuilder buildPageSqlForOracle(String sql, PageParam page) {
        StringBuilder pageSql = new StringBuilder(100);
        String beginrow = String.valueOf((page.getCurrentPage() - 1) * page.getPageSize());  
        String endrow = String.valueOf(page.getCurrentPage() * page.getPageSize());  
        pageSql.append("select * from ( select temp.*, rownum row_id from ( ");  
        pageSql.append(sql);  
        pageSql.append(" ) temp where rownum <= ").append(endrow);
        pageSql.append(") where row_id > ").append(beginrow);
        return pageSql;  
    }  
    
    /**
     * 从数据库里查询总的记录数并计算总页数,回写进分页参数PageParam,这样调用  
     * 者就可用通过 分页参数PageParam获得相关信息。
     *  
     * @param sql
     * @param connection
     * @param mappedStatement
     * @param boundSql
     * @param page
     * @throws SQLException
     */  
    private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement,  
            BoundSql boundSql, PageParam page) throws SQLException {  
        // 记录总记录数  
        String countSql = "select count(0) from (" + sql + ")";  
        PreparedStatement countStmt = null;  
        ResultSet rs = null;  
        try {  
            countStmt = connection.prepareStatement(countSql);  
            BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,  
                    boundSql.getParameterMappings(), boundSql.getParameterObject());  
            setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());  
            rs = countStmt.executeQuery();
            int totalCount = 0;  
            if (rs.next()) {
                totalCount = rs.getInt(1);  
            }
            page.setTotalCount(totalCount);  
            int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1);  
            page.setTotalPage(totalPage);
        } catch (SQLException e) {  
            logger.error("exception", e);
        } finally {
            try {  
                rs.close();  
            } catch (SQLException e) {  
                logger.error("exception", e);  
            }  
            try {  
                countStmt.close();  
            } catch (SQLException e) {  
                logger.error("exception", e);  
            }  
        }  
    }  
      
    private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,  
            Object parameterObject) throws SQLException {  
        ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);  
        parameterHandler.setParameters(ps);  
    }  
    
    @Override
    public Object plugin(Object target) {    
        if (target instanceof StatementHandler) {  
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }
    
    @Override
    public void setProperties(Properties arg0) {    
    }
}


将日志的过滤模式调到DEBUG模式,控制台可以打印出SQL
使用上述方法处理的分页查询,其只需要一条SQL语句就可以(Mapper.xml文件的SQL)


    
        from channlsettle where 1=1
        AND CHANNL_ID=#{params.channelSettleModel.channelId}
        
            = to_number(substr(#{params.channelSettleModel.clearStartTime},0,8))   
             ]]>
        
        
            
        
        order by INSTDATE desc


控制台打印的SQL:

第一条:select count(0) from (select *  from channlsettle where 1=1 AND CHANNL_ID=? AND to_number(CLEAR_DATE) >= to_number(substr(?,0,8)) AND to_number(CLEAR_DATE) <= to_number(substr(?,0,8)) order by INSTDATE desc)

第二条:select * from ( select temp.*, rownum row_id from ( select *  from channlsettle where 1=1 AND CHANNL_ID=? AND to_number(CLEAR_DATE) >= to_number(substr(?,0,8)) AND to_number(CLEAR_DATE) <= to_number(substr(?,0,8)) order by INSTDATE desc ) temp where rownum <= 20) where row_id > 0

从而让公共类实现了我们需要在Mapper.xml配置文件中重复写入两条SQL的工作,以后没一个分页查询都可以使用。

java企业级通用权限安全框架源码 SpringMVC mybatis or hibernate+ehcache shiro druid bootstrap HTML5


分享一个完整的Mybatis分页解决方案


Mybatis 的物理分页是应用中的一个难点,特别是配合检索和排序功能叠加时更是如此。

我在最近的项目中开发了这个通用分页器,过程中参考了站内不少好文章,新年第一天,特此发文回馈网站。

特别鸣谢 paginator项目 (https://github.com/miemiedev/mybatis-paginator ) ,阅读源码帮助很大。

【背景】

项目框架是 SpringMVC+Mybatis, 需求是想采用自定义的分页标签,同时,要尽量少的影响业务程序开发的。
如果你已经使用了JS框架( 如:Ext,EasyUi等)自带的分页机能,是属于前端分页,不在本文讨论范围。

【关于问题】

大多数分页器会使用在查询页面,要考虑以下问题:
1)分页时是要随时带有最近一次查询条件
2)不能影响现有的sql,类似aop的效果
3)mybatis提供了通用的拦截接口,要选择适当的拦截方式和时点
4)尽量少的影响现有service等接口


【关于依赖库】

Google Guava    作为基础工具包
Commons JXPath  用于对象查询  (1/23日版改善后,不再需要)
Jackson  向前台传送Json格式数据转换用

 
【关于适用数据库】

现在只适用mysql

(如果需要用在其他数据库可参考 paginator的Dialect部分,改动都不大)

首先是Page类,比较简单,保存分页相关的所有信息,涉及到分页算法。虽然“其貌不扬”,但很重要。后面会看到这个page类对象会以“信使”的身份出现在全部与分页相关的地方。

/** 
 * 封装分页数据 
 */  
import java.util.List;  
import java.util.Map;  
  
import org.codehaus.jackson.map.ObjectMapper;  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
  
import com.google.common.base.Joiner;  
import com.google.common.collect.Lists;  
import com.google.common.collect.Maps;  
  
public class Page {  
  
  private static final Logger logger = LoggerFactory.getLogger(Page.class);  
  private static ObjectMapper mapper = new ObjectMapper();  
  
  public static String DEFAULT_PAGESIZE = "10";  
  private int pageNo;          //当前页码  
  private int pageSize;        //每页行数  
  private int totalRecord;      //总记录数  
  private int totalPage;        //总页数  
  private Map params;  //查询条件  
  private Map> paramLists;  //数组查询条件  
  private String searchUrl;      //Url地址  
  private String pageNoDisp;       //可以显示的页号(分隔符"|",总页数变更时更新)  
  
  private Page() {  
    pageNo = 1;  
    pageSize = Integer.valueOf(DEFAULT_PAGESIZE);  
    totalRecord = 0;  
    totalPage = 0;  
    params = Maps.newHashMap();  
    paramLists = Maps.newHashMap();  
    searchUrl = "";  
    pageNoDisp = "";  
  }  
     
  public static Page newBuilder(int pageNo, int pageSize, String url){  
    Page page = new Page();  
    page.setPageNo(pageNo);  
    page.setPageSize(pageSize);  
    page.setSearchUrl(url);  
    return page;  
  }  
    
  /** 
   * 查询条件转JSON 
   */  
  public String getParaJson(){  
    Map map = Maps.newHashMap();  
    for (String key : params.keySet()){  
      if ( params.get(key) != null  ){  
        map.put(key, params.get(key));  
      }  
    }  
    String json="";  
    try {  
      json = mapper.writeValueAsString(map);  
    } catch (Exception e) {  
      logger.error("转换JSON失败", params, e);  
    }  
    return json;  
  }  
  
  /** 
   * 数组查询条件转JSON 
   */  
  public String getParaListJson(){  
    Map map = Maps.newHashMap();  
    for (String key : paramLists.keySet()){  
      List lists = paramLists.get(key);  
      if ( lists != null && lists.size()>0 ){  
        map.put(key, lists);  
      }  
    }  
    String json="";  
    try {  
      json = mapper.writeValueAsString(map);  
    } catch (Exception e) {  
      logger.error("转换JSON失败", params, e);  
    }  
    return json;  
  }  
  
  /** 
   * 总件数变化时,更新总页数并计算显示样式 
   */  
  private void refreshPage(){  
    //总页数计算  
    totalPage = totalRecord%pageSize==0 ? totalRecord/pageSize : (totalRecord/pageSize + 1);  
    //防止超出最末页(浏览途中数据被删除的情况)  
    if ( pageNo > totalPage && totalPage!=0){  
        pageNo = totalPage;  
    }  
    pageNoDisp = computeDisplayStyleAndPage();  
  }  
    
  /** 
   * 计算页号显示样式 
   *  这里实现以下的分页样式("[]"代表当前页号),可根据项目需求调整 
   *   [1],2,3,4,5,6,7,8..12,13 
   *   1,2..5,6,[7],8,9..12,13 
   *   1,2..6,7,8,9,10,11,12,[13] 
   */  
  private String computeDisplayStyleAndPage(){  
    List pageDisplays = Lists.newArrayList();  
    if ( totalPage <= 11 ){  
      for (int i=1; i<=totalPage; i++){  
        pageDisplays.add(i);  
      }  
    }else if ( pageNo < 7 ){  
      for (int i=1; i<=8; i++){  
        pageDisplays.add(i);  
      }  
      pageDisplays.add(0);// 0 表示 省略部分(下同)  
      pageDisplays.add(totalPage-1);         
      pageDisplays.add(totalPage);  
    }else if ( pageNo> totalPage-6 ){  
      pageDisplays.add(1);  
      pageDisplays.add(2);  
      pageDisplays.add(0);  
      for (int i=totalPage-7; i<=totalPage; i++){  
        pageDisplays.add(i);  
      }         
    }else{  
      pageDisplays.add(1);  
      pageDisplays.add(2);  
      pageDisplays.add(0);  
      for (int i=pageNo-2; i<=pageNo+2; i++){  
        pageDisplays.add(i);  
      }  
      pageDisplays.add(0);  
      pageDisplays.add(totalPage-1);  
      pageDisplays.add(totalPage);  
    }  
    return Joiner.on("|").join(pageDisplays.toArray());  
  }  
   
  public int getPageNo() {  
     return pageNo;  
  }  
   
  public void setPageNo(int pageNo) {  
     this.pageNo = pageNo;  
  }  
   
  public int getPageSize() {  
     return pageSize;  
  }  
   
  public void setPageSize(int pageSize) {  
     this.pageSize = pageSize;  
  }  
   
  public int getTotalRecord() {  
     return totalRecord;  
  }  
   
  public void setTotalRecord(int totalRecord) {  
    this.totalRecord = totalRecord;  
    refreshPage();       
  }  
  
  public int getTotalPage() {  
     return totalPage;  
  }  
   
  public void setTotalPage(int totalPage) {  
     this.totalPage = totalPage;  
  }  
   
  public Map getParams() {  
     return params;  
  }  
     
  public void setParams(Map params) {  
     this.params = params;  
  }  
    
  public Map> getParamLists() {  
    return paramLists;  
  }  
  
  public void setParamLists(Map> paramLists) {  
    this.paramLists = paramLists;  
  }  
  public String getSearchUrl() {  
    return searchUrl;  
  }  
  public void setSearchUrl(String searchUrl) {  
    this.searchUrl = searchUrl;  
  }  
  public String getPageNoDisp() {  
    return pageNoDisp;  
  }  
  public void setPageNoDisp(String pageNoDisp) {  
    this.pageNoDisp = pageNoDisp;  
  }  
}


然后是最核心的拦截器了。涉及到了mybatis的核心功能,期间阅读大量mybatis源码几经修改重构,辛苦自不必说。

核心思想是将拦截到的select语句,改装成select count(*)语句,执行之得到,总数据数。再根据page中的当前页号算出limit值,拼接到select语句后。

为简化代码使用了Commons JXPath 包,做对象查询。

/** 
 * 分页用拦截器 
 */  
import java.sql.Connection;  
import java.sql.PreparedStatement;  
import java.sql.ResultSet;  
import java.util.Properties;  
  
import org.apache.commons.jxpath.JXPathContext;  
import org.apache.commons.jxpath.JXPathNotFoundException;  
import org.apache.ibatis.executor.Executor;  
import org.apache.ibatis.executor.parameter.DefaultParameterHandler;  
import org.apache.ibatis.mapping.BoundSql;  
import org.apache.ibatis.mapping.MappedStatement;  
import org.apache.ibatis.mapping.MappedStatement.Builder;  
import org.apache.ibatis.mapping.ParameterMapping;  
import org.apache.ibatis.mapping.SqlSource;  
import org.apache.ibatis.plugin.Interceptor;  
import org.apache.ibatis.plugin.Intercepts;  
import org.apache.ibatis.plugin.Invocation;  
import org.apache.ibatis.plugin.Plugin;  
import org.apache.ibatis.plugin.Signature;  
import org.apache.ibatis.session.ResultHandler;  
import org.apache.ibatis.session.RowBounds;  
  
@Intercepts({@Signature(type=Executor.class,method="query",args={ MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class })})  
public class PageInterceptor implements Interceptor{  
  
  public Object intercept(Invocation invocation) throws Throwable {  
      
    //当前环境 MappedStatement,BoundSql,及sql取得  
    MappedStatement mappedStatement=(MappedStatement)invocation.getArgs()[0];      
    Object parameter = invocation.getArgs()[1];   
    BoundSql boundSql = mappedStatement.getBoundSql(parameter);   
    String originalSql = boundSql.getSql().trim();  
    Object parameterObject = boundSql.getParameterObject();  
  
    //Page对象获取,“信使”到达拦截器!  
    Page page = searchPageWithXpath(boundSql.getParameterObject(),".","page","*/page");  
  
    if(page!=null ){  
      //Page对象存在的场合,开始分页处理  
      String countSql = getCountSql(originalSql);  
      Connection connection=mappedStatement.getConfiguration().getEnvironment().getDataSource().getConnection()  ;            
      PreparedStatement countStmt = connection.prepareStatement(countSql);    
      BoundSql countBS = copyFromBoundSql(mappedStatement, boundSql, countSql);  
      DefaultParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, countBS);  
      parameterHandler.setParameters(countStmt);  
      ResultSet rs = countStmt.executeQuery();  
      int totpage=0;  
      if (rs.next()) {    
        totpage = rs.getInt(1);    
      }  
      rs.close();    
      countStmt.close();    
      connection.close();  
        
      //分页计算  
      page.setTotalRecord(totpage);  
        
      //对原始Sql追加limit  
      int offset = (page.getPageNo() - 1) * page.getPageSize();  
      StringBuffer sb = new StringBuffer();  
      sb.append(originalSql).append(" limit ").append(offset).append(",").append(page.getPageSize());  
      BoundSql newBoundSql = copyFromBoundSql(mappedStatement, boundSql, sb.toString());  
      MappedStatement newMs = copyFromMappedStatement(mappedStatement,new BoundSqlSqlSource(newBoundSql));    
      invocation.getArgs()[0]= newMs;    
    }  
    return invocation.proceed();  
      
  }  
    
  /** 
   * 根据给定的xpath查询Page对象 
   */  
  private Page searchPageWithXpath(Object o,String... xpaths) {  
    JXPathContext context = JXPathContext.newContext(o);  
    Object result;  
    for(String xpath : xpaths){  
      try {  
        result = context.selectSingleNode(xpath);  
      } catch (JXPathNotFoundException e) {  
        continue;  
      }  
      if ( result instanceof Page ){  
        return (Page)result;  
      }  
    }  
    return null;  
  }  
  
  /** 
   * 复制MappedStatement对象 
   */  
  private MappedStatement copyFromMappedStatement(MappedStatement ms,SqlSource newSqlSource) {  
    Builder builder = new Builder(ms.getConfiguration(),ms.getId(),newSqlSource,ms.getSqlCommandType());  
      
    builder.resource(ms.getResource());  
    builder.fetchSize(ms.getFetchSize());  
    builder.statementType(ms.getStatementType());  
    builder.keyGenerator(ms.getKeyGenerator());  
    builder.keyProperty(ms.getKeyProperty());  
    builder.timeout(ms.getTimeout());  
    builder.parameterMap(ms.getParameterMap());  
    builder.resultMaps(ms.getResultMaps());  
    builder.resultSetType(ms.getResultSetType());  
    builder.cache(ms.getCache());  
    builder.flushCacheRequired(ms.isFlushCacheRequired());  
    builder.useCache(ms.isUseCache());  
      
    return builder.build();  
  }  
  
  /** 
   * 复制BoundSql对象 
   */  
  private BoundSql copyFromBoundSql(MappedStatement ms, BoundSql boundSql, String sql) {  
    BoundSql newBoundSql = new BoundSql(ms.getConfiguration(),sql, boundSql.getParameterMappings(), boundSql.getParameterObject());  
    for (ParameterMapping mapping : boundSql.getParameterMappings()) {  
        String prop = mapping.getProperty();  
        if (boundSql.hasAdditionalParameter(prop)) {  
            newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop));  
        }  
    }  
    return newBoundSql;  
  }  
  
  /** 
   * 根据原Sql语句获取对应的查询总记录数的Sql语句 
   */  
  private String getCountSql(String sql) {  
    return "SELECT COUNT(*) FROM (" + sql + ") aliasForPage";  
  }  
  
  public class BoundSqlSqlSource implements SqlSource {    
      BoundSql boundSql;    
      public BoundSqlSqlSource(BoundSql boundSql) {    
        this.boundSql = boundSql;    
      }    
      public BoundSql getBoundSql(Object parameterObject) {    
        return boundSql;    
      }    
    }    
  public Object plugin(Object arg0) {  
     return Plugin.wrap(arg0, this);  
  }  
  public void setProperties(Properties arg0) {  
  }  
}


到展示层终于可以轻松些了,使用了文件标签来简化前台开发。

采用临时表单提交,CSS使用了Bootstrap。

<%@tag pageEncoding="UTF-8"%>  
<%@ attribute name="page" type="cn.com.intasect.ots.common.utils.Page" required="true"%>  
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>  
  
<%  
int current =  page.getPageNo();  
int begin = 1;  
int end = page.getTotalPage();  
  request.setAttribute("current", current);  
request.setAttribute("begin", begin);  
request.setAttribute("end", end);  
request.setAttribute("pList", page.getPageNoDisp());  
  
%>  
  
  var paras = '<%=page.getParaJson()%>';  
  var paraJson = eval('(' + paras + ')');  
  
  //将提交参数转换为JSON  
  var paraLists = '<%=page.getParaListJson()%>';  
  var paraListJson = eval('(' + paraLists + ')');  
  function pageClick( pNo ){  
    paraJson["pageNo"] = pNo;  
    paraJson["pageSize"] = "<%=page.getPageSize()%>";  
      
    var jsPost = function(action, values, valueLists) {  
      var id = Math.random();  
      document.write('');  
      for (var key in values) {  
        document.write('');  
      }  
      for (var key2 in valueLists) {  
        for (var index in valueLists[key2]) {  
          document.write('');  
        }  
      }  
      document.write('');      
      document.getElementById('post' + id).submit();  
    }  
      
    //发送POST  
    jsPost("<%=page.getSearchUrl()%>", paraJson, paraListJson);  
  }  
  
  
  <% if (current!=1 && end!=0){%>  
    首页  
    前页  
  <%}else{%>  
    首页  
    前页  
  <%} %>  
    
      
        
        •••  
        
        
        ${pNo}  
        
        
        ${pNo}  
        
      
    
  <% if (current  
    后页  
    末页  
  <%}else{%>  
    后页  
    末页  
  <%} %>  


注意“信使”在这里使出了浑身解数,7个主要的get方法全部用上了。

page.getPageNo()        //当前页号  
page.getTotalPage()     //总页数  
page.getPageNoDisp()    //可以显示的页号  
page.getParaJson()      //查询条件  
page.getParaListJson()  //数组查询条件  
page.getPageSize()      //每页行数  
page.getSearchUrl()     //Url地址(作为action名称)


到这里三个核心模块完成了。然后是拦截器的注册。

【拦截器的注册】

需要在mybatis-config.xml 中加入拦截器的配置

  
       
     


【相关代码修改】

首先是后台代码的修改,Controller层由于涉及到查询条件,需要修改的内容较多。

1)入参需增加 pageNo,pageSize 两个参数
2)根据pageNo,pageSize 及你的相对url构造page对象。(
3)最重要的是将你的其他入参(查询条件)保存到page中
4)Service层的方法需要带着page这个对象(最终目的是传递到sql执行的入参,让拦截器识别出该sql需要分页,同时传递页号)
5)将page对象传回Mode中

修改前

@RequestMapping(value = "/user/users")  
public String list(  
  @ModelAttribute("name") String name,  
  @ModelAttribute("levelId") String levelId,  
  @ModelAttribute("subjectId") String subjectId,  
  Model model) {  
  model.addAttribute("users",userService.selectByNameLevelSubject(  
          name, levelId, subjectId));  
  return USER_LIST_JSP;  
}


修改后

@RequestMapping(value = "/user/users")  
public String list(  
  @RequestParam(required = false, defaultValue = "1") int pageNo,  
  @RequestParam(required = false, defaultValue = "5") int pageSize,  
  @ModelAttribute("name") String name,  
  @ModelAttribute("levelId") String levelId,  
  @ModelAttribute("subjectId") String subjectId,  
  Model model) {  
  // 这里是“信使”诞生之地,一出生就加载了很多重要信息!  
  Page page = Page.newBuilder(pageNo, pageSize, "users");  
  page.getParams().put("name", name);           //这里再保存查询条件  
  page.getParams().put("levelId", levelId);  
  page.getParams().put("subjectId", subjectId);  
      
  model.addAttribute("users",userService.selectByNameLevelSubject(  
          name, levelId, subjectId, page));  
  model.addAttribute("page", page);             //这里将page返回前台  
  return USER_LIST_JSP;  
}


注意pageSize的缺省值决定该分页的每页数据行数 ,实际项目更通用的方式是使用配置文件指定。

 
Service层

拦截器可以自动识别在Map或Bean中的Page对象。

如果使用Bean需要在里面增加一个page项目,Map则比较简单,以下是例子。

@Override  
public List selectByNameLevelSubject(String name, String levelId, String subjectId, Page page) {  
  Map map = Maps.newHashMap();  
  levelId = DEFAULT_SELECTED.equals(levelId)?null: levelId;  
  subjectId = DEFAULT_SELECTED.equals(subjectId)?null: subjectId;  
  if (name != null && name.isEmpty()){  
    name = null;  
  }  
  map.put("name", name);  
  map.put("levelId", levelId);  
  map.put("subjectId", subjectId);  
  map.put("page", page);             //MAP的话加这一句就OK  
  return userMapper.selectByNameLevelSubject(map);  
}


前台页面方面,由于使用了标签,在适当的位置加一句就够了。

 

 “信使”page在这里进入标签,让分页按钮最终展现。

至此,无需修改一句sql,完成分页自动化。
 

【效果图】


 
【总结】

 现在回过头来看下最开始提出的几个问题:

1)分页时是要随时带有最近一次查询条件

  回答:在改造Controller层时,通过将提交参数设置到 Page对象的 Map params(单个基本型参数) 和 Map> paramLists(数组基本型)解决。

  顺便提一下,例子中没有涉及参数是Bean的情况,实际应用中应该比较常见。简单的方法是将Bean转换层Map后加入到params。

 

2)不能影响现有的sql,类似aop的效果

  回答:利用Mybatis提供了 Interceptor 接口,拦截后改头换面去的件数并计算limit值,自然能神不知鬼不觉。

 
3)mybatis提供了通用的拦截接口,要选择适当的拦截方式和时点

  回答:@Signature(method = "query", type = Executor.class, args = {  MappedStatement.class, Object.class, RowBounds.class,  ResultHandler.class }) 只拦截查询语句,其他增删改查不会影响。


4)尽量少的影响现有service等接口

  回答:这个自认为本方案做的还不够好,主要是Controller层改造上,感觉代码量还比较大。如果有有识者知道更好的方案还请多指教。

 
【遗留问题】

1)一个“明显”的性能问题,是每次检索前都要去 select count(*)一次。在很多时候(数据变化不是特别敏感的场景)是不必要的。调整也不难,先Controller参数增加一个 totalRecord 总记录数 ,在稍加修改一下Page相关代码即可。

2)要排序怎么办?本文并未讨论排序,但是方法是类似的。以上面代码为基础,可以较容易地实现一个通用的排序标签。

 
============================================================================

对于Controller层需要将入参传入Page对象的问题已经进行了改善,思路是自动从HttpServletRequest 类中提取入残,减

热门栏目