`

mybatis高级应用系列一:分页功能

阅读更多

转载自【海鸟】的博客:http://www.cnblogs.com/jcli/archive/2011/08/09/2132222.html

 

 

Mybatis3.0出来已有段时间了,其实自己挺喜欢这样的一个持久化框架的,因为它简单实用,学习成本低。Mybatis3.0在整体结构上和ibatis2.X差不多,改进特性如下:

1.         解析xml引进了Xpath,不像ibatis2.x那样业余

2.         动态sqlOGNL解析

3.         加入注解配置sql,感觉没什么特别大的用途,我更喜欢xml方式,代码和配置分离,这也是ibatis的初衷

4.         加强了缓存这块的功能。Mybatis3.0把缓存模块分得更细,分为“持久实现(prepetual)”和“资源回收策略实现(eviction)”,更好的对缓存功能进行自己组合和扩展

5.         终于加入的plugin功能,就像struts一样,这样就可以很好的扩展内部的Executor,StatementHandler….等内部对象功能。

 

一下只能想到这些了,总之改动后的代码结构清晰多了,如果各位看下源码的话,也是学习设计模式很好的课件,里面的代码用到了很多经典的设计模式,这在之后的系列学习中会讲到。

 

这一篇文章讲下分页的功能。

 

正如和ibatis以前的版本一样,mybatis的分页还是基于内存分页(查找出所有记录再取出偏移量的记录,如果jdbc驱支持absolute定位或者rs.next()到指定偏移位置),其实这样的分页实现基本没用,特别是大量数据情况下。

 

要想改变mybatis内部的分页行为,理论上只要把最终要执行的sql转变成对应的分页语句就行了。首先,我们熟悉下mybatis内部执行查询的动态交互图:

 

可以很清楚的看到,真正生成Statement并执行sql的语句是StatementHandler接口的某个实现,这样就可以写个插件对StatementHandler的行为进行拦截。

 

package study.mybatis.interceptor;

import java.sql.Connection;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.RowBounds;
import study.mybatis.dialect.Dialect;
import study.mybatis.dialect.MySql5Dialect;

@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class})})

publicclass PaginationInterceptor implements Interceptor{

    privatefinalstatic Log log = LogFactory.getLog(PaginationInterceptor.class);

   

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
       StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
       BoundSql boundSql = statementHandler.getBoundSql();
       MetaObject metaStatementHandler = MetaObject.forObject(statementHandler);
       RowBounds rowBounds = (RowBounds)metaStatementHandler.getValue("delegate.rowBounds");
       if(rowBounds ==null|| rowBounds == RowBounds.DEFAULT){
           return invocation.proceed();
       }
       Configuration configuration = (Configuration)metaStatementHandler.getValue("delegate.configuration");
       Dialect.Type databaseType  =null;
       try{
           databaseType = Dialect.Type.valueOf(configuration.getVariables().getProperty("dialect").toUpperCase());
       } catch(Exception e){
           //ignore
       }
       if(databaseType ==null){
           thrownew RuntimeException("the value of the dialect property in configuration.xml is not defined : "+ configuration.getVariables().getProperty("dialect"));
       }
       Dialect dialect =null;
       switch(databaseType){
           case MYSQL:
              dialect =new MySql5Dialect(); 
       }

       String originalSql = (String)metaStatementHandler.getValue("delegate.boundSql.sql");
       metaStatementHandler.setValue("delegate.boundSql.sql", dialect.getLimitString(originalSql, rowBounds.getOffset(), rowBounds.getLimit()) );
       metaStatementHandler.setValue("delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET );
       metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT );
       if(log.isDebugEnabled()){
           log.debug("生成分页SQL : "+ boundSql.getSql());
       }
       return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
       return Plugin.wrap(target, this);
    }

    @Override
    publicvoid setProperties(Properties properties) {
    }
}

里面最重要的三条语句:

metaStatementHandler.setValue("delegate.boundSql.sql", dialect.getLimitString(originalSql, rowBounds.getOffset(), rowBounds.getLimit()) );

metaStatementHandler.setValue(
"delegate.rowBounds.offset", RowBounds.NO_ROW_OFFSET );

metaStatementHandler.setValue(
"delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT );                                          
改别要执行的sql语句,现在新设置的sql语句是物理分页的,所以现在不再需要mybatis进行额外的操作了,所以把rowBounds的偏移量恢复为初始值(offet:0,limit:Integer.max) 为了指定数据库版本,在mybatis全局配置文件设置dialect值
  
<properties>
    <property name="dialect" value="mysql"/>
</properties>
<plugins>
<plugin interceptor="study.mybatis.interceptor.PaginationInterceptor">
    </plugin>
</plugins>
 完整代码请用svn从下面链接检出查看:

 

svn checkout http://20110311start.googlecode.com/svn/trunk/

下个系列将会讲下缓存的扩展应用。    
-----------------------------分隔线---------------------------------------------

最近有朋友用mybatis和spring整合的时候如果按照下列方式发现dialect属性不能设置成功:

 

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="typeAliasesPackage" value="com.***.web.domain" />
    <property name="plugins">
        <array>
            <ref bean="paginationInterceptor"/>
        </array>
    </property>
    <property name="configurationProperties">
        <props>
            <prop key="dialect">mysql</prop>
        </props>
    </property>
</bean>
 

 

这个问题是org.mybatis.spring.SqlSessionFactoryBean这个代码里有个bug(244行,或者不是bug,是作者不想这么做法),如果感兴趣可以看下源码。配置文件做下如下修改:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="typeAliasesPackage" value="com.***.web.domain" />
    <property name="plugins">
        <array>
            <ref bean="paginationInterceptor"/>
        </array>
    </property>
    <!-- 这里不要,注释掉
    <property name="configurationProperties">
        <props>
            <prop key="dialect">mysql</prop>
        </props>
    </property>
    -->
    <!--  加上这个属性 -->
    <property name="configLocation" value="classpath:Mybatis_Configuration.xml"/>
</bean>
 Mybatis_Configuration.xml的配置如下:
<?xml version="1.0" encoding="UTF-8" ?>
 <!DOCTYPE configuration
 PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-config.dtd">
 <configuration>
   <properties>
     <property name="dialect" value="oracle"/>
   </properties>
 </configuration>
 

 

分享到:
评论

相关推荐

    MyBatis Pagination:一个分页插件-开源

    通过全面的示例代码和测试,讲解了MyBatis XML模式和注解模式下增删改查操作的基本用法,动态SQL在不同方面的应用以及使用过程中的最佳实践方案介绍。 为 MyBatis 高级映射、存储过程和类型处理器提供了丰富的示例...

    JavaEE企业级分布式高级架构师018期 94G

    ├─第1章 mybatis从入门到精通 │ │ 第一章第1节: 02-mybatis介绍.mp4 │ │ 第一章第1节: ...│ │ 第一章第1节: 16-mybatis PageHelper分页插件.mp4   │ ├─第3节 9月5日 Mybatis02-手写Mybatis框架

    springmybatis

    mybatis实战教程mybatis in action之七实现mybatis分页源码下载 mybatis实战教程mybatis in action之八mybatis 动态sql语句 mybatis实战教程mybatis in action之九mybatis 代码生成工具的使用 mybatis ...

    Spring3.x企业应用开发实战(完整版) part1

    《Spring3.x企业应用开发实战》是在《精通Spring2.x——企业应用开发详解》的基础上,经过历时一年的重大调整改版而成的,本书延续了上一版本追求深度,注重原理,不停留在技术表面的写作风格,力求使读者在熟练使用...

    Spring.3.x企业应用开发实战(完整版).part2

    《Spring3.x企业应用开发实战》是在《精通Spring2.x——企业应用开发详解》的基础上,经过历时一年的重大调整改版而成的,本书延续了上一版本追求深度,注重原理,不停留在技术表面的写作风格,力求使读者在熟练使用...

    网络架构师148讲视频课程

    │ 第11节:Mybatis的分页实现.avi │ 第12节:Service的实现以及模块化.avi │ 第13节:Spring MVC实现Web层开发.avi │ 第14节:新增和列表页面和分页tag.avi │ 第15节:带查询的分页、修改和删除页面.avi │ 第...

    spring-boot-realworld-example-app:示例Spring代码库,包含遵循RealWorld API规范的真实示例(CRUD,auth,高级模式等)

    创建该代码库的目的是演示使用Spring Boot + Mybatis构建的完整的全栈应用程序,其中包括CRUD操作,身份验证,路由,分页等。 有关如何与其他前端/后端一起使用的更多信息,请转到库。 这个怎么运作 该应用程序...

    全新JAVAEE大神完美就业实战课程 超150G巨制课程轻松实战JAVAEE课程 就业部分.txt

    SpringMvc_day02高级参数.上传图片.JSON数据交互.拦截器 15-SSM企业案例-客户管理系统(学习1天) SpringMvc_SSM综合练习.分页.增删改查 16-SSM分布式案例-互联网商城(学习13天) day01_电商介绍--互联网术语...

    Eclipse开发分布式商城系统+完整视频代码及文档

    │ 淘淘商城第一天笔记.docx │ ├─02.第二天 │ 07.商品类目选择完成.avi │ 01.课程计划.avi │ 02.展示首页.avi │ 03.分页插件01.avi │ 04.分页插件的使用方法.avi │ 05.商品列表展示.avi │ 06.商品类目...

Global site tag (gtag.js) - Google Analytics