1.Mybatis分页介绍
我们在项目中经常会查询数据列表,数据库不会显示所有的数据,而是分页显示,Mybatis作为常用的持久层框架需要对数据进行分页,那么常有的分页方式有如下几种:
数组分页:即查询出全部数据,在list中截部分数据,它属于内存分页,适用于对查询效率较高的场景,例如“首页搜索“。
Sql分页:使用sql语句中limit关键字,查询指定下标的数据,它属于物理分页,对于数据量较大取部分数据
拦截器分页:通过实现Interceptor 接口完成对数据的拦截处理。
RowBounds分页:它是针对ResultSet结果集执行的内存分页,通过在mybatis接口中增加加入RowBounds参数,当数据量小时,RowBounds不失为一种好办法。但是数据量大时,实现拦截器就很有必要了。
通过分页插件(工具类)进行分页:其原理采用拦截器方式处理,对于开发者简单易用。
Mybatis使用RowBounds对象进行分页,它是针对ResultSet结果集执行的内存分页,而非物理分页。可以在sql内直接书写带有物理分页的参数来完成物理分页功能,也可以使用分页插件来完成物理分页。
2.MyBatis分页实现
2.1 数组分页
查询出全部数据,然后再list中截取需要的部分;
mybatis接口
List<Student> queryStudentsByArray();
xml配置文件
<select id="queryStudentsByArray" resultMap="studentmapper">
select * from student
</select>
service
接口
List<Student> queryStudentsByArray(int currPage, int pageSize);
实现接口
@Override
public List<Student> queryStudentsByArray(int currPage, int pageSize) {
//查询全部数据
List<Student> students = studentMapper.queryStudentsByArray();
//从第几条数据开始
int firstIndex = (currPage - 1) * pageSize;
//到第几条数据结束
int lastIndex = currPage * pageSize;
return students.subList(firstIndex, lastIndex); //直接在list中截取
}
controller
@ResponseBody
@RequestMapping("/student/array/{currPage}/{pageSize}")
public List<Student> getStudentByArray(@PathVariable("currPage") int currPage, @PathVariable("pageSize") int pageSize) {
List<Student> student = StuServiceIml.queryStudentsByArray(currPage, pageSize);
return student;
}
2.2 Sql分页
使用limit关键字分页,limit start,pageSize
mybatis接口
List<Student> queryStudentsBySql(Map<String,Object> data);
xml文件
<select id="queryStudentsBySql" parameterType="map" resultMap="studentmapper">
select * from student limit #{currIndex} , #{pageSize}
</select>
service
接口
List<Student> queryStudentsBySql(int currPage, int pageSize);
实现类
public List<Student> queryStudentsBySql(int currPage, int pageSize) {
Map<String, Object> data = new HashedMap();
data.put("currIndex", (currPage-1)*pageSize);
data.put("pageSize", pageSize);
return studentMapper.queryStudentsBySql(data);
}
controller
@ResponseBody
@RequestMapping("/student/array/{currPage}/{pageSize}")
public List<Student> queryStudentsBySql(@PathVariable("currPage") int currPage, @PathVariable("pageSize") int pageSize) {
List<Student> student = StuServiceIml.queryStudentsBySql(currPage, pageSize);
return student;
}
2.3 拦截器分页
拦截器对分页可以灵活处理,创建拦截器,拦截mybatis接口方法id以ByPage结束的语句
拦截器
package com.atguigu.interceptor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import java.sql.Connection;
import java.util.Map;
import java.util.Properties;
/**
* @Intercepts 说明是一个拦截器
* @Signature 拦截器的签名
* type 拦截的类型 四大对象之一( Executor,ResultSetHandler,ParameterHandler,StatementHandler)
* method 拦截的方法
* args 参数,高版本需要加个Integer.class参数,不然会报错
*/
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class})})
public class MyPageInterceptor implements Interceptor {
//每页显示的条目数
private int pageSize;
//当前现实的页数
private int currPage;
//数据库类型
private String dbType;
@Override
public Object intercept(Invocation invocation) throws Throwable {
//获取StatementHandler,默认是RoutingStatementHandler
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
//获取statementHandler包装类
MetaObject MetaObjectHandler = SystemMetaObject.forObject(statementHandler);
//分离代理对象链
while (MetaObjectHandler.hasGetter("h")) {
Object obj = MetaObjectHandler.getValue("h");
MetaObjectHandler = SystemMetaObject.forObject(obj);
}
while (MetaObjectHandler.hasGetter("target")) {
Object obj = MetaObjectHandler.getValue("target");
MetaObjectHandler = SystemMetaObject.forObject(obj);
}
//获取连接对象
//Connection connection = (Connection) invocation.getArgs()[0];
//object.getValue("delegate"); 获取StatementHandler的实现类
//获取查询接口映射的相关信息
MappedStatement mappedStatement = (MappedStatement) MetaObjectHandler.getValue("delegate.mappedStatement");
String mapId = mappedStatement.getId();
//statementHandler.getBoundSql().getParameterObject();
//拦截以.ByPage结尾的请求,分页功能的统一实现
if (mapId.matches(".+ByPage$")) {
//获取进行数据库操作时管理参数的handler
ParameterHandler parameterHandler = (ParameterHandler) MetaObjectHandler.getValue("delegate.parameterHandler");
//获取请求时的参数
Map<String, Object> paraObject = (Map<String, Object>) parameterHandler.getParameterObject();
//也可以这样获取
//paraObject = (Map<String, Object>) statementHandler.getBoundSql().getParameterObject();
//参数名称和在service中设置到map中的名称一致
currPage = (int) paraObject.get("currPage");
pageSize = (int) paraObject.get("pageSize");
String sql = (String) MetaObjectHandler.getValue("delegate.boundSql.sql");
//也可以通过statementHandler直接获取
//sql = statementHandler.getBoundSql().getSql();
//构建分页功能的sql语句
String limitSql;
sql = sql.trim();
limitSql = sql + " limit " + (currPage - 1) * pageSize + "," + pageSize;
//将构建完成的分页sql语句赋值个体'delegate.boundSql.sql',偷天换日
MetaObjectHandler.setValue("delegate.boundSql.sql", limitSql);
}
//调用原对象的方法,进入责任链的下一级
return invocation.proceed();
}
//获取代理对象
@Override
public Object plugin(Object o) {
//生成object对象的动态代理对象
return Plugin.wrap(o, this);
}
//设置代理对象的参数
@Override
public void setProperties(Properties properties) {
//如果项目中分页的pageSize是统一的,也可以在这里统一配置和获取,这样就不用每次请求都传递pageSize参数了。参数是在配置拦截器时配置的。
String limit1 = properties.getProperty("limit", "10");
this.pageSize = Integer.valueOf(limit1);
this.dbType = properties.getProperty("dbType", "mysql");
}
}
配置文件SqlMapConfig.xml
<configuration>
<plugins>
<plugin interceptor="com.atguigu.interceptor.MyPageInterceptor">
<property name="limit" value="10"/>
<property name="dbType" value="mysql"/>
</plugin>
</plugins>
</configuration>
mybatis配置
<!--接口-->
List<AccountExt> getAllBookByPage(@Param("currPage")Integer pageNo,@Param("pageSize")Integer pageSize);
xml配置文件
<sql id="getAllBooksql" >
acc.id, acc.cateCode, cate_name, user_id,u.name as user_name, money, remark, time
</sql>
<select id="getAllBook" resultType="com.atguigu.pojo.AccountExt" >
select
<include refid="getAllBooksql" />
from account as acc
</select>
service
public List<AccountExt> getAllBookByPage(String pageNo,String pageSize) {
return accountMapper.getAllBookByPage(Integer.parseInt(pageNo),Integer.parseInt(pageSize));
}
controller
@RequestMapping("/getAllBook")
@ResponseBody
public Page getAllBook(String pageNo,String pageSize,HttpServletRequest request,HttpServletResponse response){
pageNo=pageNo==null?"1":pageNo; //当前页码
pageSize=pageSize==null?"5":pageSize; //页面大小
//获取当前页数据
List<Book> list = bookService.getAllBookByPage(pageNo,pageSize);
//获取总数据大小
int totals = bookService.getAllBook();
//封装返回结果
Page page = new Page();
page.setTotal(totals+"");
page.setRows(list);
return page;
}
Page实体类
package com.atguigu .pojo;
import java.util.List;
/**
* Created by Autumn on 2021/6/21.
*/
public class Page {
private String pageNo = null;
private String pageSize = null;
private String total = null;
private List rows = null;
public String getTotal() {
return total;
}
public void setTotal(String total) {
this.total = total;
}
public List getRows() {
return rows;
}
public void setRows(List rows) {
this.rows = rows;
}
public String getPageNo() {
return pageNo;
}
public void setPageNo(String pageNo) {
this.pageNo = pageNo;
}
public String getPageSize() {
return pageSize;
}
public void setPageSize(String pageSize) {
this.pageSize = pageSize;
}
}
2.4 RowRounds分页
在 mybatis 中,使用 RowBounds 进行分页,非常方便,不需要在 sql 语句中写 limit,即可完成分页功能。但是由于它是在 sql 查询出所有结果的基础上截取数据的,所以在数据量大的sql中并不适用,它更适合在返回数据结果较少的查询中使用
最核心的是在 mapper 接口层,传参时传入 RowBounds(int offset, int limit) 对象,即可完成分页
mapper接口
@Mapper
public interface BookMapper {
//添加数据
int insert(Book book);
//模糊查询
List<Book> selectBookByName(Map<String, Object> map, RowBounds rowBounds);
}
xml文件
<select id="selectBookByName" resultMap="BaseResultMap">
<bind name="pattern_bookName" value="'%' + bookName + '%'" />
<bind name="pattern_bookAuthor" value="'%' + bookAuthor + '%'" />
select * from book
where 1 = 1
<if test="bookName != null and bookName !=''">
and book_name LIKE #{pattern_bookName}
</if>
<if test="bookAuthor != null and bookAuthor !=''">
and book_author LIKE #{pattern_bookAuthor}
</if>
</select>
service
public List<Book> selectBookByName(String pageNo,String pageSize) {
List<Book> list = bookMapper.selectBookByName(map, new RowBounds((pageNo-1)*pageSize, pageSize));
return list;
}
controller
@RequestMapping("/selectBookByName")
@ResponseBody
public Page selectBookByName(String pageNo,String pageSize,HttpServletRequest request,HttpServletResponse response){
pageNo=pageNo==null?"1":pageNo; //当前页码
pageSize=pageSize==null?"5":pageSize; //页面大小
//获取当前页数据
List<Book> list = bookService.selectBookByName(pageNo,pageSize);
//获取总数据大小
//int totals = bookService.getAllBook();
//封装返回结果
Page page = new Page();
page.setTotal(totals+"");
page.setRows(list);
return page;
}
2.5 PageHelper插件分页实现
这里只讲解用法,下一节我们详细讲解源码分析
引入maven包依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.6</version>
</dependency>
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>
<plugins>
<!-- com.github.pagehelper 为 PageHelper 类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<!-- 设置数据库类型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL 六种数据库-->
<!--
不同数据库分页语法不一样的。所以,需要告诉分页组件采用是哪种数据库,根据这种数据库产生对应SQL语法
mysql: Limit ?,?
-->
<property name="dialect" value="mysql"/>
</plugin>
</plugins>
</configuration>
dao代码
Page<TravelGroup> findPage(String queryString);
xml
<select id="findPage" parameterType="string" resultType="com.atguigu.pojo.TravelGroup">
select * from t_travelgroup
<where>
<if test="value!=null and value.length>0">
code=#{value} or name like concat("%",value,"%")
</if>
</where>
</select>
service代码
@Override
public PageResult findPage(Integer currentPage, Integer pageSize, String queryString) {
//1.初始化分页操作
PageHelper.startPage(currentPage,pageSize);
//2.查询数据
Page<TravelGroup> page = travelGroupDao.findPage(queryString);
//3.封装
return new PageResult(page.getTotal(), page.getResult());
}
这里截取项目中的部分代码,controller与分页无关,PageResult也是封装分页的bean,也可以写其他实体类封装list数据和total记录。
3.源码分析PageHelper分页插件
通过以上的分页实现,我们发现无论是哪一种,都需要自己写一些代码进行处理,对于相同功能没法复用,所以关于分页功能推荐大家使用分页插件,这里推荐市面上比较流行的PageHelper。
下面简单讲解分页查询的实现步骤。
3.1 PageHelper的maven依赖及插件配置
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.2.8</version>
</dependency>
PageHelper除了本身的jar包外,它还依赖了一个叫jsqlparser的jar包,使用时,我们不需要单独指定jsqlparser的maven依赖,maven的间接依赖会帮我们引入。
3.2 配置拦截器插件
配置在mybatis-config.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>
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql" />
<!-- 该参数默认为false -->
<!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
<!-- 和startPage中的pageNum效果一样 -->
<property name="offsetAsPageNum" value="true" />
<!-- 该参数默认为false -->
<!-- 设置为true时,使用RowBounds分页会进行count查询 -->
<property name="rowBoundsWithCount" value="true" />
<!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
<!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型) -->
<property name="pageSizeZero" value="true" />
<!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
<!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
<!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
<property name="reasonable" value="false" />
<!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
<!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
<!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 -->
<!-- 不理解该含义的前提下,不要随便复制该配置 -->
<property name="params" value="pageNum=start;pageSize=limit;" />
<!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
<property name="returnPageInfo" value="check" />
</plugin>
</plugins>
</configuration>
3.3 加载过程
// 获取配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 通过加载配置文件获取SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取SqlSession对象
SqlSession session = factory.openSession();
PageHelper.startPage(1, 5);
session.selectList("com.atguigu.UserMapper.query");
在这里进入到sqlSessionFactory中
new SqlSessionFactoryBuilder().build(inputStream);
public SqlSessionFactory build(InputStream inputStream) {
return build(inputStream, null, null);
}
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 获取到内容:com.github.pagehelper.PageHelper
String interceptor = child.getStringAttribute("interceptor");
// 获取配置的属性信息
Properties properties = child.getChildrenAsProperties();
// 创建的拦截器实例
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// 将属性和拦截器绑定
interceptorInstance.setProperties(properties);
// 这个方法需要进入查看
configuration.addInterceptor(interceptorInstance);
}
}
}
public void addInterceptor(Interceptor interceptor) {
// 将拦截器添加到了 拦截器链中 而拦截器链本质上就是一个List有序集合
interceptorChain.addInterceptor(interceptor);
}
3.4 PageHelper定义的拦截信息
我们来看下PageHelper的源代码的头部定义,这里跟我们在上一节讲的自定义拦截器分页几乎一样,拦截query方法
@SuppressWarnings("rawtypes")
@Intercepts(
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class
, Object.class
, RowBounds.class
, ResultHandler.class
}))
public class PageHelper implements Interceptor {
//sql工具类
private SqlUtil sqlUtil;
//属性参数信息
private Properties properties;
//配置对象方式
private SqlUtilConfig sqlUtilConfig;
//自动获取dialect,如果没有setProperties或setSqlUtilConfig,也可以正常进行
private boolean autoDialect = true;
//运行时自动获取dialect
private boolean autoRuntimeDialect;
//多数据源时,获取jdbcurl后是否关闭数据源
private boolean closeConn = true;
}
PageHelper中已经定义了该拦截器拦截的方法是什么。
3.5 Executor
接下来我们需要分析下SqlSession的实例化过程中Executor发生了什么。我们需要从这行代码开始跟踪
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
增强Executor
到此我们明白了,Executor对象其实被我们生存的代理类增强了。invoke的代码为
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 如果是定义的拦截的方法 就执行intercept方法
if (methods != null && methods.contains(method)) {
// 进入查看 该方法增强
return interceptor.intercept(new Invocation(target, method, args));
}
// 不是需要拦截的方法 直接执行
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
/**
* Mybatis拦截器方法
*
* @param invocation 拦截器入参
* @return 返回执行结果
* @throws Throwable 抛出异常
*/
public Object intercept(Invocation invocation) throws Throwable {
if (autoRuntimeDialect) {
SqlUtil sqlUtil = getSqlUtil(invocation);
return sqlUtil.processPage(invocation);
} else {
if (autoDialect) {
initSqlUtil(invocation);
}
return sqlUtil.processPage(invocation);
}
}
该方法中的内容我们后面再分析。Executor的分析我们到此,接下来看下PageHelper实现分页的具体过程。
3.6 分页过程
接下来我们通过代码跟踪来看下具体的分页流程,我们需要分别从两行代码开始:
startPage
PageHelper.startPage(1, 5);
/**
* 开始分页
*
* @param params
*/
public static <E> Page<E> startPage(Object params) {
Page<E> page = SqlUtil.getPageFromObject(params);
//当已经执行过orderBy的时候
Page<E> oldPage = SqlUtil.getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
SqlUtil.setLocalPage(page);
return page;
}
/**
* 开始分页
*
* @param pageNum 页码
* @param pageSize 每页显示数量
* @param count 是否进行count查询
* @param reasonable 分页合理化,null时用默认配置
*/
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable) {
return startPage(pageNum, pageSize, count, reasonable, null);
}
/**
* 开始分页
*
* @param offset 页码
* @param limit 每页显示数量
* @param count 是否进行count查询
*/
public static <E> Page<E> offsetPage(int offset, int limit, boolean count) {
Page<E> page = new Page<E>(new int[]{offset, limit}, count);
//当已经执行过orderBy的时候
Page<E> oldPage = SqlUtil.getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
// 这是重点!!!
SqlUtil.setLocalPage(page);
return page;
}
selectList方法
session.selectList("com.atguigu.UserMapper.query");
public <E> List<E> selectList(String statement) {
return this.selectList(statement, null);
}
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
我们需要回到invoke方法中继续看
/**
* Mybatis拦截器方法
*
* @param invocation 拦截器入参
* @return 返回执行结果
* @throws Throwable 抛出异常
*/
public Object intercept(Invocation invocation) throws Throwable {
if (autoRuntimeDialect) {
SqlUtil sqlUtil = getSqlUtil(invocation);
return sqlUtil.processPage(invocation);
} else {
if (autoDialect) {
initSqlUtil(invocation);
}
return sqlUtil.processPage(invocation);
}
}
进入sqlUtil.processPage(invocation)方法
/**
* Mybatis拦截器方法
*
* @param invocation 拦截器入参
* @return 返回执行结果
* @throws Throwable 抛出异常
*/
private Object _processPage(Invocation invocation) throws Throwable {
final Object[] args = invocation.getArgs();
Page page = null;
//支持方法参数时,会先尝试获取Page
if (supportMethodsArguments) {
// 从线程本地变量中获取Page信息,就是我们刚刚设置的
page = getPage(args);
}
//分页信息
RowBounds rowBounds = (RowBounds) args[2];
//支持方法参数时,如果page == null就说明没有分页条件,不需要分页查询
if ((supportMethodsArguments && page == null)
//当不支持分页参数时,判断LocalPage和RowBounds判断是否需要分页
|| (!supportMethodsArguments && SqlUtil.getLocalPage() == null && rowBounds == RowBounds.DEFAULT)) {
return invocation.proceed();
} else {
//不支持分页参数时,page==null,这里需要获取
if (!supportMethodsArguments && page == null) {
page = getPage(args);
}
// 进入查看
return doProcessPage(invocation, page, args);
}
}
/**
* Mybatis拦截器方法
*
* @param invocation 拦截器入参
* @return 返回执行结果
* @throws Throwable 抛出异常
*/
private Page doProcessPage(Invocation invocation, Page page, Object[] args) throws Throwable {
//保存RowBounds状态
RowBounds rowBounds = (RowBounds) args[2];
//获取原始的ms
MappedStatement ms = (MappedStatement) args[0];
//判断并处理为PageSqlSource
if (!isPageSqlSource(ms)) {
processMappedStatement(ms);
}
//设置当前的parser,后面每次使用前都会set,ThreadLocal的值不会产生不良影响
((PageSqlSource)ms.getSqlSource()).setParser(parser);
try {
//忽略RowBounds-否则会进行Mybatis自带的内存分页
args[2] = RowBounds.DEFAULT;
//如果只进行排序 或 pageSizeZero的判断
if (isQueryOnly(page)) {
return doQueryOnly(page, invocation);
}
//简单的通过total的值来判断是否进行count查询
if (page.isCount()) {
page.setCountSignal(Boolean.TRUE);
//替换MS
args[0] = msCountMap.get(ms.getId());
//查询总数
Object result = invocation.proceed();
//还原ms
args[0] = ms;
//设置总数
page.setTotal((Integer) ((List) result).get(0));
if (page.getTotal() == 0) {
return page;
}
} else {
page.setTotal(-1l);
}
//pageSize>0的时候执行分页查询,pageSize<=0的时候不执行相当于可能只返回了一个count
if (page.getPageSize() > 0 &&
((rowBounds == RowBounds.DEFAULT && page.getPageNum() > 0)
|| rowBounds != RowBounds.DEFAULT)) {
//将参数中的MappedStatement替换为新的qs
page.setCountSignal(null);
// 重点是查看该方法
BoundSql boundSql = ms.getBoundSql(args[1]);
args[1] = parser.setPageParameter(ms, args[1], boundSql, page);
page.setCountSignal(Boolean.FALSE);
//执行分页查询
Object result = invocation.proceed();
//得到处理结果
page.addAll((List) result);
}
} finally {
((PageSqlSource)ms.getSqlSource()).removeParser();
}
//返回结果
return page;
}
进入 BoundSql boundSql = ms.getBoundSql(args[1])方法跟踪到PageStaticSqlSource类中的
@Override
protected BoundSql getPageBoundSql(Object parameterObject) {
String tempSql = sql;
String orderBy = PageHelper.getOrderBy();
if (orderBy != null) {
tempSql = OrderByParser.converToOrderBySql(sql, orderBy);
}
tempSql = localParser.get().getPageSql(tempSql);
return new BoundSql(configuration, tempSql, localParser.get().getPageParameterMapping(configuration, original.getBoundSql(parameterObject)), parameterObject);
}
3.7 小总结
我们发现PageHelper分页的实现原来是在我们执行SQL语句之前动态的将SQL语句拼接了分页的语句,从而实现了从数据库中分页获取的过程。 PageHelpler分页使用了拦截器,自动拼接limit语句,反射调用目标方法。