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

mybatis 插件分页

    博客分类:
  • java
阅读更多
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的方法);

插件的原理
参见深入浅出Mybatis-插件原理。



有了上面这些基础知识的准备后,就可以我们的主题了。

拦截器签名
[java] view plaincopy
@Intercepts({@Signature(type =StatementHandler.class, method = "prepare", args ={Connection.class})}) 
publicclass PageInterceptor implementsInterceptor { 
... 

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

intercept的实现
[java] view plaincopy
public Object intercept(Invocation invocation) throws Throwable { 
     StatementHandler statementHandler = (StatementHandler) invocation.getTarget(); 
     MetaObject metaStatementHandler = MetaObject.forObject(statementHandler, 
     DEFAULT_OBJECT_FACTORY, DEFAULT_OBJECT_WRAPPER_FACTORY); 
     // 分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过下面的两次循环 
     // 可以分离出最原始的的目标类) 
     while (metaStatementHandler.hasGetter("h")) { 
         Object object = metaStatementHandler.getValue("h"); 
         metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY,  
         DEFAULT_OBJECT_WRAPPER_FACTORY); 
     } 
     // 分离最后一个代理对象的目标类 
     while (metaStatementHandler.hasGetter("target")) { 
         Object object = metaStatementHandler.getValue("target"); 
         metaStatementHandler = MetaObject.forObject(object, DEFAULT_OBJECT_FACTORY,  
         DEFAULT_OBJECT_WRAPPER_FACTORY); 
     } 
     Configuration configuration = (Configuration) metaStatementHandler. 
     getValue("delegate.configuration"); 
     dialect = configuration.getVariables().getProperty("dialect"); 
     if (null == dialect || "".equals(dialect)) { 
         logger.warn("Property dialect is not setted,use default 'mysql' "); 
         dialect = defaultDialect; 
     } 
     pageSqlId = configuration.getVariables().getProperty("pageSqlId"); 
     if (null == pageSqlId || "".equals(pageSqlId)) { 
         logger.warn("Property pageSqlId is not setted,use default '.*Page$' "); 
         pageSqlId = defaultPageSqlId; 
     } 
     MappedStatement mappedStatement = (MappedStatement)  
     metaStatementHandler.getValue("delegate.mappedStatement"); 
     // 只重写需要分页的sql语句。通过MappedStatement的ID匹配,默认重写以Page结尾的 
     //  MappedStatement的sql 
     if (mappedStatement.getId().matches(pageSqlId)) { 
         BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql"); 
         Object parameterObject = boundSql.getParameterObject(); 
         if (parameterObject == null) { 
             throw new NullPointerException("parameterObject is null!"); 
         } else { 
             // 分页参数作为参数对象parameterObject的一个属性 
             PageParameter page = (PageParameter) metaStatementHandler 
                     .getValue("delegate.boundSql.parameterObject.page"); 
             String sql = boundSql.getSql(); 
             // 重写sql 
             String pageSql = buildPageSql(sql, page); 
             metaStatementHandler.setValue("delegate.boundSql.sql", pageSql); 
             // 采用物理分页后,就不需要mybatis的内存分页了,所以重置下面的两个参数 
             metaStatementHandler.setValue("delegate.rowBounds.offset",  
             RowBounds.NO_ROW_OFFSET); 
             metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT); 
             Connection connection = (Connection) invocation.getArgs()[0]; 
             // 重设分页参数里的总页数等 
             setPageParameter(sql, connection, mappedStatement, boundSql, page); 
         } 
     } 
     // 将执行权交给下一个拦截器 
     return invocation.proceed(); 

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

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

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

[html] view plaincopy
<select id="queryUserByPage" parameterType="UserDto" resultType="UserDto"> 
    <![CDATA[
    select * from t_user t where t.username = #{username}
    ]]> 
</select> 
当然,也可以自定义拦截模式,在mybatis的配置文件里加入以下配置项:

[html] view plaincopy
<properties> 
    <property name="dialect" value="mysql" /> 
    <property name="pageSqlId" value=".*Page$" /> 
</properties> 
其中,属性dialect指示数据库类型,目前只支持mysql和oracle两种数据库。其中,属性pageSqlId指示拦截的规则,以正则方式匹配。

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

