关于spring:SpringBoot-Mybatis系列之插件机制-Interceptor

4次阅读

共计 7514 个字符,预计需要花费 19 分钟才能阅读完成。

【SpringBoot + 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

<!– more –>

I. 环境筹备

1. 数据库筹备

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

CREATE TABLE `money` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL DEFAULT ''COMMENT' 用户名 ',
  `money` int(26) NOT NULL DEFAULT '0' COMMENT '钱',
  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
  `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创立工夫',
  `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新工夫',
  PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;

2. 我的项目环境

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

pom 依赖如下

<dependencies>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.0</version>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
</dependencies>

db 配置信息 application.yml

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

II. 实例演示

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

1. 自定义 interceptor

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

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

@Slf4j
@Component
@Intercepts(value = {@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
})
public class ExecuteStatInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // MetaObject 是 Mybatis 提供的一个用于拜访对象属性的对象
        MappedStatement statement = (MappedStatement) invocation.getArgs()[0];
        BoundSql sql = statement.getBoundSql(invocation.getArgs()[1]);

        long start = System.currentTimeMillis();
        List<ParameterMapping> list = sql.getParameterMappings();
        OgnlContext context = (OgnlContext) Ognl.createDefaultContext(sql.getParameterObject());
        List<Object> params = new ArrayList<>(list.size());
        for (ParameterMapping mapping : list) {params.add(Ognl.getValue(Ognl.parseExpression(mapping.getProperty()), context, context.getRoot()));
        }
        try {return invocation.proceed();
        } finally {System.out.println("------------> sql:" + sql.getSql() + "\n------------> args:" + params + "------------> cost:" + (System.currentTimeMillis() - start));
        }
    }

    @Override
    public Object plugin(Object o) {return Plugin.wrap(o, this);
    }

    @Override
    public void setProperties(Properties properties) {}}

留神下面的实现,外围逻辑在 intercept 办法,外部实现 sql 获取,参数解析,耗时统计

1.1 sql 参数解析阐明

下面 case 中,对于参数解析,mybatis 是借助 Ognl 来实现参数替换的,因而下面间接应用 ognl 表达式来获取 sql 参数,当然这种实现形式比拟粗犷

// 上面这一段逻辑,次要是 OGNL 的应用姿态
OgnlContext context = (OgnlContext) Ognl.createDefaultContext(sql.getParameterObject());
List<Object> params = new ArrayList<>(list.size());
for (ParameterMapping mapping : list) {params.add(Ognl.getValue(Ognl.parseExpression(mapping.getProperty()), context, context.getRoot()));
}

除了下面这种姿态之外,咱们晓得最终 mybatis 也是会实现 sql 参数解析的,如果有剖析过源码的小伙伴,对上面这种姿态应该比拟相熟了

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

BoundSql sql = statementHandler.getBoundSql();
DefaultParameterHandler handler = (DefaultParameterHandler) statementHandler.getParameterHandler();
Field field = handler.getClass().getDeclaredField("configuration");
field.setAccessible(true);
Configuration configuration = (Configuration) ReflectionUtils.getField(field, handler);
// 这种姿态,与 mybatis 源码中参数解析姿态始终
//
MetaObject mo = configuration.newMetaObject(sql.getParameterObject());
List<Object> args = new ArrayList<>();
for (ParameterMapping key : sql.getParameterMappings()) {args.add(mo.getValue(key.getProperty()));
}

然而应用下面这种姿态,须要留神并不是所有的切点都能够失效;这个波及到 mybatis 提供的四个切点的个性,这里也就不具体进行开展,在前面的源码篇,这些都是绕不过来的点

1.2 Intercepts 注解

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

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

如依据下面的实例 case 进行阐明

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

首先从切点为 Executor,而后两个办法的执行会被拦挡;这两个办法的办法名别离是query, update,参数类型也一并定义了,通过这些信息,能够准确匹配Executor 接口上定义的类,如下

// org.apache.ibatis.executor.Executor

// 对应第一个 @Signature
<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;

// 对应第二个 @Signature
int update(MappedStatement var1, Object var2) throws SQLException;

1.3 切点阐明

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

