开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 11 天,点击查看活动详情
前面几篇文章,我们介绍了 逻辑分页及物理分页的几种实现方式,包括 内存分页,limit分页,RowRounds分页, 这几种都属于定制SQL, 操作起来不方便
今天我们实现一种通用的分页方式,通过自定义Interceptor来实现Mybatis分页
1. 创建Interceptor
Interceptor 底层实现逻辑还是Limit 来控制底层SQL 逻辑来实现分页效果
import org.apache.ibatis.executor.parameter.ParameterHandler;
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.Properties;
/**
* 利用MyBatis拦截器进行分页
*
* @Intercepts 说明是一个拦截器
* @Signature 拦截器的签名
* type 拦截的类型 四大对象之一( Executor,ResultSetHandler,ParameterHandler,StatementHandler)
*
*/
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
@Slf4j
public class DefinedPageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//获取StatementHandler,默认的是RoutingStatementHandler
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
//获取StatementHandler的包装类
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
//分隔代理对象
while (metaObject.hasGetter("h")) {
Object obj = metaObject.getValue("h");
metaObject = SystemMetaObject.forObject(obj);
}
while (metaObject.hasGetter("target")) {
Object obj = metaObject.getValue("target");
metaObject = SystemMetaObject.forObject(obj);
}
//获取查看接口映射的相关信息
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
String mapId = mappedStatement.getId();
//拦截以ByInterceptor结尾的请求,统一实现分页
if (mapId.matches(".+ByInterceptor$")) {
log("已触发分页拦截器");
//获取进行数据库操作时管理参数的Handler
ParameterHandler parameterHandler = (ParameterHandler) metaObject.getValue("delegate.parameterHandler");
//获取请求时的参数
PageInfo info = (PageInfo) parameterHandler.getParameterObject();
//获取原始SQL语句
String originalSql = (String) metaObject.getValue("delegate.boundSql.sql");
//构建分页功能的SQL语句
String sql = originalSql.trim() + " limit " + info.getPageNo() + ", " + info.getPageSize();
metaObject.setValue("delegate.boundSql.sql", sql);
}
//调用原对象方法,进入责任链下一级
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
//生成Object对象的动态代理对象
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
//如果分页每页数量是统一的,可以在这里进行统一配置,也就无需再传入PageInfo信息了
}
}
他是如何实现 拦截的 ?
注意看这里 这里会对方法进行遍历 只要你的方法结尾是以 ByInterceptor 结尾的 方法, 都会被拦截到实现 分页逻辑,从而实现了对所有方法的统一处理
然后 里面的Page 就是 hutool工具包中的Page分页信息
!!! 注意这里 就是拼装limit信息
!!! 注意这里 就是拼装limit信息
!!! 注意这里 就是拼装limit信息
重要事情说三遍
我们要取的是 page的起始位置 getStartPosition信息
" limit " + info.getStartPosition() + ", " + info.getPageSize();
2.配置Interceptor
我们需要构造自己的 SqlSessionFactory 来配置interceptor
!!!!! 注意 SqlSession的Plugins的 interceptor 要用 mybatis 配置包中的信息
package com.jzj.tdmybatis.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
import java.io.IOException;
@Configuration
public class DataSourceConfiguration {
@ConfigurationProperties(prefix = "spring.datasource.druid")
@Bean
public DataSource dataSource(){
return new DruidDataSource();
}
@Bean
public SqlSessionFactoryBean sqlSession() {
SqlSessionFactoryBean sqlSession = new SqlSessionFactoryBean();
sqlSession.setDataSource(dataSource());
try {
Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath:mapper/*.xml");
sqlSession.setMapperLocations(resources);
//配置自定义的Interceptro作为MyBatis的Interceptor,完成分页操作
DefinedPageInterceptor definedPageInterceptor = new DefinedPageInterceptor();
//这里的 interceptor 要用 mybatis包中的
sqlSession.setPlugins(new Interceptor[]{definedPageInterceptor});
return sqlSession;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
3. XML 文件中方法实现
我们只需要在 XML文件中定义方法时候 以 "ByInterceptor"结尾即可
查询UserInfo信息
/**
* Page 就是 hutool 工具包中的 Page分页
*/
List<UserInfoPO> selectMyUserByInterceptor(Page page);
=======================================================
<select id="selectMyUserByInterceptor" resultType="com.jzj.tdmybatis.domain.po.UserInfoPO">
select * from user_info
</select>
4.测试Service及TestController信息
Service信息
/**
* 测试自定义拦截器分页插件
*/
List<UserInfoPO> interceptorPage(Page page);
============================================
@Override
public List<UserInfoPO> interceptorPage(Page page) {
return mapper.selectMyUserByInterceptor(page);
}
TestController信息
/**
* 查询user接口
*/
@RequestMapping("/temp/query9")
@ResponseBody
public void query9(Integer pageNumber) {
//pageNumber 默认都是 从第1页开始,每页取3个
Page page = new Page(pageNumber - 1, 3);
List<UserInfoPO> interceptorPage = pageService.interceptorPage(page);
log.info(" interceptorPage 第{}页 数据 \r\n:{} ", page.getPageNumber(), printUser(interceptorPage));
}
private String printUser(List<UserInfoPO> users) {
StringBuffer sb = new StringBuffer();
for (UserInfoPO user : users) {
sb.append(user.toString()).append("\r\n");
}
return sb.toString();
}
5.查询结果
请求 curl 127.0.0.1:8800/temp/query9?pageNumber=3
可以看到查询结果
的确是查询出来3条数据, 分页信息 也是正确的
根据日志 可以看出来 的确是触发了分页拦截器
c.j.t.config.DefinedPageInterceptor : 已触发分页拦截器
日志信息
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@75b875b5] was not registered for synchronization because synchronization is not active
2023-02-11 22:57:04.602 INFO 12840 --- [nio-8800-exec-1] com.alibaba.druid.pool.DruidDataSource : {dataSource-1} inited
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7e89eb56] will not be managed by Spring
2023-02-11 22:57:04.603 INFO 12840 --- [nio-8800-exec-1] c.j.t.config.DefinedPageInterceptor : 已触发分页拦截器
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@75b875b5]
2023-02-11 22:57:04.625 INFO 12840 --- [nio-8800-exec-1] c.j.tdmybatis.controller.TestController : interceptorPage 第2页 数据
:UserInfoPO{id=7, userId='null', userName='null', age=41, address='郑州'}
UserInfoPO{id=8, userId='null', userName='null', age=4, address='武汉'}
UserInfoPO{id=9, userId='null', userName='null', age=14, address='武汉'}
查询结果
现在我们已经可以通过自定义的Interceptor来实现 Mybatis的自定义分页逻辑了,但是这种方式归根到底就是 通过方法的 Interceptor规则来实现切面
通过对切面的每一个方法进行拦截,实现拼接Limit方法 来实现的, 这种如果 limit的起始页比较大的时候,游标位置会查询量很大,效率低下
我们还有一种以来插件分页的方法, 下篇文章我们着重介绍 PageHelper方式