当前位置:   article > 正文

【SpringBoot + Mybatis 系列】Mybatis 之插件机制 Interceptor_ibaits 自定义插件

ibaits 自定义插件

【SpringBoot + Mybatis 系列】Mybatis 之插件机制 Interceptor

在 Mybatis 中,插件机制提供了非常强大的扩展能力,在 sql 最终执行之前,提供了四个拦截点,支持不同场景的功能扩展

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

  • ParameterHandler (getParameterObject, setParameters)

  • ResultSetHandler (handleResultSets, handleOutputParameters)

  • StatementHandler (prepare, parameterize, batch, update, query)

本文将主要介绍一下自定义 Interceptor 的使用姿势,并给出一个通过自定义插件来输出执行 sql,与耗时的 case

I. 环境准备

1. 数据库准备

使用 mysql 作为本文的实例数据库,新增一张表

  1. CREATE TABLE `money` (
  2.   `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  3.   `name` varchar(20NOT NULL DEFAULT '' COMMENT '用户名',
  4.   `money` int(26NOT NULL DEFAULT '0' COMMENT '钱',
  5.   `is_deleted` tinyint(1NOT NULL DEFAULT '0',
  6.   `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  7.   `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  8.   PRIMARY KEY (`id`),
  9.   KEY `name` (`name`)
  10. ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

2. 项目环境

本文借助 SpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEA进行开发

pom 依赖如下

  1. <dependencies>
  2.     <dependency>
  3.         <groupId>org.mybatis.spring.boot</groupId>
  4.         <artifactId>mybatis-spring-boot-starter</artifactId>
  5.         <version>2.2.0</version>
  6.     </dependency>
  7.     <dependency>
  8.         <groupId>mysql</groupId>
  9.         <artifactId>mysql-connector-java</artifactId>
  10.     </dependency>
  11. </dependencies>

db 配置信息 application.yml

  1. spring:
  2.   datasource:
  3.     url: jdbc:mysql://127.0.0.1:3306/story?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai
  4.     username: root
  5.     password:

II. 实例演示

关于 myabtis 的配套 Entity/Mapper 相关内容,推荐查看之前的系列博文,这里就不贴出来了,将主要集中在 Interceptor 的实现上

1. 自定义 interceptor

实现一个自定义的插件还是比较简单的,试下org.apache.ibatis.plugin.Interceptor接口即可

比如定义一个拦截器,实现 sql 输出,执行耗时输出

  1. @Slf4j
  2. @Component
  3. @Intercepts(value = {@Signature(type = Executor.classmethod = "query", args = {MappedStatement.classObject.class, RowBounds.class, ResultHandler.class}),
  4.         @Signature(type = Executor.classmethod = "update", args = {MappedStatement.classObject.class}),
  5. })
  6. public class ExecuteStatInterceptor implements Interceptor {
  7.     @Override
  8.     public Object intercept(Invocation invocation) throws Throwable {
  9.         // MetaObject 是 Mybatis 提供的一个用于访问对象属性的对象
  10.         MappedStatement statement = (MappedStatement) invocation.getArgs()[0];
  11.         BoundSql sql = statement.getBoundSql(invocation.getArgs()[1]);
  12.         long start = System.currentTimeMillis();
  13.         List<ParameterMapping> list = sql.getParameterMappings();
  14.         OgnlContext context = (OgnlContext) Ognl.createDefaultContext(sql.getParameterObject());
  15.         List<Object> params = new ArrayList<>(list.size());
  16.         for (ParameterMapping mapping : list) {
  17.             params.add(Ognl.getValue(Ognl.parseExpression(mapping.getProperty()), context, context.getRoot()));
  18.         }
  19.         try {
  20.             return invocation.proceed();
  21.         } finally {
  22.             System.out.println("------------> sql: " + sql.getSql() + "\n------------> args: " + params + "------------> cost: " + (System.currentTimeMillis() - start));
  23.         }
  24.     }
  25.     @Override
  26.     public Object plugin(Object o) {
  27.         return Plugin.wrap(o, this);
  28.     }
  29.     @Override
  30.     public void setProperties(Properties properties) {
  31.     }
  32. }

注意上面的实现,核心逻辑在intercept方法,内部实现 sql 获取,参数解析,耗时统计

1.1 sql 参数解析说明

上面 case 中,对于参数解析,mybatis 是借助 Ognl 来实现参数替换的,因此上面直接使用 ognl 表达式来获取 sql 参数,当然这种实现方式比较粗暴

  1. // 下面这一段逻辑,主要是OGNL的使用姿势
  2. OgnlContext context = (OgnlContext) Ognl.createDefaultContext(sql.getParameterObject());
  3. List<Object> params = new ArrayList<>(list.size());
  4. for (ParameterMapping mapping : list) {
  5.     params.add(Ognl.getValue(Ognl.parseExpression(mapping.getProperty()), context, context.getRoot()));
  6. }

除了上面这种姿势之外,我们知道最终 mybatis 也是会实现 sql 参数解析的,如果有分析过源码的小伙伴,对下面这种姿势应该比较熟悉了

源码参考自: org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters

  1. BoundSql sql = statementHandler.getBoundSql();
  2. DefaultParameterHandler handler = (DefaultParameterHandler) statementHandler.getParameterHandler();
  3. Field field = handler.getClass().getDeclaredField("configuration");
  4. field.setAccessible(true);
  5. Configuration configuration = (Configuration) ReflectionUtils.getField(field, handler);
  6. // 这种姿势,与mybatis源码中参数解析姿势一直
  7. //
  8. MetaObject mo = configuration.newMetaObject(sql.getParameterObject());
  9. List<Object> args = new ArrayList<>();
  10. for (ParameterMapping key : sql.getParameterMappings()) {
  11.     args.add(mo.getValue(key.getProperty()));
  12. }

但是使用上面这种姿势,需要注意并不是所有的切点都可以生效;这个涉及到 mybatis 提供的四个切点的特性,这里也就不详细进行展开,在后面的源码篇,这些都是绕不过去的点

1.2 Intercepts 注解

接下来重点关注一下类上的@Intercepts注解,它表明这个类是一个 mybatis 的插件类,通过@Signature来指定切点

其中的 type, method, args 用来精确命中切点的具体方法

如根据上面的实例 case 进行说明

  1. @Intercepts(value = {@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
  2.         @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
  3. })

首先从切点为Executor,然后两个方法的执行会被拦截;这两个方法的方法名分别是queryupdate,参数类型也一并定义了,通过这些信息,可以精确匹配Executor接口上定义的类,如下

  1. // org.apache.ibatis.executor.Executor
  2. // 对应第一个@Signature
  3. <E> List<E> query(MappedStatement var1Object var2, RowBounds var3, ResultHandler var4) throws SQLException;
  4. // 对应第二个@Signature
  5. int update(MappedStatement var1Object var2) throws SQLException;

1.3 切点说明

mybatis 提供了四个切点,那么他们之间有什么区别,什么样的场景选择什么样的切点呢?

一般来讲,拦截ParameterHandler是最常见的,虽然上面的实例是拦截Executor,切点的选择,主要与它的功能强相关,想要更好的理解它,需要从 mybatis 的工作原理出发,这里将只做最基本的介绍,待后续源码进行详细分析

  • Executor:代表执行器,由它调度 StatementHandler、ParameterHandler、ResultSetHandler 等来执行对应的 SQL,其中 StatementHandler 是最重要的。

  • StatementHandler:作用是使用数据库的 Statement(PreparedStatement)执行操作,它是四大对象的核心,起到承上启下的作用,许多重要的插件都是通过拦截它来实现的。

  • ParameterHandler:是用来处理 SQL 参数的。

  • ResultSetHandler:是进行数据集(ResultSet)的封装返回处理的,它非常的复杂,好在不常用。

借用网上的一张 mybatis 执行过程来辅助说明

2. 插件注册

上面只是自定义插件,接下来就是需要让这个插件生效,也有下面几种不同的姿势

2.1 Spring Bean

将插件定义为一个普通的 Spring Bean 对象,则可以生效

2.2 SqlSessionFactory

直接通过SqlSessionFactory来注册插件也是一个非常通用的做法,正如之前注册 TypeHandler 一样,如下

  1. @Bean(name = "sqlSessionFactory")
  2. public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
  3.     SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
  4.     bean.setDataSource(dataSource);
  5.     bean.setMapperLocations(
  6.             // 设置mybatis的xml所在位置,这里使用mybatis注解方式,没有配置xml文件
  7.             new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/*.xml"));
  8.     // 注册typehandler,供全局使用
  9.     bean.setTypeHandlers(new Timestamp2LongHandler());
  10.     bean.setPlugins(new SqlStatInterceptor());
  11.     return bean.getObject();
  12. }
2.3 xml 配置

习惯用 mybatis 的 xml 配置的小伙伴,可能更喜欢使用下面这种方式,在mybatis-config.xml全局 xml 配置文件中进行定义

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <!DOCTYPE configuration
  3.         PUBLIC "-//ibatis.apache.org//DTD Config 3.1//EN"
  4.         "http://mybatis.org/dtd/mybatis-3-config.dtd">
  5. <configuration>
  6.     <settings>
  7.         <!-- 驼峰下划线格式支持 -->
  8.         <setting name="mapUnderscoreToCamelCase" value="true"/>
  9.     </settings>
  10.     <typeAliases>
  11.         <package name="com.git.hui.boot.mybatis.entity"/>
  12.     </typeAliases>
  13.     <!-- type handler 定义 -->
  14.     <typeHandlers>
  15.         <typeHandler handler="com.git.hui.boot.mybatis.handler.Timestamp2LongHandler"/>
  16.     </typeHandlers>
  17.     <!-- 插件定义 -->
  18.     <plugins>
  19.         <plugin interceptor="com.git.hui.boot.mybatis.interceptor.SqlStatInterceptor"/>
  20.         <plugin interceptor="com.git.hui.boot.mybatis.interceptor.ExecuteStatInterceptor"/>
  21.     </plugins>
  22. </configuration>

3. 小结

本文主要介绍 mybatis 的插件使用姿势,一个简单的实例演示了如果通过插件,来输出执行 sql,以及耗时

自定义插件实现,重点两步

  • 实现接口org.apache.ibatis.plugin.Interceptor

  • @Intercepts 注解修饰插件类,@Signature定义切点

插件注册三种姿势:

  • 注册为 Spring Bean

  • SqlSessionFactory 设置插件

  • myabtis.xml 文件配置

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/黑客灵魂/article/detail/892558
推荐阅读
相关标签
  

闽ICP备14008679号