[转]Mybatis极其(最)简(好)单(用)的一个分页插件

原文地址:http://blog.csdn.net/isea533/article/details/23831273

分页插件示例:http://blog.csdn.net/isea533/article/details/24700339

最新版分页插件:http://blog.csdn.net/isea533/article/details/25505413

项目地址:http://git.oschina.net/free/Mybatis_PageHelper


 

以前为Mybatis分页查询发愁过,而且在网上搜过很多相关的文章,最后一个都没采用。在分页的地方完全都是手写分页SQL和count的sql,总之很麻烦。


后来有一段时间想从Mybatis内部写一个分页的实现,我对LanguageDriver写过一个实现,自动分页是没问题了,但是查询总数(count)仍然没法一次性解决,最后不了了之。


最近又要用到分页,为了方便必须地写个通用的分页类,因此又再次参考网上大多数的Mybatis分页代码,本插件主要参考自:

http://blog.csdn.net/hupanfeng/article/details/9265341


实际上在很早之前,有人在github上开源过一个实现,支持mysql,oracle,sqlserver的,和上面这个参考的比较类似,考虑的 更全面。但是我觉得太多类太麻烦了,所以自己实现了一个只有一个拦截器的类,实际上可以分为两个类,其中一个类被我写成静态类放在了拦截器中,你也可以将 Page类提取出来,方便使用Page。


先说实现方法,该插件只有一个类:PageHelper.java


拦截器签名为:

[java] view plain
  1. @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}),  
  2.         @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})  


这里的签名对整个实现和思想至关重要,首先我拦截prepare方法来改分页SQL,来做count查询。然后我拦截handleResultSets方法来获取最后的处理结果,将结果放到Page对象中。


下面是修改分页的代码,是针对Oracle数据进行的修改,如果有用其他数据库的,自己修改这里的代码就可以。

[java] view plain
  1. /** 
  2.      * 修改原SQL为分页SQL 
  3.      * @param sql 
  4.      * @param page 
  5.      * @return 
  6.      */  
  7.     private String buildPageSql(String sql, Page page) {  
  8.         StringBuilder pageSql = new StringBuilder(200);  
  9.         pageSql.append("select * from ( select temp.*, rownum row_id from ( ");  
  10.         pageSql.append(sql);  
  11.         pageSql.append(" ) temp where rownum <= ").append(page.getEndRow());  
  12.         pageSql.append(") where row_id > ").append(page.getStartRow());  
  13.         return pageSql.toString();  
  14.     }  


之后在下面的setPageParameter方法中一个selelct count语句,这里也需要根据数据库类型进行修改:

[java] view plain
  1. // 记录总记录数  
  2.         String countSql = "select count(0) from (" + sql + ")";  


为什么我不提供对各种数据库的支持呢,我觉得没必要,还有些数据库不支持分页,而且这个插件越简单对使用的开发人员来说越容易理解,越容易修改。修改成自己需要的分页查询肯定不是问题。


