zoukankan      html  css  js  c++  java
  • Mybatis

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

    原创 2014年04月16日 12:32:24

    分页插件示例: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
     
    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
     
    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
     
    1. // 记录总记录数  
    2.         String countSql = "select count(0) from (" + sql + ")";  

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

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

    [java] view plain copy
     
    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
     
    1. <plugins>  
    2.     <plugin interceptor="com.mybatis.util.PageHelper"></plugin>  
    3. </plugins>  
    配置拦截器的时候需要注意plugins的位置,plugins位置顺序如下:
    [html] view plain copy
     
    1. properties?, settings?, typeAliases?, typeHandlers?, objectFactory?, objectWrapperFactory?, plugins?, environments?, databaseIdProvider?, mappers?  
    最后是调用该方法的例子代码(Service层):
    [java] view plain copy
     
    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的实现,所以我希望参考该分页插件实现的其他数据库的读者也能将相应的代码开源(本人不做要求),如果打算分享,欢迎回复留下地址。

  • 相关阅读:
    代码操作
    购物车
    利息计算器
    生成海报
    知识库
    JavaScript处理字符串--参照W3C
    C#输入排序-冒泡
    enum举例
    C# 表达式计算器----数据结构
    C# 测试单词的完美度
  • 原文地址:https://www.cnblogs.com/firstdream/p/7823007.html
Copyright © 2011-2022 走看看