Mybatis 逻辑与物理分页

Mybatis 逻辑与物理分页

前言

什么是物理分页和逻辑分页?

一.逻辑分页
1.逻辑分页的第一种方式,利用ResultSet的滚动分页。步骤如下:
  a.根据条件sql查询数据库。
  b.得到ResultSet的结果集,由于ResultSet带有游标,因此可以使用其next()方法来指向下一条记录。
  c.利用next()方法,得到分页所需的结果集。
  这种分页方式依靠的是对结果集的算法来分页,因此通常被称为“逻辑分页”。

2.逻辑分页的第二种方式
利用Scrollable ResultSets(可滚动结果集合)来快速定位到某个游标所指定的记录行,所使用的是ResultSet的absolute()方法。
虽然和第一种方式区别不大,单效率比ResultSet滚动要好,但是absolute()方法并不是所有jdbc驱动都支持。
可用如下代码测试当前jdbc驱动是否支持可滚动结果集:
int type = rs.getType();
if (type == ResultSet.TYPE_SCROLL_INSENSITIVE || type == ResultSet.TYPE_SCROLL_SENSITIVE)
    System.out.println("Result set is scrollable");
else
    System.out.println("Result set is not scrollable");

.物理分页
利用数据库本身的一些特性来分页。即:利用了数据库对sql语法的优化,提高分页性能。
1.
针对Oracle数据库
步骤如下:
a.
根据所使用的数据库特性来组织sql进行分页。
b.
每次跳转页面的sql查询都不相同。
 
通用的sql分页方式,“限制行数结果集的倒序”分页,步骤如下:
 (1).
取得符合条件的所有结果集中可以唯一标识的Key(通常是主键),并正向排序。
 (2).
利用数据库提供的特殊方法进行“最大结果集”的限制(在Oracle中使用rownum, sql server中使用top, mysql中使用limit...),
 
该“最大结果集”指包含当前所处页的所有记录数,“最大结果集”应该只包含惟一的Key
 (3).
对步骤(2)中的“最大结果集”进行逆序,并取得“显示当前页显示数量的结果集”,该结果集中只包含惟一的Key值。
 (4).
通过步骤(3)中所取得的Key值取得显示数据,该显示数据就是当前页应该显示的数据。

2.针对MySQL数据库
MySQL数据库中offset关键字的意思是"越过",而limit关键字的意思是“限制”,利用这两者结合可轻松分页。
(1)
取得符合条件的结果集,包含全字段。
(2)
利用offset关键字越过一段结果集(被越过的结果集就是"(当前页 - 1) * 一页显示数")
(3)
利用limit关键字限制取得一段结果集(被限制取得的结果集就是一页显示数)

分页结论:
1.物理分页速度上并不一定快于逻辑分页,逻辑分页速度上也并不一定快于物理分页。
2.物理分页总是优于逻辑分页:没有必要将属于数据库端的压力加诸到应用端来,就算速度上存在优势,
然而其它性能上的优点足以弥补这个缺点。
3.在分页工作前,有必要了解使用数据库本身的一些sql语句特点更好的分页。
代码链接:http://www.cnblogs.com/wllyy189/articles/1237478.html

1.Mybatis 逻辑分页

Mybatis 实现了逻辑分页,直接传入RowBounds 实例即可实现。

 public List<User> findUserByPage() {

// TODO Auto-generated method stub

SqlSession sqlSession = ssf.openSession();

int offSet = 0;

int limit = 2;

User user = new User();

user.setName("qing");

RowBounds rowRounds = new RowBounds(offSet, limit);

List<User> listUsers = sqlSession.selectList("select UserByPage", user, rowRounds);

return listUsers;

}

Mapper.xml:

<select id="selectUserByPage" resultType="test_ibatis.User" resultSetType="SCROLL_SENSITIVE"parameterType="test_ibatis.User">

select * from t_user where name like '%'||#{name}||'%'

</select>

在配置文件中指定resultSetType即可。

2. Mybatis物理分页

Mybatis自带的分页方法只是逻辑分页,如果数据量很大,内存一定会溢出,在不修改mybatis源代码的情况下,应该怎么使mybatis支持物理分页呢?
网上推荐了三种实现物理分页的方法:

代码的情况下,应该怎么使mybatis支持物理分页呢?

网上推荐了三种实现物理分页的方法:
  1)直接在配置文件中使用limit 子句。Select * from t_user limit 1,3
       select * from users limit #{start}, #{end}
  2)  扩展mybatis的拦截器,自己重新构造查询子句。在下面的例子中也主要使用改方式实现。
        http://www.cnblogs.com/jcli/archive/2011/08/09/2132222.html
  3)参考hibernate的Dialect的实现方式,在mybatis也用此方式实现。
        http://blog.sina.com.cn/s/blog_6853c03b0100ooe9.html
iBatis分页查询的性能问题分析
http://hi.baidu.com/lvzhnan/blog/item/1d788f9812630d046e068ca9.html具体内容将在下一讲中详细说明。

3. 分页实现步骤

