`

分享一个完整的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<String, String> params;  //查询条件
  private Map<String, List<String>> 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<String, Object> 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<String, Object> map = Maps.newHashMap();
    for (String key : paramLists.keySet()){
      List<String> 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<Integer> 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<String, String> getParams() {
     return params;
  }
   
  public void setParams(Map<String, String> params) {
     this.params = params;
  }
  
  public Map<String, List<String>> getParamLists() {
    return paramLists;
  }

  public void setParamLists(Map<String, List<String>> 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());

%>
<script type="text/javascript">
  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('<form id="post' + id + '" name="post'+ id +'" action="' + action + '" method="post">');
      for (var key in values) {
        document.write('<input type="hidden" name="' + key + '" value="' + values[key] + '" />');
      }
      for (var key2 in valueLists) {
        for (var index in valueLists[key2]) {
          document.write('<input type="hidden" name="' + key2 + '" value="' + valueLists[key2][index] + '" />');
        }
      }
      document.write('</form>');    
      document.getElementById('post' + id).submit();
    }
    
    //发送POST
    jsPost("<%=page.getSearchUrl()%>", paraJson, paraListJson);
  }
</script>
<div class="page-pull-right">
  <% if (current!=1 && end!=0){%>
    <button class="btn btn-default btn-sm" onclick="pageClick(1)">首页</button>
    <button class="btn btn-default btn-sm" onclick="pageClick(${current-1})">前页</button>
  <%}else{%>
    <button class="btn btn-default btn-sm" >首页</button>
    <button class="btn btn-default btn-sm" >前页</button>
  <%} %>
  <c:forTokens items="${pList}" delims="|" var="pNo">
    <c:choose>
      <c:when test="${pNo == 0}">
        <label style="font-size: 10px; width: 20px; text-align: center;">•••</label>
      </c:when>
      <c:when test="${pNo != current}">
        <button class="btn btn-default btn-sm" onclick="pageClick(${pNo})">${pNo}</button>
      </c:when>
      <c:otherwise>
        <button class="btn btn-primary btn-sm" style="font-weight:bold;">${pNo}</button>
      </c:otherwise>
    </c:choose>
  </c:forTokens>
  <% if (current<end && end!=0){%>
    <button class="btn btn-default btn-sm" onclick="pageClick(${current+1})">后页</button>
    <button class="btn btn-default btn-sm" onclick="pageClick(${end})">末页</button>
  <%}else{%>
    <button class="btn btn-default btn-sm">后页</button>
    <button class="btn btn-default btn-sm">末页</button>
  <%} %>
</div>

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

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

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

 

【拦截器的注册】

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

    <plugins>
       <plugin interceptor="cn.com.dingding.common.utils.PageInterceptor">  
       </plugin>
    </plugins>  

 

【相关代码修改】

首先是后台代码的修改,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<UserDTO> selectByNameLevelSubject(String name, String levelId, String subjectId, Page page) {
    Map<String, Object> 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);
  }

 

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

<tags:page page="${page}"/>

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

 

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

 

【效果图】

 

【总结】

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

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

  回答:在改造Controller层时,通过将提交参数设置到 Page对象的 Map<String, String> params(单个基本型参数) 和 Map<String, List<String>> 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)要排序怎么办?本文并未讨论排序,但是方法是类似的。以上面代码为基础,可以较容易地实现一个通用的排序标签。

 

===================================== 分割线 (1/8)=======================================

对于Controller层需要将入参传入Page对象的问题已经进行了改善,思路是自动从HttpServletRequest 类中提取入残,减低了分页代码的侵入性,详细参看文章 http://duanhengbin.iteye.com/blog/2001142

===================================== 分割线 (1/23)=======================================

再次改善,使用ThreadLocal类封装Page对象,让Service层等无需传Page对象,减小了侵入性。拦截器也省去了查找Page对象的动作,性能也同时改善。整体代码改动不大。

===================================== 分割线 (2/21)=======================================

今天比较闲,顺便聊下这个分页的最终版,当然从来只有不断变化的需求,没有完美的方案,这里所说的最终版其实是一个优化后的“零侵入”的方案。为避免代码混乱还是只介绍思路。在上一个版本(1/23版)基础上有两点改动:

一是增加一个配置文件,按Url 配置初始的每页行数。如下面这样(pagesize 指的是每页行数):

<pager url="/user/users" pagesize="10" />

 二是增加一个过滤器,并将剩下的位于Control类中 唯一侵入性的分页相关代码移入过滤器。发现当前的 Url  在配置文件中有匹配是就构造Page对象,并加入到Response中。

 

使用最终版后,对于开发者需要分页时,只要在配置文件中加一行,并在前端页面上加一个分页标签即可,其他代码,SQL等都不需要任何改动,可以说简化到了极限。

 

【技术列表】

总结下最终方案用到的技术:

  • Mybatis 提供的拦截器接口实现(实现分页sql自动 select count 及limit 拼接)
  • Servlet过滤器+ThreadLocal  (生成线程共享的Page对象)
  • 标签文件   (实现前端共通的分页效果)
  • 临时表单提交 (减少页面体积)

【其他分页方案比较】

时下比较成熟的 JPA 的分页方案,(主要应用在 Hibernate + Spring Data 的场合),主要切入点在DAO层,而Controller等各层接口依然需要带着pageNumber,pageSize 这些的参数,另外框架开发者还要掌握一些必须的辅助类,如:

  org.springframework.data.repository.PagingAndSortingRepository    可分页DAO基类

  org.springframework.data.domain.Page            抽取结果封装类

  org.springframework.data.domain.Pageable     分页信息类

比较来看 本方案 做到了分页与业务逻辑的完全解耦,开发者无需关注分页,全部通过配置实现。通过这个例子也可以反映出Mybatis在底层开发上有其独特的优势。

 

【备选方案】

最后再闲扯下,上面的最终案是基于 Url 配置的,其实也可以基于方法加自定义注解来做。这样配置文件省了,但是要增加一个注解解析类。注解中参数 为初始的每页行数。估计注解fans会喜欢,如下面的样子:

  @RequestMapping(value = "/user/users")
  @Pagination(size=10)
  public String list(
  ...

同样与过滤器配合使用,只是注解本身多少还是有“侵入性”。在初始行数基本不会变更时,这个比较直观的方案也是不错的选择。大家自行决定吧。

 

  • 大小: 5.8 KB
分享到:
评论
35 楼 nlilewy 2017-06-26  
如果传值进来是
int findChaSanFang(@Param("id")String id,@Param("nums")String nums);
会报 org.apache.ibatis.binding.BindingException: Parameter 'page' not found. Available parameters are [nums, id, param1, param2]这个错。这个分页写得有问题,除非作者能改进。大家注意一下。
34 楼 xjhdcy 2017-02-03  
searchPageWithXpath,这个方法是做什么用的啊,如果直接用Page.newBuilder(,,,)代替可以吗
33 楼 sucheng2016 2016-10-11  
额滴神啊 搞个分页这复杂..
32 楼 gaby_wolf 2016-06-28  
shreo_2007 写道
有jar包maven地址就好了,或者直接上个实例


其中需要的jar包在pom.xml中的配置

<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
<dependency>
<groupId>commons-jxpath</groupId>
<artifactId>commons-jxpath</artifactId>
<version>1.3</version>
</dependency>
31 楼 Smart_咚咚 2016-04-10  
Smart_咚咚 写道
Smart_咚咚 写道
这种方式不能通过链接访问。。post提交 我看看能改不

好吧  没啥问题  关键是好像把所有select都拦截了。。
不过估计你不定什么时候才上

奇怪 我看了下代码  好像没这个问题啊  我自己找找吧
30 楼 Smart_咚咚 2016-04-10  
Smart_咚咚 写道
这种方式不能通过链接访问。。post提交 我看看能改不

好吧  没啥问题  关键是好像把所有select都拦截了。。
不过估计你不定什么时候才上
29 楼 Smart_咚咚 2016-04-09  
这种方式不能通过链接访问。。post提交 我看看能改不
28 楼 123191084 2016-01-14  
for (ParameterMapping mapping : boundSql.getParameterMappings()) { 
        String prop = mapping.getProperty(); 
        if (boundSql.hasAdditionalParameter(prop)) { 
            newBoundSql.setAdditionalParameter(prop, boundSql.getAdditionalParameter(prop)); 
        } 
    }
这段代码很有用 解决了list传值问题 
27 楼 u013740864 2015-10-26  
您好 问下org.apache.ibatis.executor.parameter.DefaultParameterHandler需要哪个jar包??我导入了好多个mybatis包都不行,一直报错。
26 楼 leipeng321123 2015-10-23  
刚看了一下你的分页,我有这个一个疑问,在程序中每次点击下一,都会重新去查询数据库,mybatis的插件都会拦截一次sql,生成一个count(*)的sql语句,这样的话,每次点击下一页,上一页的时候都会去查询总条数,会造成浪费资源,性能下降,583631723,能不能加下我QQ,我想和你聊聊这个,我还有几个问题想向您请教一下,代码里面有几处我还是不明白
25 楼 那年我十八 2015-10-22  
zhbf5156 写道
不明白为什么要引入Guava库,一个2m多的jar包,而程序中也只是使用Guava创建集合,是不是有些大材小用了。

24 楼 那年我十八 2015-10-22  
[url][url][img][list]
  • [list]
  • [*][list]
  • [*][*][list]
  • [*][*][*][*]
    引用
    引用
    引用
    引用
    引用
    引用
    [u][i][b][b][b]
    引用
    引用
    [list]
    [*][*][*][*][*][img][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*][*]
    [flash=200,200][url][img][list]
    [*]
    [/list][/img][/url][/flash]
    [/img] [*][*][*][*][/list]
    [/b][/b][/b][/i][/u]
  • [*][*][/list]
  • [*][/list]
  • [/list]
  • [/list][/img][/url][/url]
    23 楼 zhbf5156 2015-05-11  
    不明白为什么要引入Guava库,一个2m多的jar包,而程序中也只是使用Guava创建集合,是不是有些大材小用了。
    22 楼 smile_wangchun 2015-04-24  
    [size=large][size=x-large][size=xx-small][size=xx-large]这里有个问题,就是如果查询总数为0后,任然会执行查询列表集的sql!其实这是没有必要的,因为总数都为0了,集合列表就没有数据,不需要查询了。想想办法怎样改进一下[/size][/size][/size][/size]
    21 楼 shreo_2007 2015-02-27  
    有jar包maven地址就好了,或者直接上个实例
    20 楼 mc1013785085 2015-01-08  
    [list]
    [*]

    [/list][flash=200,200][/flash]
    19 楼 duanhengbin 2014-08-05  

    过滤器中的request貌似不能返回请求吧? 这个request是过来的路,不是回去的路吧?您能具体说说嘛?

    哎,被问到这里还是大概说一下吧。之前的回答好像不全。
    我现在的做法是继承 DispatcherSerlvet,覆写 render方法,在渲染之前吧 Page 放入ModelAndView里面。(DispatcherSerlvet变了,要修改Servlet配置)

    protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
        Page page = SystemContext.getPage();
        if ( page != null ){
            mv.addObject( "page", page);
        }
        super.render(mv, request, response);
    }
    


    这个地方还是有些疑虑的,因为感觉可能有更好的方法。
    18 楼 lisciple 2014-08-05  
    duanhengbin 写道
    lisciple 写道
    model.addAttribute("page", page);             //这里将page返回前台 
    请问你最后的版本中从threadlocal拿到page还是放在model里面返回吗?
    能不能把这行也消灭掉?


    page的生成及放入model都在过滤器中,对业务代码无侵入,没有消灭的必要了吧。


    过滤器中的request貌似不能返回请求吧? 这个request是过来的路,不是回去的路吧?您能具体说说嘛?
    17 楼 duanhengbin 2014-08-04  
    lisciple 写道
    model.addAttribute("page", page);             //这里将page返回前台 
    请问你最后的版本中从threadlocal拿到page还是放在model里面返回吗?
    能不能把这行也消灭掉?


    page的生成及放入model都在过滤器中,对业务代码无侵入,没有消灭的必要了吧。

    16 楼 lisciple 2014-08-04  
    model.addAttribute("page", page);             //这里将page返回前台 
    请问你最后的版本中从threadlocal拿到page还是放在model里面返回吗?
    能不能把这行也消灭掉?

    相关推荐

      06实现mybatis分页插件demo

      06实现mybatis分页插件demo06实现mybatis分页插件demo06实现mybatis分页插件demo06实现mybatis分页插件demo06实现mybatis分页插件demo06实现mybatis分页插件demo06实现mybatis分页插件demo06实现mybatis分页插件demo...

      mybatis分页完整的项目

      这是一个mybatis分页的完整的项目,同时提供了sql语句文件,是mysql的,只要将它所需的数据库表创建好,将项目导入发布,就能成功。 咱让没积分给吓怕了,所以,好心的哥们给点积分吧,呵呵。

      mybatis分页jar包

      自己封装的mybatis分页jar包,实现了mybatis的物理分页,目前只支持mysql和oracle两种数据库。

      mybatis分页插件支持查询

      mybatis分页插件支持查询~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

      mybatis分页例子(spring MVC mybatis 分页)

      mybatis 3.1.1, spring-3.1.3 与 mybatis-spring -1.1.1 集成的 分页程序,以及spring MVC 例子. 没分了,收取一分,如果有和我一样穷的兄弟,你可以到我的博客上去下载,不需要积分:...

      MyBatis 分页源码简单

      MyBatis 分页源码简单

      Mybatis分页插件PageHelper的JAR包

      Mybatis分页插件PageHelper的jar文件,Mybatis分页插件PageHelper的jar文件,Mybatis分页插件PageHelper的jar文件 4.1.4版本

      Mybatis分页拦截器

      Mybatis分页拦截器,mybatis-3.1.1和mybatis-3.0.4版本下测试通过。

      springboot+mybatis分页

      springboot集成mybatis分页插件的一个小demo,用xml实现sql语句

      mybatis分页插件源码

      这个是mybatis分页插件的源码分享,支持mysql和oracl

      mybatis分页拦截器(自动封装版)剖析.pdf

      mybatis分页拦截器(自动封装版)剖析.pdfmybatis分页拦截器(自动封装版)剖析.pdfmybatis分页拦截器(自动封装版)剖析.pdfmybatis分页拦截器(自动封装版)剖析.pdfmybatis分页拦截器(自动封装版)剖析.pdfmybatis分页拦截...

      mybatis分页(struts2+spring+mybatis)

      一个很简单的mybatis分页demo,数据库是MySQL,页面是Bootstrap

      mybatis分页插件的使用

      如果你也在用Mybatis,建议尝试该分页插件,这个一定是最方便使用的分页插件。 该插件目前支持Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六种数据库分页。

      MyBatis分页插件.rar

      本链接主要为MyBatis使用分页插件实现分页所需Jar包

      MyBatis分页功能实现

      一个集成spring的MyBatis的分页功能实现的例子

      MyBatis分页插件-PageHelper

      这是MyBatis的分页插件。解压后,可得到一个【pagehelper】的文件夹。该pagehelper中的pom.xml中的版本为3.4.2-fix。

      基于mysql的数据库mybatis 分页插件

      mybatis 分页 mybatis-generate Mysql数据库 大家知道mybatis自动生成代码是没有分页功能的 我在网上找了很久 有很多内容 但正真可以使用的少之又少 本人整合了网上的资源 整理了基于Mysql数据库的mybatis插件 经...

      mybatis分页spring3.1+struts2

      mybatis分页spring3.1+struts2 测试代码。自己写的。仅供参考。 文章地址 http://blog.csdn.net/fairyhawk/article/details/7787939

      mybatis分页

      mybatis分页完整版,利用sql在动态sql语句,配置的spring,springmvc,mybatis,数据库改成自己的。并且改下自己配置

    Global site tag (gtag.js) - Google Analytics