【MyBatis源码】插件体系

简介: 概述Mybatis作为一个应用广泛的优秀的ORM开源框架,这个框架具有强大的灵活性,在四大组件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了简单易用的插 件扩展机制。Mybatis对持久层的操作就是借助于四大核心对象。MyBatis支持用插件对四大核心对象进行拦截,对mybatis来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说,MyBatis中的四大对象都是代理对象插件机制是为了对MyBatis现有体系进行扩展 而提供的入口。底层通过动态代理实现。可供代

概述
Mybatis作为一个应用广泛的优秀的ORM开源框架,这个框架具有强大的灵活性,在四大组件(Executor、StatementHandler、ParameterHandler、ResultSetHandler)处提供了简单易用的插 件扩展机制。Mybatis对持久层的操作就是借助于四大核心对象。MyBatis支持用插件对四大核心对象进行拦截,对mybatis来说插件就是拦截器,用来增强核心对象的功能,增强功能本质上是借助于底层的动态代理实现的,换句话说,MyBatis中的四大对象都是代理对象

插件机制是为了对MyBatis现有体系进行扩展 而提供的入口。底层通过动态代理实现。可供代理拦截的接口有四个:

执行器Executor (update、query、commit、rollback等方法);
SQL语法构建器StatementHandler (prepare、parameterize、batch、updates query等方 法);
参数处理器ParameterHandler (getParameterObject、setParameters方法);
结果集处理器ResultSetHandler (handleResultSets、handleOutputParameters等方法);
这四个接口已经涵盖从发起接口调用到SQl声明、参数处理、结果集处理的全部流程。接口中任何一个方法都可以进行拦截改变方法原有属性和行为。不过这是一个非常危险的行为,稍不注意就会破坏MyBatis核心逻辑还不自知。所以在在使用插件之前一定要非常清晰MyBatis内部机制。

插件的使用
创建一个插件在MyBatis当中是一件非常简单的事情 ,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