主要分为两步,第一步是定义每个数据库自己的sql拼接statement。在该实例中,首先定义了一个基本的方言(Dialect)类。然后实现了Oracle和Mysql两个数据库。第二步是定义Mybatis拦截器。

4.具体实现

dialect类中主要是定义一个抽象的方法,用于其他子类的实现。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
publicabstractclass Dialect {
 
publicstaticenum Type {
 
MYSQL, ORACLE
 
}
 
publicabstract String getLimitString(String sql, int skipResults,
 
int maxResults);
 
}

 

Mysql使用limit实现分页:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
 
* 得到分页的SQL
 
*
 
* @param offset
 
* 偏移量
 
* @param limit
 
* 位置
 
* @return分页SQL
 
*/
 
publicstatic String getLimitString(String querySelect, int offset,
 
int limit) {
 
  
 
querySelect = getLineSql(querySelect);
 
  
 
String sql = querySelect.replaceAll( "[^\\s,]+\\." , "" ) + " limit "
 
+ offset + " ," + limit;
 
  
 
return sql;
 
}

Oralce使用两层查询实现分页:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public String getLimitString(String sql, int offset, int limit) {
 
  
 
sql = sql.trim();
 
boolean isForUpdate = false ;
 
if (sql.toLowerCase().endsWith( " for update" )) {
 
sql = sql.substring( 0 , sql.length() - 11 );
 
isForUpdate = true ;
 
}
 
  
 
StringBuffer pagingSelect = new StringBuffer(sql.length() + 100 );
 
  
 
pagingSelect
 
.<span style= "color: #ff0000;" ><strong>append( "select * from ( select row_.*, rownum rownum_ from ( " ); </strong></span>
  
 
pagingSelect.append(sql);
 
  
 
pagingSelect.append( " ) row_ ) where rownum_ > " + offset
 
+ " and rownum_ <= " + (offset + limit));
 
  
 
if (isForUpdate) {
 
pagingSelect.append( " for update" );
 
}
 
  
 
return pagingSelect.toString();
 
}

拦截器中重要的代码部分:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
public Object intercept(Invocation invocation) throws Throwable {
 
Dialect dialect = null ;
 
switch (databaseType) {
 
caseMYSQL:
 
dialect = new MysqlDialect();
 
  
 
}

5. XML文件配置

a. 在spring配置文件中加载该拦截器

<beanid="paginationInterceptor"class="page.PaginationInterceptor">

</bean>

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

<property name="dataSource" ref="myDataSource" />

<property name="plugins">

<array>

<ref bean="paginationInterceptor" />

</array>

</property>

<property name="configLocation" value="WebContent/WEB-INF/conf/SqlMapConfig.xml"/>

</bean>

bSqlConfig.xml

mybatis的配置文件中指定数据库的类型

<properties>

<property name="dialect" value="mysql"/>

</properties>

6.推荐文章

http://blog.csdn.net/firecoder/article/details/6069434

拦截器里面最重要的三条语句:

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

Q&A 常见问题:

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

1 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
2 <property name="dataSource" ref="dataSource"/>
3 <property name="typeAliasesPackage" value="com.***.web.domain"/>
4 <property name="plugins">
 5 <array>
 6 <ref bean="paginationInterceptor"/>
 7 </array>
 8 </property>
 9 <property name="configurationProperties">
10 <props>
11 <prop key="dialect">mysql</prop>
12 </props>
13 </property>
14 </bean>

这个问题是org.mybatis.spring.SqlSessionFactoryBean这个代码里有个bug244行,或者不是bug,是作者不想这么做法),如果感兴趣可以看下源码。配置文件做下如下修改:
1 <ref bean="paginationInterceptor"/>
 2 </array>
3 </property>
 4<!-- 这里不要,注释掉
 5 <property name="configurationProperties">
 6 <props>
 7 <prop key="dialect">mysql</prop>
 8 </props>
 9 </property>
 10 -->
11<!-- 加上这个属性 -->
12 <property name="configLocation" value="classpath:Mybatis_Configuration.xml"/>
13 </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>
 7 <properties>
 8 <property name="dialect" value="oracle"/>
 9 </properties>
10</configuration> 

2. delegate.rowBounds.offset 是在哪定义的?为什么要设置这个属性呢?

 我们调用分页的API一般是这样的:

List l = session.selectList("users.selectUsers", user,newRowBounds(2, 1));
mybatis
默认情况下全部查询出来后再截取当前页的记录。可是如果我们自己写了个分页插件的情况下采用了物理分页,查询出来的记录就是当前页的内容。所以现在要告诉mybatis不再对结果进行加工,所以要重置为不分页的设置:

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

metaStatementHandler.setValue("delegate.rowBounds.limit", RowBounds.NO_ROW_LIMIT ); 
这两条语句就是用反射机制设置,相当于:

delegate.getRowBounds.setLimit(RowBounds.NO_ROW_LIMIT);

delegate.getRowBounds.setOffset(RowBounds.NO_ROW_OFFSET);

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值