[java] view plaincopy
private String buildPageSql(String sql, PageParameter page) { 
    if (page != null) { 
        StringBuilder pageSql = new StringBuilder(); 
        if ("mysql".equals(dialect)) { 
            pageSql = buildPageSqlForMysql(sql, page); 
        } else if ("oracle".equals(dialect)) { 
            pageSql = buildPageSqlForOracle(sql, page); 
        } else { 
            return sql; 
        } 
        return pageSql.toString(); 
    } else { 
        return sql; 
    } 

mysql的分页实现:

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

oracle的分页实现:

[java] view plaincopy
public StringBuilder buildPageSqlForOracle(String sql, PageParameter page) { 
    StringBuilder pageSql = new StringBuilder(100); 
    String beginrow = String.valueOf((page.getCurrentPage() - 1) * page.getPageSize()); 
    String endrow = String.valueOf(page.getCurrentPage() * page.getPageSize()); 
    pageSql.append("select * from ( select temp.*, rownum row_id from ( "); 
    pageSql.append(sql); 
    pageSql.append(" ) temp where rownum <= ").append(endrow); 
    pageSql.append(") where row_id > ").append(beginrow); 
    return pageSql; 

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


[java] view plaincopy
/**
* 从数据库里查询总的记录数并计算总页数,回写进分页参数<code>PageParameter</code>,这样调用 
* 者就可用通过 分页参数<code>PageParameter</code>获得相关信息。

* @param sql
* @param connection
* @param mappedStatement
* @param boundSql
* @param page
*/ 
private void setPageParameter(String sql, Connection connection, MappedStatement mappedStatement, 
        BoundSql boundSql, PageParameter page) { 
    // 记录总记录数 
    String countSql = "select count(0) from (" + sql + ") as total"; 
    PreparedStatement countStmt = null; 
    ResultSet rs = null; 
    try { 
        countStmt = connection.prepareStatement(countSql); 
        BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(), countSql, 
                boundSql.getParameterMappings(), boundSql.getParameterObject()); 
        setParameters(countStmt, mappedStatement, countBS, boundSql.getParameterObject()); 
        rs = countStmt.executeQuery(); 
        int totalCount = 0; 
        if (rs.next()) { 
            totalCount = rs.getInt(1); 
        } 
        page.setTotalCount(totalCount); 
        int totalPage = totalCount / page.getPageSize() + ((totalCount % page.getPageSize() == 0) ? 0 : 1); 
        page.setTotalPage(totalPage); 
    } catch (SQLException e) { 
        logger.error("Ignore this exception", e); 
    } finally { 
        try { 
            rs.close(); 
        } catch (SQLException e) { 
            logger.error("Ignore this exception", e); 
        } 
        try { 
            countStmt.close(); 
        } catch (SQLException e) { 
            logger.error("Ignore this exception", e); 
        } 
    } 

 
/**
* 对SQL参数(?)设值

* @param ps
* @param mappedStatement
* @param boundSql
* @param parameterObject
* @throws SQLException
*/ 
private void setParameters(PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, 
        Object parameterObject) throws SQLException { 
    ParameterHandler parameterHandler = new DefaultParameterHandler(mappedStatement, parameterObject, boundSql); 
    parameterHandler.setParameters(ps); 

plugin的实现
[java] view plaincopy
public Object plugin(Object target) { 
    // 当目标类是StatementHandler类型时,才包装目标类,否者直接返回目标本身,减少目标被代理的 
    // 次数 
    if (target instanceof StatementHandler) { 
        return Plugin.wrap(target, this); 
    } else { 
        return target; 
    } 


来源:http://blog.csdn.net/hupanfeng/article/details/9265341
分享到:
评论

相关推荐

    mybatis插件分页测试

    在"mybatis插件分页测试"中,我们将关注以下关键知识点: 1. **插件配置**:在MyBatis的配置文件(mybatis-config.xml)中,我们需要声明我们的分页插件,指定拦截的目标方法和插件类。例如,可以使用PageHelper...

    06实现mybatis分页插件demo

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

    ssm三层整合 mybatis插件分页查询

    MyBatis支持多种分页查询方式,如PageHelper插件、自定义分页拦截器或者手动实现分页。PageHelper是最常用的分页插件,通过简单配置即可实现分页功能: 1. 添加PageHelper依赖并配置相关属性,如dialect(数据库类型...

    mybatis物理分页插件

    而“mybatis物理分页插件”是针对MyBatis设计的一个扩展,用于解决在大数据量查询时的性能问题,通过实现物理分页来避免内存溢出。 物理分页是指在数据库层面进行分页,相比于逻辑分页(在应用层进行数据截取),...

    Mybatis通用分页插件

    Mybatis通用分页插件是Java开发中广泛使用的ORM(对象关系映射)框架扩展,主要针对Mybatis进行优化,提供了高效便捷的分页功能。这个插件的目的是简化在数据库查询时的分页操作,使得开发者能够更专注于业务逻辑,...

    mybatis分页插件代码

    【标题】"mybatis分页插件代码"主要涉及到MyBatis框架中的一种增强功能——分页插件的使用。MyBatis是一个优秀的持久层框架,它支持定制化SQL、存储过程以及高级映射。在处理大数据量时,分页查询是必不可少的优化...

    mybatis的分页插件

    MyBatis 分页插件是针对 MyBatis 框架设计的一款强大的辅助工具,它极大地简化了在数据库查询时的分页操作。在没有分页插件的情况下,开发者需要手动编写分页相关的 SQL 语句,这既繁琐又容易出错。而 PageHelper ...

    mybatis平台包 集成分页插件

    mybatis集成了分页的插件,采用springmvc+spring+mybatis或者springboot+mybatis的时候可以无缝对接使用

    Jsp+Servlet+MyBatis完成分页查询

    在本文中,我们将深入探讨如何使用JSP、Servlet和MyBatis这三种技术来实现一个分页查询的功能。这是一个常见的需求,在许多Web应用程序中,为了提高用户体验,通常需要将大量数据分批次展示,而不是一次性加载所有...

    mybatis分页插件支持查询

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

    Spring Boot集成MyBatis与分页插件

    本教程将详细讲解如何在Spring Boot项目中集成MyBatis,并利用分页插件实现高效的数据分页。 首先,我们需要在Spring Boot项目中引入MyBatis依赖。在`pom.xml`文件中添加以下Maven依赖: ```xml &lt;groupId&gt;org....

    mybatis分页插件的使用

    ### Mybatis分页插件详解 #### 一、概述 在使用Mybatis处理大量数据时,分页查询是一项常见的需求。传统的做法是在SQL语句中手动加入`LIMIT`和`OFFSET`来实现分页功能,这种方法不仅繁琐而且容易出错。针对这一...

    springboot项目(四)添加mybatis分页插件

    在本篇中,我们将深入探讨如何在Spring Boot项目中集成MyBatis分页插件,以便为数据库查询提供高效、便捷的分页功能。Spring Boot以其简洁的配置和快速的开发体验深受开发者喜爱,而MyBatis作为轻量级持久层框架,与...

    MyBatis分页插件.rar

    MyBatis分页插件是数据库操作中常用的一种工具,它可以帮助开发者在使用MyBatis框架进行数据查询时实现高效且便捷的分页功能。在Java Web开发中,当需要处理大量的数据时,分页显示不仅可以提高用户体验,也能减轻...

    mybatis分页插件源码

    MyBatis 分页插件是数据库操作中常用的一个工具,它极大地简化了在MyBatis框架下实现分页查询的工作。这个源码分享主要针对MySQL和Oracle两大主流数据库,为开发者提供了高效且易于使用的分页解决方案。下面我们将...

    MyBatis高级应用:实现自定义分页插件

    自定义分页插件提供了一种灵活且高效的方式来实现 MyBatis 的分页查询。通过实现 Interceptor 接口并注册插件,我们可以根据不同的业务需求和数据库特性来定制分页逻辑。本文详细介绍了自定义分页插件的实现步骤和...

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

    这个插件专门为MyBatis设计,旨在提供高性能的物理分页解决方案。 首先,物理分页与逻辑分页的区别在于,物理分页直接在数据库层面进行,通过SQL语句的LIMIT和OFFSET或者ROWNUM等关键字来限制返回的数据量,避免了...

    Mybatis插件下 的分页查询

    PageHelper是Mybatis的一个强大插件,它实现了对数据库的分页查询功能,使得在处理大数据量时能有效地提高性能并优化用户体验。 首先,我们来理解一下分页查询的基本概念。分页查询是指在数据量较大的情况下,将...

Global site tag (gtag.js) - Google Analytics