一般来讲,拦挡 ParameterHandler 是最常见的,尽管下面的实例是拦挡Executor,切点的抉择,次要与它的性能强相干,想要更好的了解它,须要从 mybatis 的工作原理登程,这里将只做最根本的介绍,待后续源码进行详细分析

  • Executor:代表执行器,由它调度 StatementHandler、ParameterHandler、ResultSetHandler 等来执行对应的 SQL,其中 StatementHandler 是最重要的。
  • StatementHandler:作用是应用数据库的 Statement(PreparedStatement)执行操作,它是四大对象的外围,起到承前启后的作用,许多重要的插件都是通过拦挡它来实现的。
  • ParameterHandler:是用来解决 SQL 参数的。
  • ResultSetHandler:是进行数据集(ResultSet)的封装返回解决的,它十分的简单,好在不罕用。

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

原文 https://blog.csdn.net/weixin_39494923/article/details/91534658

2. 插件注册

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

2.1 Spring Bean

将插件定义为一个一般的 Spring Bean 对象,则能够失效

2.2 SqlSessionFactory

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

@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
    bean.setDataSource(dataSource);
    bean.setMapperLocations(
            // 设置 mybatis 的 xml 所在位置,这里应用 mybatis 注解形式,没有配置 xml 文件
            new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/*.xml"));
    // 注册 typehandler,供全局应用
    bean.setTypeHandlers(new Timestamp2LongHandler());
    bean.setPlugins(new SqlStatInterceptor());
    return bean.getObject();}

2.3 xml 配置

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

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration
        PUBLIC "-//ibatis.apache.org//DTD Config 3.1//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <!-- 驼峰下划线格局反对 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <typeAliases>
        <package name="com.git.hui.boot.mybatis.entity"/>
    </typeAliases>

    <!-- type handler 定义 -->
    <typeHandlers>
        <typeHandler handler="com.git.hui.boot.mybatis.handler.Timestamp2LongHandler"/>
    </typeHandlers>

    <!-- 插件定义 -->
    <plugins>
        <plugin interceptor="com.git.hui.boot.mybatis.interceptor.SqlStatInterceptor"/>
        <plugin interceptor="com.git.hui.boot.mybatis.interceptor.ExecuteStatInterceptor"/>
    </plugins>
</configuration>

3. 小结

本文次要介绍 mybatis 的插件应用姿态,一个简略的实例演示了如果通过插件,来输入执行 sql,以及耗时

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

  • 实现接口org.apache.ibatis.plugin.Interceptor
  • @Intercepts 注解润饰插件类,@Signature定义切点

插件注册三种姿态:

  • 注册为 Spring Bean
  • SqlSessionFactory 设置插件
  • myabtis.xml 文件配置

III. 不能错过的源码和相干知识点

0. 我的项目

  • 工程:https://github.com/liuyueyi/spring-boot-demo
  • 源码:https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/104-mybatis-ano
  • 源码:https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/103-mybatis-xml

mybatis 系列博文

  • 【DB 系列】SpringBoo 系列 Mybatis 之自定义类型转换 TypeHandler
  • 【DB 系列】SpringBoot 系列 Mybatis 之 Mapper 接口与 Sql 绑定几种姿态
  • 【DB 系列】SpringBoot 系列 Mybatis 之 Mapper 注册的几种形式
  • 【DB 系列】Mybatis-Plus 多数据源配置
  • 【DB 系列】Mybatis 基于 AbstractRoutingDataSource 与 AOP 实现多数据源切换
  • 【DB 系列】Mybatis 多数据源配置与应用
  • 【DB 系列】JdbcTemplate 之多数据源配置与应用
  • 【DB 系列】Mybatis-Plus 代码主动生成
  • 【DB 系列】MybatisPlus 整合篇
  • 【DB 系列】Mybatis+ 注解整合篇
  • 【DB 系列】Mybatis+xml 整合篇

1. 微信公众号:一灰灰 Blog

尽信书则不如,以上内容,纯属一家之言,因集体能力无限,不免有疏漏和谬误之处,如发现 bug 或者有更好的倡议,欢送批评指正,不吝感谢

上面一灰灰的集体博客,记录所有学习和工作中的博文,欢送大家前去逛逛

  • 一灰灰 Blog 集体博客 https://blog.hhui.top
  • 一灰灰 Blog-Spring 专题博客 http://spring.hhui.top

正文完
 0