@Intercepts({@Signature(
type= Executor.class,
method = "update",
args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
// 当执行目标方法时会被方法拦截
public Object intercept(Invocation invocation) throws Throwable {
long begin = System.currentTimeMillis();
try {
return invocation.proceed();// 继续执行原逻辑;
} finally {
System.out.println("执行时间:"+(System.currentTimeMillis() - begin));
}
}
// 生成代理对象,可自定义生成代理对象,这样就无需配置@Intercepts注解。另外需要自行判断是否为拦截目标接口。
public Object plugin(Object target) {
return Plugin.wrap(target,this);// 调用通用插件代理生成机器
}
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Intercepts是MyBatis中用于声明插件拦截信息的注解。
当我们开发一个MyBatis插件时,需要指定该插件要拦截哪些对象的哪些方法。我们就可以使用@Intercepts注解来声明这些拦截信息。
@Intercepts注解的定义如下:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
Signature[] value();
}
1
2
3
4
5
它包含一个Signature数组,每个Signature表示一条拦截信息,包含三个属性:

type:要拦截的接口类型
method:接口中的方法名
args:方法的参数类型
所以,当我们使用@Intercepts注解一个插件时,MyBatis会根据其value属性解析出该插件要拦截的所有信息,然后生成代理对象来实现方法拦截。

在config.xml 中添加插件配置:




1
2
3
​ 通过上述配置即可以监控 在执行过修改过程当中,所耗费的时间。

​注:只有从外部类调用拦截目标时 拦截才会生效,如果在内部调用代理逻辑会生效。如在Executor中有两个Query 方法,第一个会调用第二个query。如果你拦截的是第二个Query 则不会成功。

插件代理机制
Configuration 中有一个InterceptorChain(拦截链)保存了所有拦截器,当创建四大对象之后就会调用拦截链,对目标对象进行拦截代理。

对于这个插件拦截实现类似Spring AOP 但其实现要简单很多。代理很轻量清晰,连注释都显得多余。

接下来通过一个自动分页插件全面掌握插件的用法

自动分页插件
自动分页是指查询时,指定页码和大小等参数,插件就自动进行分页查询,并返回总数量。这个插件设计需要满足以下目特性:

易用性:不需要额外配置,参数中带上 Page 即可. Page尽可能简单
不对使用场景作假设:不限制用户使用方式,如接口调用,还是会话调。又或是对Executor 以及StatementHandler的选择等。不能影响缓存业务
友好性:当不符合分页情况下,作出友好的用户提示。如在修改操作中付入分页参数。或用户本身已在查询语句已自带分页语句 ,这种情况应作出提示。
拦截目标
接下来要解决的问题,是插件的入口写在哪里?去拦截的目标有哪些?

参数处理器 和结果集处理器显然不合适,而Executor.query() 又需要额外考虑 一、二级缓存逻辑。最后还是选定StatementHandler. 并拦截其prepare 方法。

@Intercepts(@Signature(type = StatementHandler.class,
method = "prepare", args = {Connection.class,
Integer.class}))
1
2
3
分页插件原理
首先设定一个Page类,其包含total、size、index 3个属性,在Mapper接口中声明该参数即表示需要执行自动分页逻辑。

总体实现步骤包含3个:

检测是否满足分页条件
自动求出当前查询的总行数
修改原有的SQL语句 ,添加 limit offset 关键字。
接下来我们对每一步进行说明:

检测是否满足分页条件

分页条件是 1.是否为查询方法,2.查询参数中是否带上Page参数。在intercept 方法中可直接获得拦截目标StatementHandler ,通过它又可以获得BoundSql 里面就包含了SQL 和参数。遍历参数即可获得Page。

// 带上分页参数
StatementHandler target = (StatementHandler) invocation.getTarget();
// SQL包 sql、参数、参数映射
BoundSql boundSql = target.getBoundSql();
Object parameterObject = boundSql.getParameterObject();
Page page = null;
if (parameterObject instanceof Page) {
page = (Page) parameterObject;
} else if (parameterObject instanceof Map) {
page = (Page) ((Map) parameterObject).values().stream().filter(v -> v instanceof Page).findFirst().orElse(null);
}
1
2
3
4
5
6
7
8
9
10
11
查询总行数

实现逻辑是 将原查询SQL作为子查询进行包装成子查询,然后用原有参数,还是能过原来的参数处理器进行赋值。关于执行是采用JDBC 原生API实现。MyBatis执行器,从而绕开了一二级缓存。

private int selectCount(Invocation invocation) throws SQLException {
int count = 0;
StatementHandler target = (StatementHandler) invocation.getTarget();
// SQL包 sql、参数、参数映射
String countSql = String.format("select count(*) from (%s) as _page", target.getBoundSql().getSql());
// JDBC
Connection connection = (Connection) invocation.getArgs()[0];
PreparedStatement preparedStatement = connection.prepareStatement(countSql);
target.getParameterHandler().setParameters(preparedStatement);
ResultSet resultSet = preparedStatement.executeQuery();
if (resultSet.next()) {
count = resultSet.getInt(1);
}
resultSet.close();
preparedStatement.close();

return count;

}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
修改原SQL

最后一项就是修改原来的SQL,前面我是可以拿到BoundSql 的,但它没有提供修改SQL的方法,这里可以采用反射强行为SQL属性赋值。也可以采用MyBatis提供的工具类SystemMetaObject 来 赋值

String newSql= String.format("%s limit %s offset %s", boundSql.getSql(),page.getSize(),page.getOffset());
SystemMetaObject.forObject(boundSql).setValue("sql",newSql);
————————————————
版权声明:本文为CSDN博主「十八岁讨厌编程」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/zyb18507175502/article/details/131142633

目录
相关文章
|
3月前
|
SQL XML Java
mybatis-源码深入分析(一)
mybatis-源码深入分析(一)
|
4月前
|
SQL XML Java
8、Mybatis-Plus 分页插件、自定义分页
这篇文章介绍了Mybatis-Plus的分页功能,包括如何配置分页插件、使用Mybatis-Plus提供的Page对象进行分页查询,以及如何在XML中自定义分页SQL。文章通过具体的代码示例和测试结果,展示了分页插件的使用和自定义分页的方法。
8、Mybatis-Plus 分页插件、自定义分页
|
1月前
|
SQL Java 数据库连接
深入 MyBatis-Plus 插件:解锁高级数据库功能
Mybatis-Plus 提供了丰富的插件机制,这些插件可以帮助开发者更方便地扩展 Mybatis 的功能,提升开发效率、优化性能和实现一些常用的功能。
191 26
深入 MyBatis-Plus 插件:解锁高级数据库功能
|
28天前
|
SQL Java 数据库连接
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
MyBatis-Plus是一个MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。本文讲解了最新版MP的使用教程,包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段等核心功能。
【MyBatisPlus·最新教程】包含多个改造案例,常用注解、条件构造器、代码生成、静态工具、类型处理器、分页插件、自动填充字段
|
1月前
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
3月前
|
SQL Java 数据库连接
解决mybatis-plus 拦截器不生效--分页插件不生效
本文介绍了在使用 Mybatis-Plus 进行分页查询时遇到的问题及解决方法。依赖包包括 `mybatis-plus-boot-starter`、`mybatis-plus-extension` 等,并给出了正确的分页配置和代码示例。当分页功能失效时,需将 Mybatis-Plus 版本改为 3.5.5 并正确配置拦截器。
914 6
解决mybatis-plus 拦截器不生效--分页插件不生效
|
2月前
|
前端开发 Java 数据库连接
表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
本文是一份全面的表白墙/留言墙项目教程,使用SpringBoot + MyBatis技术栈和MySQL数据库开发,涵盖了项目前后端开发、数据库配置、代码实现和运行的详细步骤。
66 0
表白墙/留言墙 —— 中级SpringBoot项目,MyBatis技术栈MySQL数据库开发,练手项目前后端开发(带完整源码) 全方位全步骤手把手教学
|
2月前
|
Java 数据库连接 mybatis
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码
该文档详细介绍了如何在Springboot Web项目中整合Mybatis,包括添加依赖、使用`@MapperScan`注解配置包扫描路径等步骤。若未使用`@MapperScan`,系统会自动扫描加了`@Mapper`注解的接口;若使用了`@MapperScan`,则按指定路径扫描。文档还深入分析了相关源码,解释了不同情况下的扫描逻辑与优先级,帮助理解Mybatis在Springboot项目中的自动配置机制。
157 0
Springboot整合Mybatis,MybatisPlus源码分析,自动装配实现包扫描源码
|
3月前
|
SQL XML Java
springboot整合mybatis-plus及mybatis-plus分页插件的使用
这篇文章介绍了如何在Spring Boot项目中整合MyBatis-Plus及其分页插件,包括依赖引入、配置文件编写、SQL表创建、Mapper层、Service层、Controller层的创建,以及分页插件的使用和数据展示HTML页面的编写。
springboot整合mybatis-plus及mybatis-plus分页插件的使用
|
4月前
|
XML Java 数据库连接
mybatis源码研究、搭建mybatis源码运行的环境
这篇文章详细介绍了如何搭建MyBatis源码运行的环境,包括创建Maven项目、导入源码、添加代码、Debug运行研究源码,并提供了解决常见问题的方法和链接到搭建好的环境。
mybatis源码研究、搭建mybatis源码运行的环境