MyBatis拦截器自定义分页插件实现
MyBaits是一个开源的优秀的持久层框架,SQL语句与代码分离,面向配置的编程,良好支持复杂数据映射,动态SQL;MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
通常使用MyBatis时使用以下几种形式进行分页:
- 逻辑分页:RowBounds
物理分页: 在SQL里面使用LIMIT或者使用第三方插件(PageHelper等)
环境及介绍
导入核心依赖:
<!-- SpringBoot MyBatis starter -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.2.2</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
搭建环境步骤可以参考:SpringBoot企业中常用starter
本次主要实现一个接口org.apache.ibatis.plugin.Interceptor,在接口中有3个方法为:
Object intercept(Invocation invocation) throws Throwable;
Object plugin(Object target);
void setProperties(Properties properties);
- intercept 方法是主要拦截执行方法。
- plugin 方法是决定当前对象是否需要生成代理对象。
setProperties 设置运行时mybatis核心配置参数方法。
Mybatis的拦截器实现机制,使用的是JDK的InvocationHandler,当我们调用ParameterHandler,ResultSetHandler,StatementHandler,Executor的对象的时候,实际上使用的是Plugin这个代理类的对象,这个类实现了InvocationHandler接口。
当我们调用ParameterHandler,ResultSetHandler,StatementHandler,Executor的对象的时候,
实际上使用的是Plugin这个代理类的对象,这个类实现了InvocationHandler接口。自定义分页实现
创建
Pager实体对象,用来进行分页,实体内容如下:
@Data
@ToString
public class Pager {
/*当前页*/
private int page;
/*每页大小*/
private int size;
/*总记录*/
private long total;
/*总页数*/
private int totalPage;
/*自定义分页sql*/
private String customSQL;
/*分页执行时长*/
private long executeTime;
}
这里customSQL变量为自定义分页SQL,很多时候以为sql过于复杂关联了N张表,获取了N个字段会造成查询时间过于缓慢,加入这个字段主要是为了在一些复杂的SQL中不暴力使用默认的分页数量统计,可以自己根据SQL去除不需要的字段,已经不需要的表连接后的SQL来执行分页数量的统计。
定义分页处理接口PagerHandler,内容如下:
public interface PagerHandler {
/**
* 获取sql执行参数
* @param boundSql
* @return
*/
public Pager getPager(BoundSql boundSql);
/**
* 执行分页
*
* @param pager
* @param boundSql
* @param connection
* @param metaObject
* @return
* @throws SQLException
*/
public Pager executer(Pager pager, BoundSql boundSql, Connection connection, MetaObject metaObject) throws SQLException;
}
创建MyBats拦截器实现分页PagerMyBatisInterceptor,该类实现接口org.apache.ibatis.plugin.Interceptor和我们自己定义的PagerHandler,重写接口中方法。
我们还需要告诉MyBatis具体在什么地点进行拦截,使用@Intercepts来标注:
@Intercepts(value = {
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
主要拦截StatementHandler中prepare编译参数方法该方法需要传入参数类型为Connection.class, Integer.class,在MyBatis 3.4.1版本下参数只有一个Connection
在类中定义一些常量:
private final Logger log = LoggerFactory.getLogger(PagerMyBatisInterceptor.class);
private final int CONNECTION_INDEX = 0; //连接参数索引
private final DefaultReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();//默认反射工厂
private final String DELEGATE_MAPPED_STATEMENT = "delegate.mappedStatement";//反射值获取路径
private final String DELEGATE_PARAMETER_HANDLER = "delegate.parameterHandler";//反射值获取路径
具体拦截器内容:
@Component
@Intercepts(value = {
@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class PagerMyBatisInterceptor implements PagerHandler, Interceptor {
private final Logger log = LoggerFactory.getLogger(PagerMyBatisInterceptor.class);
private final int CONNECTION_INDEX = 0;
private final DefaultReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();
private final String DELEGATE_MAPPED_STATEMENT = "delegate.mappedStatement";
private final String DELEGATE_PARAMETER_HANDLER = "delegate.parameterHandler";
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object[] args = invocation.getArgs();
//数据库连接
Connection connection = (Connection) args[CONNECTION_INDEX];
//负责处理Mybatis与JDBC之间Statement的交互
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
//MetaObject是Mybatis提供的一个用于方便、优雅访问对象属性的对象,通过它可以简化代码、不需要try/catch各种reflect异常,同时它支持对JavaBean、Collection、Map三种类型对象的操作。
MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY,
SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);
//MappedStatement表示的是XML中的一个SQL
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue(DELEGATE_MAPPED_STATEMENT);
//SqlCommandType代表SQL类型
SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
//BoundSql则是其保存Sql语句的对象
BoundSql boundSql = statementHandler.getBoundSql();
//分页对象
Pager pager = getPager(boundSql);
if (sqlCommandType.compareTo(SqlCommandType.SELECT) == 0 && pager != null) {
executer(pager, boundSql, connection, metaObject);
//执行查询
int left = (pager.getPage() - 1) * pager.getSize();
int right = pager.getSize();
String rewriteSql = boundSql.getSql() + " LIMIT " + left + "," + right;
metaObject.setValue("boundSql.sql", rewriteSql);
}
long startTime = System.currentTimeMillis();
Object proceed = invocation.proceed();
long endTime = System.currentTimeMillis();
log.info("SQL TYPE [{}] , SQL EXECUTE TIME [{}] SQL:\n{}", sqlCommandType, startTime - endTime, boundSql.getSql().toUpperCase());
return proceed;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
//TODO 设置mybatis参数
}
@Override
public Pager getPager(BoundSql boundSql) {
Object parameterObject = boundSql.getParameterObject();
if (parameterObject instanceof Pager) {
return (Pager) parameterObject;
} else if (parameterObject instanceof Map) {
Map<String, Object> paramMap = (Map<String, Object>) parameterObject;
Iterator<String> keys = paramMap.keySet().iterator();
while (keys.hasNext()) {
String key = keys.next();
Object obj = paramMap.get(key);
if (obj instanceof Pager) {
return (Pager) obj;
}
}
}
return null;
}
@Override
public Pager executer(Pager pager, BoundSql boundSql, Connection connection, MetaObject metaObject) throws SQLException {
if (pager.getPage() == 0) {
pager.setPage(0);
}
if (pager.getSize() == 0) {
pager.setSize(0);
}
if (pager.getCustomSQL() == null) {
//如果自己没有定义分页SQL,那么使用默认暴力分页
pager.setCustomSQL("SELECT COUNT(1) FROM (" + boundSql.getSql() + " ) tmp_table");
}
// 预编译
PreparedStatement prepareStatement = connection.prepareStatement(pager.getCustomSQL());
// 预编译执行
ParameterHandler parameterHandler = (ParameterHandler) metaObject.getValue(DELEGATE_PARAMETER_HANDLER);
parameterHandler.setParameters(prepareStatement); // 给sql语句设置参数
long startTime = System.currentTimeMillis();
ResultSet resultSet = prepareStatement.executeQuery();
long endTime = System.currentTimeMillis();
log.info("sql execute time {} sql:\n{}", startTime - endTime, pager.getCustomSQL().toUpperCase());
if (resultSet.next()) {
long total = (long) resultSet.getObject(1);// 总记录数量
int totalPageNum = (int) ((total + pager.getSize() - 1) / pager.getSize());
pager.setTotal(total);
pager.setTotalPage(totalPageNum);
pager.setExecuteTime(startTime - endTime);
}
return pager;
}
}
通过方法getPager获取到pager对象,如果是Map参数那么就优先第一个通过引用的传递在BoundSql对象中获取到,然后执行分页获取里面的值进行计算,通过引用对象返回总记录数,总页数等。
在SpringBoot中如果需要使拦截器生效只需要在类型使用@Component将该类交给Spring IOC管理即可,至于拦截器顺序如:
有拦截器 PagerMyBatisInterceptor 与 OneInterceptor ,想要分页拦截器作为第二个拦截器只需要在类上标注@ConditionalOnBean(OneInterceptor)即可,在第一个拦截器实例化后再实例化第二个拦截器.
Pager使用方式
List<Map<String, Object>> selectUser(Pager pager);
List<Map<String, Object>> selectUser(Map<String,Object> paramMap);
创建一个Pager对象传入即可。
本文源码地址:https://github.com/450255266/open-doubi/tree/master/spring-boot/custom-mybatis-pager
MyBatis拦截器自定义分页插件实现的更多相关文章
- Mybatis拦截器实现分页
本文介绍使用Mybatis拦截器,实现分页:并且在dao层,直接返回自定义的分页对象. 最终dao层结果: public interface ModelMapper { Page<Model&g ...
- mybatis拦截器实现分页功能的示例讲解
import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import jav ...
- Mybatis拦截器介绍及分页插件
1.1 目录 1.1 目录 1.2 前言 1.3 Interceptor接口 1.4 注册拦截器 1.5 Mybatis可拦截的方法 1.6 利用拦截器进行分页 1.2 前言 拦截器的一 ...
- Mybatis拦截器(插件实现原理)
在mybatis的mybatis.cfg.xml中插入: <plugins> <plugin interceptor="cn.sxt.util.PageIntercepto ...
- mybaits拦截器+自定义注解
实现目的:为了存储了公共字典表主键的其他表在查询的时候不用关联查询(所以拦截位置位于mybaits语句查询得出结果集后) 项目环境 :springboot+mybaits 实现步骤:自定义注解——自定 ...
- spring boot 实现mybatis拦截器
spring boot 实现mybatis拦截器 项目是个报表系统,服务端是简单的Java web架构,直接在请求参数里面加了个query id参数,就是mybatis mapper的query id ...
- mybatis拦截器使用
目录 mybatis 拦截器接口Interceptor spring boot + mybatis整合 创建自己的拦截器MyInterceptor @Intercepts注解 mybatis拦截器入门 ...
- Mybatis拦截器执行过程解析
上一篇文章 Mybatis拦截器之数据加密解密 介绍了 Mybatis 拦截器的简单使用,这篇文章将透彻的分析 Mybatis 是怎样发现拦截器以及调用拦截器的 intercept 方法的 小伙伴先按 ...
- Mybatis拦截器实现原理深度分析
1.拦截器简介 拦截器可以说使我们平时开发经常用到的技术了,Spring AOP.Mybatis自定义插件原理都是基于拦截器实现的,而拦截器又是以动态代理为基础实现的,每个框架对拦截器的实现不完全相同 ...
随机推荐
- QRowTable表格控件(四)-效率优化之-优化数据源
目录 一.开心一刻 二.问题分析 三.重写数据源 1.自己存储数据 2.重写data接口 四.比较 五.相关文章 原文链接:QRowTable表格控件(四)-效率优化之-优化数据源 一.开心一刻 一程 ...
- 大型公司java架构师面试实战讲解高清视频教程 15课
目录: 01.面试必考之HashMap源码分析与实现02.探索JVM底层奥秘ClassLoader源码分析与案例讲解03.大型网站数据库瓶颈之数据库分库分表方案实践04.资料为图灵学院所有05.大型公 ...
- GC垃圾收集算法
JVM中的垃圾收集算法实现涉及大量的程序细节,而且各个平台的虚拟机操作内存的方法又各不相同,这里介绍几种垃圾收集算法的思想. 1.标记-清除算法 这是最基础的垃圾收集算法,分为“标记”和“清除”两个阶 ...
- .Net基础篇_学习笔记_第四天_switch-case
swith-case 用来处理多条件的定值的判断. 语法: switch(变量或者表达式的值) { case 值1:要执行的代码: break: case 值2:要执行的代码: break: case ...
- Docker Compose部署项目到容器-基于Tomcat和mysql的项目yml配置文件代码
场景 Docker-Compose简介与Ubuntu Server 上安装Compose: https://blog.csdn.net/BADAO_LIUMANG_QIZHI/article/deta ...
- Winforn中导入Excel并显示然后获取多选框选中的内容
场景 使用NPOI导入Excel并赋值给DataTable,然后显示在DataGrdView上,并且添加多选框,然后获取选中行的内容. Winform中使用NPOI实现Excel导入并赋值给DataT ...
- Dagger2 探索记1——四大基本组件(一)
喝很多自主学习的人,我接触Dagger 2 框架的原因是刚进公司的时候导师给安排的学习任务,学习方式是组内培训. 听到这个消息的我,以为是部门的人轮流给我讲课. 后来导师跟我说,组内培训的意思是,我先 ...
- centos7 下安装mysql5.7 数据库并使用nevicat连接数据库
安装mysql5.7的教程: https://www.cnblogs.com/yybrhr/p/9810375.html 遇到的问题: 无法连接,到阿里云服务器安全组设置3306端口
- python自增自减?赋值语句返回值?逗号表达式?
咳咳,直接进入正题吧. 自增自减(++/--),以及赋值语句,还有逗号表达式都是在C/C++中常见的运算符或表达式. 熟悉C/C++的小伙伴们都知道,在C/C++中: 自增自减(前缀/后缀)运算符将实 ...
- jmeter linux压测报错:Error in NonGUIDriver java.lang.IllegalArgumentException: Problem loading XML from:'/home/server/ptest/disk_out.jmx'.
1.linux环境jmeter与win环境编写脚本的jmeter版本不一致,版本改为一致 2.脚本中存在中文,去除中文 3.脚本中存在类似于jp@gc - Active Threads Over Ti ...