最后上完整代码(继续看下去,下面还有使用方法):(点击下载

[java] view plain
  1. package com.mybatis.util;  
  2.   
  3. import org.apache.ibatis.executor.parameter.ParameterHandler;  
  4. import org.apache.ibatis.executor.resultset.ResultSetHandler;  
  5. import org.apache.ibatis.executor.statement.StatementHandler;  
  6. import org.apache.ibatis.mapping.BoundSql;  
  7. import org.apache.ibatis.mapping.MappedStatement;  
  8. import org.apache.ibatis.plugin.*;  
  9. import org.apache.ibatis.reflection.MetaObject;  
  10. import org.apache.ibatis.reflection.SystemMetaObject;  
  11. import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;  
  12. import org.apache.log4j.Logger;  
  13.   
  14. import java.sql.*;  
  15. import java.util.List;  
  16. import java.util.Properties;  
  17.   
  18. /** 
  19.  * Mybatis - 通用分页拦截器 
  20.  * @author liuzh/abel533/isea 
  21.  * Created by liuzh on 14-4-15. 
  22.  */  
  23. @Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class}),  
  24.         @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})})  
  25. public class PageHelper implements Interceptor {  
  26.     private static final Logger logger = Logger.getLogger(PageHelper.class);  
  27.   
  28.     public static final ThreadLocal<Page> localPage = new ThreadLocal<Page>();  
  29.   
  30.     /** 
  31.      * 开始分页 
  32.      * @param pageNum 
  33.      * @param pageSize 
  34.      */  
  35.     public static void startPage(int pageNum, int pageSize) {  
  36.         localPage.set(new Page(pageNum, pageSize));  
  37.     }  
  38.   
  39.     /** 
  40.      * 结束分页并返回结果,该方法必须被调用,否则localPage会一直保存下去,直到下一次startPage 
  41.      * @return 
  42.      */  
  43.     public static Page endPage() {  
  44.         Page page = localPage.get();  
  45.         localPage.remove();  
  46.         return page;  
  47.     }  
  48.   
  49.     @Override  
  50.     public Object intercept(Invocation invocation) throws Throwable {  
  51.         if (localPage.get() == null) {  
  52.             return invocation.proceed();  
  53.         }  
  54.         if (invocation.getTarget() instanceof StatementHandler) {  
  55.             StatementHandler statementHandler = (StatementHandler) invocation.getTarget();  
  56.             MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);  
  57.             // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环  
  58.             // 可以分离出最原始的的目标类)  
  59.             while (metaStatementHandler.hasGetter("h")) {  
  60.                 Object object = metaStatementHandler.getValue("h");  
  61.                 metaStatementHandler = SystemMetaObject.forObject(object);  
  62.             }  
  63.             // 分离最后一个代理对象的目标类  
  64.             while (metaStatementHandler.hasGetter("target")) {  
  65.                 Object object = metaStatementHandler.getValue("target");  
  66.                 metaStatementHandler = SystemMetaObject.forObject(object);  
  67.             }  
  68.             MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");  
  69.             //分页信息if (localPage.get() != null) {  
  70.             Page page = localPage.get();  
  71.             BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");  
  72.             // 分页参数作为参数对象parameterObject的一个属性  
  73.             String sql = boundSql.getSql();  
  74.             // 重写sql  
  75.             String pageSql = buildPageSql(sql, page);  
  76.             //重写分页sql  
  77.             metaStatementHandler.setValue("delegate.boundSql.sql", pageSql);  
  78.             Connection connection = (Connection) invocation.getArgs()[0];  
  79.             // 重设分页参数里的总页数等  
  80.             setPageParameter(sql, connection, mappedStatement, boundSql, page);  
  81.             // 将执行权交给下一个拦截器  
  82.             return invocation.proceed();  
  83.         } else if (invocation.getTarget() instanceof ResultSetHandler) {  
  84.             Object result = invocation.proceed();  
  85.             Page page = localPage.get();  
  86.             page.setResult((List) result);  
  87.             return result;  
  88.         }  
  89.         return null;  
  90.     }  
  91.   
  92.     /** 
  93.      * 只拦截这两种类型的 
  94.      * <br>StatementHandler 
  95.      * <br>ResultSetHandler 
  96.      * @param target 
  97.      * @return 
  98.      */  
  99.     @Override  
  100.     public Object plugin(Object target) {  
  101.         if (target instanceof StatementHandler || target instanceof ResultSetHandler) {  
  102.             return Plugin.wrap(target, this);  
  103.         } else {  
  104.             return target;  
  105.         }  
  106.     }  
  107.   
  108.     @Override  
  109.     public void setProperties(Properties properties) {  
  110.   
  111.     }  
  112.   
  113.     /** 
  114.      * 修改原SQL为分页SQL 
  115.      * @param sql 
  116.      * @param page 
  117.      * @return 
  118.      */  
  119.     private String buildPageSql(String sql, Page page) {  
  120.         StringBuilder pageSql = new StringBuilder(200);  
  121.         pageSql.append("select * from ( select temp.*, rownum row_id from ( ");  
  122.         pageSql.append(sql);  
  123.         pageSql.append(" ) temp where rownum <= ").append(page.getEndRow());  
  124.         pageSql.append(") where row_id > ").append(page.getStartRow());  
  125.         return pageSql.toString();  
  126.     }  
  127.   
  128.     /** 
  129.      * 获取总记录数 
  130.      * @param sql 
  131.      * @param connection 
  132.      * @param mappedStatement 
  133.      * @param boundSql 
  134.      * @param page 
  135.      */  
  136.     private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement,  
  137.                                   BoundSql boundSql, Page page) {  
  138.         // 记录总记录数  
  139.         String countSql = "select count(0) from (" + sql + ")";  
  140.         PreparedStatement countStmt = null;  
  141.         ResultSet rs = null;  
  142.         try {  
  143.             countStmt = connection.prepareStatement(countSql);  
  144.             BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql,  
  145.                     boundSql.getParameterMappings(), boundSql.getParameterObject());  
  146.             setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject());  
  147.             rs = countStmt.executeQuery();  
  148.             int totalCount = 0;  
  149.             if (rs.next()) {  
  150.                 totalCount = rs.getInt(1);  
  151.             }  
  152.             page.setTotal(totalCount);  
  153.             int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1);  
  154.             page.setPages(totalPage);  
  155.         } catch (SQLException e) {  
  156.             logger.error("Ignore this exception", e);  
  157.         } finally {  
  158.             try {  
  159.                 rs.close();  
  160.             } catch (SQLException e) {  
  161.                 logger.error("Ignore this exception", e);  
  162.             }  
  163.             try {  
  164.                 countStmt.close();  
  165.             } catch (SQLException e) {  
  166.                 logger.error("Ignore this exception", e);  
  167.             }  
  168.         }  
  169.     }  
  170.   
  171.     /** 
  172.      * 代入参数值 
  173.      * @param ps 
  174.      * @param mappedStatement 
  175.      * @param boundSql 
  176.      * @param parameterObject 
  177.      * @throws SQLException 
  178.      */  
  179.     private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql,  
  180.                                Object parameterObject) throws SQLException {  
  181.         ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);  
  182.         parameterHandler.setParameters(ps);  
  183.     }  
  184.   
  185.     /** 
  186.      * Description: 分页 
  187.      * Author: liuzh 
  188.      * Update: liuzh(2014-04-16 10:56) 
  189.      */  
  190.     public static class Page<E> {  
  191.         private int pageNum;  
  192.         private int pageSize;  
  193.         private int startRow;  
  194.         private int endRow;  
  195.         private long total;  
  196.         private int pages;  
  197.         private List<E> result;  
  198.   
  199.         public Page(int pageNum, int pageSize) {  
  200.             this.pageNum = pageNum;  
  201.             this.pageSize = pageSize;  
  202.             this.startRow = pageNum > 0 ? (pageNum - 1) * pageSize : 0;  
  203.             this.endRow = pageNum * pageSize;  
  204.         }  
  205.   
  206.         public List<E> getResult() {  
  207.             return result;  
  208.         }  
  209.   
  210.         public void setResult(List<E> result) {  
  211.             this.result = result;  
  212.         }  
  213.   
  214.         public int getPages() {  
  215.             return pages;  
  216.         }  
  217.   
  218.         public void setPages(int pages) {  
  219.             this.pages = pages;  
  220.         }  
  221.   
  222.         public int getEndRow() {  
  223.             return endRow;  
  224.         }  
  225.   
  226.         public void setEndRow(int endRow) {  
  227.             this.endRow = endRow;  
  228.         }  
  229.   
  230.         public int getPageNum() {  
  231.             return pageNum;  
  232.         }  
  233.   
  234.         public void setPageNum(int pageNum) {  
  235.             this.pageNum = pageNum;  
  236.         }  
  237.   
  238.         public int getPageSize() {  
  239.             return pageSize;  
  240.         }  
  241.   
  242.         public void setPageSize(int pageSize) {  
  243.             this.pageSize = pageSize;  
  244.         }  
  245.   
  246.         public int getStartRow() {  
  247.             return startRow;  
  248.         }  
  249.   
  250.         public void setStartRow(int startRow) {  
  251.             this.startRow = startRow;  
  252.         }  
  253.   
  254.         public long getTotal() {  
  255.             return total;  
  256.         }  
  257.   
  258.         public void setTotal(long total) {  
  259.             this.total = total;  
  260.         }  
  261.   
  262.         @Override  
  263.         public String toString() {  
  264.             return "Page{" +  
  265.                     "pageNum=" + pageNum +  
  266.                     ", pageSize=" + pageSize +  
  267.                     ", startRow=" + startRow +  
  268.                     ", endRow=" + endRow +  
  269.                     ", total=" + total +  
  270.                     ", pages=" + pages +  
  271.                     '}';  
  272.         }  
  273.     }  
  274. }  


