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

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

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的配置如下:

复制代码
 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>
复制代码

原创文章,转载请注明出处,谢谢!

分类: java
0
0
    (请您对文章做出评价)   
« 博主上一篇: DriverManager怎样查找当前Driver
» 博主下一篇: java中显示设置实例为null多余吗
posted @ 2011-08-09 14:07 海鸟 阅读(9186) 评论( 6 编辑 收藏
 
#1楼 2011-09-07 09:30 kugua-
delegate.rowBounds.offset 是在哪定义的? 为什么要设置这个属性呢?
 
#2楼 [ 楼主2011-09-07 11:21 海鸟
@kugua-
我们调用分页的API一般是这样的:
?
1
List l = session.selectList( "users.selectUsers" , user, new RowBounds( 2 , 1 ));

mybatis默认情况下全部查询出来后再截取当前页的记录。可是如果我们自己写了个分页插件的情况下采用了物理分页,查询出来的记录就是当前页的内容。所以现在要告诉mybatis不再对结果进行加工, 所以要重置为不分页的设置:
?
1
2
3
metaStatementHandler.setValue( "delegate.rowBounds.offset" , RowBounds.NO_ROW_OFFSET );
metaStatementHandler.setValue( "delegate.rowBounds.limit" , RowBounds.NO_ROW_LIMIT ); 

这两条语句就是用反射机制设置,相当于:
?
1
2
3
delegate.getRowBounds.setLimit(RowBounds.NO_ROW_LIMIT);
delegate.getRowBounds.setOffset(RowBounds.NO_ROW_OFFSET);
 
#3楼 2011-09-07 11:27 kugua-
感谢楼主,解惑.
楼主,源码能不能发我下啊
   邮箱是 doudou360176196@qq.com  谢谢啦。
 
#5楼 [ 楼主2011-11-24 16:55 海鸟
@天天敲代码的男人丶
引用 完整代码请用svn从下面链接检出查看:
svn checkout http://20110311start.googlecode.com/svn/trunk/
下个系列将会讲下缓存的扩展应用。
 
#6楼 2378076 2012/5/16 9:29:58 2012-05-16 09:29 mojunbin
还是貌似有点用不到啊?可能我某个地方配置出错了
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值