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

分页插件示例: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 copy 在CODE上查看代码片 派生到我的代码片
  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 copy 在CODE上查看代码片 派生到我的代码片
  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 copy 在CODE上查看代码片 派生到我的代码片
  1. // 记录总记录数  
  2.         String countSql = "select count(0) from (" + sql + ")";  

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


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

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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 copy 在CODE上查看代码片 派生到我的代码片
  1. <plugins>  
  2.     <plugin interceptor="com.mybatis.util.PageHelper"></plugin>  
  3. </plugins>  
配置拦截器的时候需要注意plugins的位置,plugins位置顺序如下:

[html]  view plain copy 在CODE上查看代码片 派生到我的代码片
  1. properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, plugins?, environments?, databaseIdProvider?, mappers?  


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

[java]  view plain copy 在CODE上查看代码片 派生到我的代码片
  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。



由于这里只提供了Oracle的实现,所以我希望参考该分页插件实现的其他数据库的读者也能将相应的代码开源(本人不做要求),如果打算分享,欢迎回复留下地址。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值