使用该拦截器首先需要在Mybatis配置中配置该拦截器:

[html] view plain
  1. <plugins>  
  2.     <plugin interceptor="com.mybatis.util.PageHelper"></plugin>  
  3. </plugins>  

配置拦截器的时候需要注意plugins的位置,plugins位置顺序如下:

[html] view plain
  1. properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, plugins?, environments?, databaseIdProvider?, mappers?  


最后是调用该方法的例子代码(Service层):

[java] view plain
  1. @Override  
  2. public PageHelper.Page<SysLoginLog> findSysLoginLog(String loginIp,  
  3.                                          String username,  
  4.                                          String loginDate,  
  5.                                          String exitDate,  
  6.                                          String logerr,  
  7.                                          int pageNumber,  
  8.                                          int pageSize) throws BusinessException {  
  9.     PageHelper.startPage(pageNumber,pageSize);  
  10.     sysLoginLogMapper.findSysLoginLog(loginIp, username, loginDate, exitDate, logerr);  
  11.     return PageHelper.endPage();  
  12. }  

 

从上面可以看到使用该插件使用起来是很简单的,只需要在查询前后使用PageHelper的startPage和endPage方法即可,中间代码 的调用结果已经存在于Page的result中,如果你在一个返回一个结果的地方调用PageHelper,返回的结果仍然是一个List,取第一个值即 可(我想没人会在这种地方这么用,当然这样也不出错)。

另外在startPage和endPage中间的所有mybatis代码都会被分页,而且PageHelper只会保留最后一次的结果,因而使用时需要保证每次只在其中执行一个mybatis查询,如果有多个分页,请多次使用startPage和endPage。

posted @ 2016-02-14 22:09  dirgo  阅读(368)  评论(0编辑  收藏  举报