`
weitao1026
  • 浏览: 1047953 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

Mybatis-分页

阅读更多
Mybatis的分页功能很弱,它是基于内存的分页(查出所有记录再按偏移量和limit取结果),在大数据量的情况下这样的分页基本上是没有用的。本文基于插件,通过拦截StatementHandler重写sql语句,实现数据库的物理分页。本文适配的mybatis版本是3.2.2。
为什么在StatementHandler拦截

在深入浅出MyBatis-Sqlsession章节介绍了一次sqlsession的完整执行过程,从中可以知道sql的解析是在StatementHandler里完成的,所以为了重写sql需要拦截StatementHandler。

MetaObject简介

在我的实现里大量使用了MetaObject这个对象,因此有必要先介绍下它。MetaObject是Mybatis提供的一个的工具类,通过它包装一个对象后可以获取或设置该对象的原本不可访问的属性(比如那些私有属性)。它有个三个重要方法经常用到:

1)       MetaObject forObject(Object object,ObjectFactory objectFactory, ObjectWrapperFactory objectWrapperFactory)

2)       Object getValue(String name)

3)       void setValue(String name, Object value)

方法1)用于包装对象;方法2)用于获取属性的值(支持OGNL的方法);方法3)用于设置属性的值(支持OGNL的方法);

插件的原理
拦截器签名



[java] view plaincopy
01.@Intercepts({@Signature(type =StatementHandler.class, method = "prepare", args ={Connection.class})}) 
02.publicclass PageInterceptor implementsInterceptor { 
03.... 
04.} 


从签名里可以看出,要拦截的目标类型是StatementHandler(注意:type只能配置成接口类型),拦截的方法是名称为prepare参数为Connection类型的方法。

intercept的实现



[java] view plaincopy
01.public Object intercept(Invocation invocation) throws Throwable { 
02.     StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); 
03.     MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, 
04.     DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY); 
05.     // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环 
06.     // 可以分离出最原始的的目标类) 
07.     while (metaStatementHandler.hasGetter("h")) { 
08.         Object object = metaStatementHandler.getValue("h"); 
09.         metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY,  
10.         DEFAULT_OBJECT_WRAPPER_FACTORY); 
11.     } 
12.     // 分离最后一个代理对象的目标类 
13.     while (metaStatementHandler.hasGetter("target")) { 
14.         Object object = metaStatementHandler.getValue("target"); 
15.         metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY,  
16.         DEFAULT_OBJECT_WRAPPER_FACTORY); 
17.     } 
18.     Configuration configuration = (Configuration) metaStatementHandler. 
19.     getValue("delegate.configuration"); 
20.     dialect = configuration.getVariables().getProperty("dialect"); 
21.     if (null == dialect || "".equals(dialect)) { 
22.         logger.warn("Property dialect is not setted,use default 'mysql' "); 
23.         dialect = defaultDialect; 
24.     } 
25.     pageSqlId = configuration.getVariables().getProperty("pageSqlId"); 
26.     if (null == pageSqlId || "".equals(pageSqlId)) { 
27.         logger.warn("Property pageSqlId is not setted,use default '.*Page$' "); 
28.         pageSqlId = defaultPageSqlId; 
29.     } 
30.     MappedStatement mappedStatement = (MappedStatement)  
31.     metaStatementHandler.getValue("delegate.mappedStatement"); 
32.     // 只重写需要分页的sql语句。通过MappedStatement的ID匹配,默认重写以Page结尾的 
33.     //  MappedStatement的sql 
34.     if (mappedStatement.getId().matches(pageSqlId)) { 
35.         BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql"); 
36.         Object parameterObject = boundSql.getParameterObject(); 
37.         if (parameterObject == null) { 
38.             throw new NullPointerException("parameterObject is null!"); 
39.         } else { 
40.             // 分页参数作为参数对象parameterObject的一个属性 
41.             PageParameter page = (PageParameter) metaStatementHandler 
42.                     .getValue("delegate.boundSql.parameterObject.page"); 
43.             String sql = boundSql.getSql(); 
44.             // 重写sql 
45.             String pageSql = buildPageSql(sql, page); 
46.             metaStatementHandler.setValue("delegate.boundSql.sql", pageSql); 
47.             // 采用物理分页后,就不需要mybatis的内存分页了,所以重置下面的两个参数 
48.             metaStatementHandler.setValue("delegate.rowBounds.offset",  
49.             RowBounds.NO_ROW_OFFSET); 
50.             metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT); 
51.             Connection connection = (Connection) invocation.getArgs()[0]; 
52.             // 重设分页参数里的总页数等 
53.             setPageParameter(sql, connection, mappedStatement, boundSql, page); 
54.         } 
55.     } 
56.     // 将执行权交给下一个拦截器 
57.     return invocation.proceed(); 
58. } 


StatementHandler的默认实现类是RoutingStatementHandler,因此拦截的实际对象是它。RoutingStatementHandler的主要功能是分发,它根据配置Statement类型创建真正执行数据库操作的StatementHandler,并将其保存到delegate属性里。由于delegate是一个私有属性并且没有提供访问它的方法,因此需要借助MetaObject的帮忙。通过MetaObject的封装后我们可以轻易的获得想要的属性。

在上面的方法里有个两个循环,通过他们可以分离出原始的RoutingStatementHandler(而不是代理对象)。

前面提到,签名里配置的要拦截的目标类型是StatementHandler拦截的方法是名称为prepare参数为Connection类型的方法,而这个方法是每次数据库访问都要执行的。因为我是通过重写sql的方式实现分页,为了不影响其他sql(update或不需要分页的query),我采用了通过ID匹配的方式过滤。默认的过滤方式只对id以Page结尾的进行拦截(注意区分大小写),如下:



[html] view plaincopy
01.<select id="queryUserByPage" parameterType="UserDto" resultType="UserDto"> 
02.    <![CDATA[
03.    select * from t_user t where t.username = #{username}
04.    ]]> 
05.</select> 


当然,也可以自定义拦截模式,在mybatis的配置文件里加入以下配置项:



[html] view plaincopy
01.<properties> 
02.    <property name="dialect" value="mysql" /> 
03.    <property name="pageSqlId" value=".*Page$" /> 
04.</properties> 


其中,属性dialect指示数据库类型,目前只支持mysql和oracle两种数据库。其中,属性pageSqlId指示拦截的规则,以正则方式匹配。

sql重写

sql重写其实在原始的sql语句上加入分页的参数,目前支持mysql和oracle两种数据库的分页。



[java] view plaincopy
01.private String buildPageSql(String sql, PageParameter page) { 
02.    if (page != null) { 
03.        StringBuilder pageSql = new StringBuilder(); 
04.        if ("mysql".equals(dialect)) { 
05.            pageSql = buildPageSqlForMysql(sql, page); 
06.        } else if ("oracle".equals(dialect)) { 
07.            pageSql = buildPageSqlForOracle(sql, page); 
08.        } else { 
09.            return sql; 
10.        } 
11.        return pageSql.toString(); 
12.    } else { 
13.        return sql; 
14.    } 
15.} 


mysql的分页实现:



[java] view plaincopy
01.public StringBuilder buildPageSqlForMysql(String sql, PageParameter page) { 
02.    StringBuilder pageSql = new StringBuilder(100); 
03.    String beginrow = String.valueOf((page.getCurrentPage() - 1) * page.getPageSize()); 
04.    pageSql.append(sql); 
05.    pageSql.append(" limit " + beginrow + "," + page.getPageSize()); 
06.    return pageSql; 
07.} 


oracle的分页实现:



[java] view plaincopy
01.public StringBuilder buildPageSqlForOracle(String sql, PageParameter page) { 
02.    StringBuilder pageSql = new StringBuilder(100); 
03.    String beginrow = String.valueOf((page.getCurrentPage() - 1) * page.getPageSize()); 
04.    String endrow = String.valueOf(page.getCurrentPage() * page.getPageSize()); 
05.    pageSql.append("select * from ( select temp.*, rownum row_id from ( "); 
06.    pageSql.append(sql); 
07.    pageSql.append(" ) temp where rownum <= ").append(endrow); 
08.    pageSql.append(") where row_id > ").append(beginrow); 
09.    return pageSql; 
10.} 


分页参数重写

有时候会有这种需求,就是不但要查出指定页的结果,还需要知道总的记录数和页数。我通过重写分页参数的方式提供了一种解决方案:





[java] view plaincopy
01./**
02. * 从数据库里查询总的记录数并计算总页数,回写进分页参数<code>PageParameter</code>,这样调用 
03. * 者就可用通过 分页参数<code>PageParameter</code>获得相关信息。
04. * 
05. * @param sql
06. * @param connection
07. * @param mappedStatement
08. * @param boundSql
09. * @param page
10. */ 
11.private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement, 
12.        BoundSql boundSql, PageParameter page) { 
13.    // 记录总记录数 
14.    String countSql = "select count(0) from (" + sql + ") as total"; 
15.    PreparedStatement countStmt = null; 
16.    ResultSet rs = null; 
17.    try { 
18.        countStmt = connection.prepareStatement(countSql); 
19.        BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, 
20.                boundSql.getParameterMappings(), boundSql.getParameterObject()); 
21.        setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject()); 
22.        rs = countStmt.executeQuery(); 
23.        int totalCount = 0; 
24.        if (rs.next()) { 
25.            totalCount = rs.getInt(1); 
26.        } 
27.        page.setTotalCount(totalCount); 
28.        int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1); 
29.        page.setTotalPage(totalPage); 
30.    } catch (SQLException e) { 
31.        logger.error("Ignore this exception", e); 
32.    } finally { 
33.        try { 
34.            rs.close(); 
35.        } catch (SQLException e) { 
36.            logger.error("Ignore this exception", e); 
37.        } 
38.        try { 
39.            countStmt.close(); 
40.        } catch (SQLException e) { 
41.            logger.error("Ignore this exception", e); 
42.        } 
43.    } 
44.} 
45. 
46./**
47. * 对SQL参数(?)设值
48. * 
49. * @param ps
50. * @param mappedStatement
51. * @param boundSql
52. * @param parameterObject
53. * @throws SQLException
54. */ 
55.private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, 
56.        Object parameterObject) throws SQLException { 
57.    ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql); 
58.    parameterHandler.setParameters(ps); 
59.} 




plugin的实现



[java] view plaincopy
01.public Object plugin(Object target) { 
02.    // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的 
03.    // 次数 
04.    if (target instanceof StatementHandler) { 
05.        return Plugin.wrap(target, this); 
06.    } else { 
07.        return target; 
08.    } 
09.} 
分享到:
评论

相关推荐

    MyBatis-Plus 的官方示例(mybatis-plus-samples-master.zip)

    mybatis-plus-sample-pagination: 分页功能示例 mybatis-plus-sample-active-record: ActiveRecord示例 mybatis-plus-sample-sequence: Sequence示例 mybatis-plus-sample-execution-analysis: Sql执行分析示例

    MyBatis-Plus入门+MyBatis-Plus文档手册 中文pdf高清版.rar

    特性:无侵入、损耗小、强大的CRUD操作,支持lambda 形势调用、支持多种数据库,支持主键自动生成、支持ActiveRecord模式,支持自定义全局通用操作、支持关键词自动转义,内置代码生成器、内置分页插件、内置性能...

    mybatis-plus源码(mybatis-plus-3.5.1.zip)

    6. **分页插件**:集成PageHelper分页插件,可以方便地实现物理分页和逻辑分页。 7. **乐观锁**:通过在表中增加一个版本号字段,实现乐观锁,避免并发问题。 8. **自定义全局配置**:可以全局配置一些参数,如...

    mybatis-plug.jar和 mybatis-plug的安装说明

    3. **内置高性能分页插件**:支持多种数据库的高效分页,提高查询性能。 4. **条件构造器**:通过Java对象动态构建SQL,使得SQL语句更加灵活。 5. **实体关系映射**:支持一对多、多对一、多对多等复杂关系的映射。 ...

    mybatis-plus 实践及架构原理

    Mybatis-Plus是一个Mybatis的增强工具,在保留Mybatis原有特性的同时,提供了很多便捷的功能,如代码生成器、分页插件等,极大地提高了开发的效率,特别是在处理单表操作时。 2. Mybatis-Plus的核心功能: - 自动...

    springboot整合mybatis-plus实现多表分页查询

    在本文中,我们将深入探讨如何在SpringBoot项目中整合MyBatis-Plus,实现多表分页查询。MyBatis-Plus是一个强大的MyBatis扩展,简化了对数据库的操作,包括CRUD操作以及复杂的关联查询。它提供了丰富的API,使得开发...

    mybatis-pagination-master

    《MyBatis分页插件mybatis-pagination深度解析》 在大数据量的Web应用中,分页查询是不可或缺的一部分,它能有效提高系统的性能并优化用户体验。MyBatis作为一款强大的持久层框架,虽然提供了基本的SQL映射功能,但...

    mybatis-plus快速入门项目-mybatis-plus基本操作大全-附带sql+项目文档

    虽然MyBatis-Plus自身不包含分页功能,但可以配合PageHelper插件实现高效的分页查询。只需简单配置,即可实现前端和后端的分页效果。 9. **乐观锁与版本号** MyBatis-Plus支持乐观锁机制,通过在实体类中添加...

    SpringBoot集成MyBatis-Plus实现国产数据库适配.docx

    * 内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询 * 分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQL...

    MyBatis-Flex: 一个优雅的 MyBatis 增强框架

    只增强,MyBatis-Flex 支持 CRUD、分页查询、多表查询、批量操作,但不丢失 MyBatis 原有的任何功能。高性能,MyBatis-Flex 采用独特的技术架构、相比许多同类框架,MyBatis-Flex 的在增删改查等方面的性能均超越其 ...

    mybatis-plus源码(mybatis-plus-3.5.1.tar.gz)

    - MyBatis-Plus内置了通用分页插件,支持多种数据库的分页方式,如Page对象的使用,可以在查询时自动完成分页操作。 6. **Lambda表达式支持**: - 自3.x版本开始,MyBatis-Plus引入了对Java 8 Lambda表达式的支持...

    mybatis 物理分页,借助于mybatis-paginator插件

    MyBatis,作为一个轻量级的Java持久层框架,虽然提供了强大的映射功能,但默认并不支持高效的物理分页。这时,我们就需要借助于第三方插件来实现这一功能,例如`mybatis-paginator`。这个插件专门为MyBatis设计,...

    spring + springmvc + mybatis 整合 及 mybatis-pagehelper分页

    4. **PageHelper分页插件**:MyBatis-PageHelper是一个强大的分页插件,它可以自动处理分页逻辑,简化开发。在Spring的配置文件中引入PageHelper的配置,包括其初始化参数,如dialect(数据库类型)、reasonable...

    mybatis-plus 源码(mybatis-plus-3.0.zip)

    7. **分页插件PageHelper**:MyBatis-Plus 3.0集成了PageHelper分页插件,可以方便地实现高效的分页查询。 8. **ID生成策略**:支持自增ID、雪花算法、UUID等多种主键生成策略,通过@TableId注解指定。 9. **逻辑...

    mybatis-plus分页查询的实现示例.docx

    以下我们将详细介绍如何使用 MyBatis-Plus 实现分页查询。 1. **添加依赖** 在项目中使用 MyBatis-Plus 进行分页查询,首先需要引入相应的依赖。在 Maven 的 `pom.xml` 文件中添加如下内容: ```xml &lt;groupId&gt;...

    基于spring boot 2集成mybatis-plus的简单实例

    MyBatis-Plus是一个强大的MyBatis扩展,它简化了数据库操作,提供了诸如CRUD操作、条件查询、分页等功能。 首先,我们需要在Spring Boot项目中添加MyBatis-Plus的依赖。打开`pom.xml`文件,加入以下依赖: ```xml ...

    mybatis-plus案例

    内置的Page对象提供了分页查询的支持,只需要传入当前页和每页数量,Mybatis-Plus会自动处理分页SQL,返回包含总记录数和分页结果的对象。 9. **自定义拦截器** 如果有特定的SQL优化需求,Mybatis-Plus允许我们...

    mybatis-plus-demo

    6. **分页插件**:MyBatis-Plus 内置了分页插件,可以帮助我们在不改变原有代码结构的情况下实现高效的分页查询。 7. **其他高级特性**:如逻辑删除、填充公共字段、自定义全局操作等,这些特性让 MyBatis-Plus ...

    mybatis-plus-doc-master.zip

    在分页功能上,MyBatis-Plus 内置了 Page 对象,它可以方便地实现分页查询。只需要传入当前页和每页的数量,框架会自动处理分页逻辑。 除此之外,文档还会涵盖事务管理、自定义拦截器、填充公共字段、多租户支持、...

    mybatis-plus-demo.zip

    MyBatis-Plus 是在 MyBatis 基础上构建的,它包含了 MyBatis 的所有功能,并添加了更多的实用特性,如自动化 CRUD 操作、条件构造器、分页插件等。这使得开发者可以更专注于业务逻辑,而无需过多关注底层的 SQL ...

Global site tag (gtag.js) - Google Analytics