文章目录
- 分页
- 1. RowBounds 分页
- 2. PageHelper 分页
- 3. PageInfo 对象属性描述
- 延迟加载
- 立即加载
- 激进式延迟加载
- 真-延迟加载
分页
Mybatis 中实现分页功能有 3 种途径:
- RowBounds 分页(不建议使用)
- Example 分页(简单情况可用)
- PageHelper 分页(推荐 )
1. RowBounds 分页
MyBatis 本身通过 RowBounds 对象提供了分页功能,你仅需为你的 dao 的查询方法多添加 RowBounds 类型的一个参数,并且不需要对配置文件做任何调整。
RowBounds 也称原生分页、逻辑分页。
RowBounds bounds = new RowBounds(0, 4);
List<Employee> list = dao.select(bounds);
但是这种分页是一种 逻辑分页,MyBatis 并未使用 limit 子句,查询的仍是 所有数据,只是它仅给你「看」到了所有数据中的一部分,逻辑分页虽然完成了分页功能,但是它并未通过分页功能的对性能问题有所提升。
2. PageHelper 分页
PageHelper 是一款被广泛使用的 MyBatis 插件。它通过 Mybatis 的插件机制,巧妙地通过机制,在不需要配置文件(不需要写 limit 子句)的情况下,动态去修改你所执行的 SQL,在其后动态添加 limit 子句。
为了使用 PageHelper 需要引入相应的包:
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.8</version>
</dependency>
PageHelper 是一款 MyBaits 插件,使用它需要向 Mybatis 注册 PageHelper,并对它作出相关配置(mybatis-config.xml
)。
<plugins>
<!-- com.github.pagehelper 为 PageHelper 类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 使用下面的方式配置参数,后面会有所有的参数介绍 -->
<property name="helperDialect" value="mysql" />
<property name="..." value="..."/>
</plugin>
</plugins>
警告
pagehelper 有 4.x 和 5.x 两个版本,用法有所不同,并不是向下兼容,同样的配置在使用 4.x 或 5.x 版本中可能会报错。例如,上面的helperDialect
就是 5.x 中的配置,在 4.x 中使用的是dialect
。
如果 Mybatis 整合进了 Spring,除了上述这样配置外,还可以将相应的注册-配置工作就在 Spring 的配置文件中进行:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property .../>
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<!--使用下面的方式配置参数,一行配置一个 -->
<value>
param1=value1
param2=value2
...
</value>
</property>
</bean>
</array>
</property>
</bean>
插件的属性配置:
-
helperDialect
用于指明底层数据库类型:oracle, mysql, mariadb, sqlite, hsqldb, postgresql, db2, sqlserver, informix, h2, sqlserver2012, derby
-
reasonable
是否启用『合理化』功能
启用(true)时,如果
pageNum < 1
,会返回第一页内容;如果pageNum > pages
,会返回查询最后一页。禁用(false)时,超出合理的范围会直接返回空数据。
在使用 PageHelper 时,PageHelper 提供了 2 种风格来描述分页:
- pageNum / pageSize 组合
插件作者建议推荐方式
PageHelper.startPage(1, 10);
这种风格实际上是在模拟「人的语气」。
- offset / limit 组合
PageHelper.offsetPage(0, 3);
很显然,这种风格就是 SQL 语句的分页写法
在你调用查询方法之前,调用 PageHelper 的上述两个方法中的任意一个,都可激活 PageHelper 插件的分页功能,使其动态地『帮』你修改SQL语句(添加 limit 子句)。而 Mybatis 的 select 返回的结果就返回的是一页数据。
PageHelper.startPage(4, 2);
List<Employee> list = empDao.selectByExample(null);
PageInfo<Employee> info = new PageInfo<>(list, 5);
System.out.println(info);
注意
由于 PageHelper 插件的实现涉及到 ThreadLocal 原理,这导致一旦 PageHelper 生产了一个分页参数(一个内部使用的 Page 对象),但是没有被消费,这个参数就会一直保留在这个线程的 ThreadLocal 中。当这个线程再次被使用时,就可能导致不该分页的方法去消费这个分页参数,这就产生了莫名其妙的分页。所以,分页参数的创建代码,和查询方法的调用代码,必须「紧密的在一起」。
PageHelper 插件流行的原因在于,它不仅仅能实现分页功能,而且还进一步封装了页面上的『分页导航条』所需要的所有相关信息。
在使用 PageHelper 的过程中,我们已经提供了 4 个关键数据:
PageHelper.startPage(4, 2); // 当前页的页号, 每页显示的数据量
...
PageInfo<Employee> info = new PageInfo<>(list, 5); // 查询结果, 导航栏上导航数字的个数
在创建了 PageInfo 之后便可以使用它:
// << < 2 3 [4] 5 6 > >>
log.info("是否有下一页:{}", pageInfo.isHasNextPage());
log.info("是否有上一页:{}", pageInfo.isHasPreviousPage());
log.info("导航栏上第一个页号:{}", pageInfo.getNavigateFirstPage());
log.info("导航栏上最后一个页号:{}", pageInfo.getNavigateLastPage());
log.info("导航栏上的五个导航数字:{}", Arrays.toString(pageInfo.getNavigatepageNums()));
log.info("共有 {} 页", pageInfo.getPages());
log.info("{} / {} ", pageInfo.getPageNum(), pageInfo.getPages());
log.info("共有 {} 条数据", info.getTotal());
3. PageInfo 对象属性描述
属性 | 说明 | 举例 |
---|---|---|
int pageNum | 当前页 | 比如,当前为第 5 页 |
int pageSize | 每页的数量 | 比如,每页(计划/预期)显示 10 条数据 |
int size | 当前页的数量 | 比如,以 98 条总数据为例,每页最多显示 10 条(最后一页显示 8 条数据) |
int startRow | 当前页面第一个元素在数据库中的行号 | 比如,以 98 条总数据的最后一页为例,第一条数据是第 91 条 |
int endRow | 当前页面最后一个元素在数据库中的行号 | 比如,以 98 条总数据的最后一页为例,最后一条数据是第 98 条 |
int pages | 总页数 | 比如,以 98 条总数据为例,每页显示 10 条(其中最后一页 8 条),因此共 10 页 |
int prePage | 前一页 | 比如,当前是第 5 页,所以前一页为 4 |
int nextPage | 下一页 | 比如,当前是第 5 页,所以下一页为 6 |
boolean isFirstPage | 是否为第一页 | 比如,当前是第 5 页,不是第 1 页,所以为 false |
boolean isLastPage | 是否为最后一页 | 比如,当前是第 5 页,不是最后 1 页,所以为 false |
boolean hasPreviousPage | 是否有前一页 | 比如,当前是第 5 页,有前一页,所以为 true |
boolean hasNextPage | 是否有下一页 | 比如,当前是第 5 页,有后一页,所以为 true |
int navigatePages | 导航页码数 | 比如,页面导航栏显示 [3 4 5 6 7] 共 5 个数字 |
int[] navigatepageNums | 所有导航页号 | 比如,页面导航栏显示 [3 4 5 6 7] 这 5 个数字 |
int navigateFirstPage | 导航条上的第一页 | 比如,页面导航栏显示 [3 4 5 6 7] 时,第一页是第 3 页 |
int navigateLastPage | 导航条上的最后一页 | 比如,页面导航栏显示 [3 4 5 6 7] 时,第一页是第 7 页 |
延迟加载
如果一个对象关联另一个对象,那么在查询 A 对象的时候,会去关联查询 B 对象。
何时查询(加载)B 对象分为三种时机:
- 立即加载
- 激进式延迟加载
- 延迟加载
立即加载
MyBaits 默认是立即加载,即在查询 A 对象的时候,会立即查询其关联的 B 对象。如果,B 对象也有关联对象,例如 C 对象,那么还会立即查询 C 对象,… 因此类推,直到把所有有关联关系的数据全部查询出来。
激进式延迟加载
通过设置,可以启用延迟加载:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
</settings>
启用延迟加载之后,Mybatis 又是默认的激进地延迟加载。
Mybatis 内部会进行某种规则判断,从而使得激进式的延迟加载,有时候等同于立即加载,有时候等同于普通的延迟加载。
真-延迟加载
可以再通过配置关闭掉激进地延迟加载,从而进入普通的延迟加载:
<settings>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
普通的延迟加载只会在你真正用到 A 对象的 B 属性时,再去查询/加载 B 对象。