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

摘要:
正如和ibatis以前的版本一样,mybatis的分页还是基于内存分页,其实这样的分页实现基本没用,特别是大量数据情况下。要想改变mybatis内部的分页行为,理论上只要把最终要执行的sql转变成对应的分页语句就行了。首先,我们熟悉下mybatis内部执行查询的动态交互图:可以很清楚的看到,真正生成Statement并执行sql的语句是StatementHandler接口的某个实现,这样就可以写个插件对StatementHandler的行为进行拦截。

Mybatis分页插件请使用这位帅哥开发的, 看起来不错.https://github.com/miemiedev/mybatis-paginator

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内部执行查询的动态交互图:

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

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

packagestudy.mybatis.interceptor;
importjava.sql.Connection;
importjava.util.Properties;
importorg.apache.commons.logging.Log;
importorg.apache.commons.logging.LogFactory;
importorg.apache.ibatis.executor.statement.StatementHandler;
importorg.apache.ibatis.mapping.BoundSql;
importorg.apache.ibatis.plugin.Interceptor;
importorg.apache.ibatis.plugin.Intercepts;
importorg.apache.ibatis.plugin.Invocation;
importorg.apache.ibatis.plugin.Plugin;
importorg.apache.ibatis.plugin.Signature;
importorg.apache.ibatis.reflection.MetaObject;
importorg.apache.ibatis.session.Configuration;
importorg.apache.ibatis.session.RowBounds;
importstudy.mybatis.dialect.Dialect;
importstudy.mybatis.dialect.MySql5Dialect;
@Intercepts({@Signature(type
=StatementHandler.class,method="prepare",args={Connection.class})})
publicclassPaginationInterceptor implementsInterceptor{
privatefinalstaticLog log =LogFactory.getLog(PaginationInterceptor.class);
@Override
publicObject intercept(Invocation invocation) throwsThrowable {
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){
returninvocation.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){
thrownewRuntimeException("the value of the dialect property in configuration.xml is not defined : "+configuration.getVariables().getProperty("dialect"));
}
Dialect dialect
=null;
switch(databaseType){
caseMYSQL:
dialect
=newMySql5Dialect();
}
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());
}
returninvocation.proceed();
}
@Override
publicObject plugin(Object target) {
returnPlugin.wrap(target, this);
}
@Override
publicvoidsetProperties(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的配置如下:

1 <?xml version="1.0" encoding="UTF-8" ?>2 <!DOCTYPE configuration
3 PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
4 "http://mybatis.org/dtd/mybatis-3-config.dtd">5 <configuration>6 7   <properties>8     <property name="dialect"value="oracle"/>9   </properties>10 11 </configuration>

免责声明:文章转载自《mybatis高级应用系列一:分页功能》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇Myeclipse安装svn插件pycharm 运行时 interpreter options为空下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

高性能缓存架构

极客时间:《从 0 开始学架构》:高性能缓存架构 1、引言 前几章节分别从读写分离、分库分表以及数据库的选择等方面来提升系统的性能,但在某些复杂的业务场景下,单纯的提高存储系统的性能是不够的,典型的场景如下: 需要经过复杂运算后得出的数据,存储系统无能为力 读多写少的数据,存储系统有心无力。如写一次,读多次 缓存就是为了弥补存储系统在这些复杂业务场景下...

Paginator分页

1,分页 Django提供了数据分页的类,这些类被定义在django/core/paginator.py中 对象Paginator用于对列进行一页n条数据的分页运算 对象Page用于表示第m页的数据 2,Paginator对象 方法init(列表,int):返回分页对象,参数为列表数据,每面数据的条数 属性count:返回对象总数 属性num_pag...

mybatis mapper判断if条件写法

//1 mybatis处理不同字符串 String s1 = null, s2 = ""; // mapper对于这两种情况判断不同,下面语句可以排除这两种情况 <if test="str != null and str != ''"></if> //2 判断集合时候为空 if test="arr != null and arr....

mybatis中的#和$的区别?

1. #将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号。如:order by #user_id#,如果传入的值是111,那么解析成sql时的值为order by "111", 如果传入的值是id,则解析成的sql为order by "id".  2. $将传入的数据直接显示生成在sql中。如:order by $user_id$,如果传入的值...

mybatis问题合集:#{}与${}区别、动态sql语句、缓存机制

一、MyBatis 中#{}和${}区别   #{} 是预编译处理,像传进来的数据会加个" "(#将传入的数据都当成一个字符串,会对自动传入的数据加一个双引号)   ${} 就是字符串替换。直接替换掉占位符。$方式一般用于传入数据库对象,例如传入表名.   使用 ${} 的话会导致 sql 注入。什么是 SQL 注入呢?比如 select * from u...

springMVC使用map接收入参 + mybatis使用map 传入查询参数

 测试例子: controllel层 ,使用map接收请求参数,通过Debug可以看到,请求中的参数的值都是字符串形式,如果将这个接收参数的map直接传入service,mybatis接收参数时会报错,因此要先对请求中的参数进行预处理 1 package org.slsale.test; 2 3 import java.util.Date; 4...