【SpringBoot + Mybatis series 】 Plug-in mechanism Interceptor

In Mybatis, the plug-in mechanism provides a very powerful extension capability, providing four interception points to support the extension of different scenarios before the SQL is finally executed

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

This article will mainly introduce the use of custom Interceptor, and give a custom plug-in to output the execution of SQL, and the time of the case

I. Environment preparation

1. Prepare the database

Using mysql as the instance database for this article, add a new table

CREATE TABLE `money` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `name` varchar(20) NOT NULL DEFAULT ' ' COMMENT 'Username',
  `money` int(26) NOT NULL DEFAULT '0' COMMENT 'money',
  `is_deleted` tinyint(1) NOT NULL DEFAULT '0',
  `create_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 'Creation time',
  `update_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT 'Update Time'.PRIMARY KEY (`id`),
  KEY `name` (`name`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4;
Copy the code

2. Project environment

This article is developed with SpringBoot 2.2.1.RELEASE + Maven 3.5.3 + IDEA

Pom dependencies are as follows

<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>
Copy the code

Db configuration information 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:
Copy the code

II. Example demonstration

For details on myAbtis’s companion Entity/Mapper, check out the previous series of posts, which will focus on the implementation of Interceptor

1. Customize interceptor

Implement a custom plugin is relatively simple, try org. Apache. Ibatis. Plugin. The Interceptor interface

For example, define an interceptor, implement SQL output, execute time output

@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 is an object provided by Mybatis to access object properties
        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) {}}Copy the code

Note the above implementation, the core logic in intercept method, internal implementation of SQL acquisition, parameter parsing, time statistics

1.1 SQL Parameter Parsing Description

In the above case, for parameter parsing, Mybatis uses Ognl to achieve parameter replacement, so the above directly uses Ognl expression to obtain SQL parameters, of course, this implementation is rather rough

// The following paragraph of logic is mainly OGNL use posture
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()));
}
Copy the code

In addition to the above posture, we know that eventually Mybatis will also achieve SQL parameter parsing, if you have analyzed the source partners, to the following posture should be more familiar

Source reference since: 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);
// This posture, and mybatis source parameter parsing posture has been
//
MetaObject mo = configuration.newMetaObject(sql.getParameterObject());
List<Object> args = new ArrayList<>();
for (ParameterMapping key : sql.getParameterMappings()) {
    args.add(mo.getValue(key.getProperty()));
}
Copy the code

With this pose, however, it is important to note that not all cuts will work; This involves the features of the four pointcuts provided by Mybatis, which will not be expanded in detail here. In the following source code, these are points that cannot be circumvent

1.2 Intercepts annotations

Next, focus on the @Intercepts annotation on the class, which indicates that this class is a myBatis plug-in class and specifies the pointcut via @signature

Where type, method and ARgs are used to accurately hit the specific method of the pointcut

For example, refer to the above example

@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}), })
Copy the code

The cut point is Executor first, and then the execution of both methods is intercepted; The names of the two methods are Query, update, and parameter types are also defined. Using this information, you can accurately match the classes defined on the Executor interface as follows

// org.apache.ibatis.executor.Executor

// correspond to the first @signature
<E> List<E> query(MappedStatement var1, Object var2, RowBounds var3, ResultHandler var4) throws SQLException;

// Corresponds to the second @signature
int update(MappedStatement var1, Object var2) throws SQLException;
Copy the code

1.3 Description of pointcuts

Mybatis provides four pointcuts, so what is the difference between them, what kind of scene to choose what pointcuts?

In general, intercepting ParameterHandler is the most common ParameterHandler. Although the example above is intercepting Executor, the choice of pointcut is mainly related to its functionality. To better understand mybatis, we will only introduce the basic principles of mybatis

  • Executor: an Executor that schedules StatementHandler, ParameterHandler, ResultSetHandler, and so on to execute SQL. StatementHandler is the most important one.
  • StatementHandler: Performs operations using the Statement (PreparedStatement) of a database. It is the core of the four objects and serves as a link between the preceding and the following. Many important plug-ins are implemented by intercepting it.
  • ParameterHandler: Is used to process SQL parameters.
  • ResultSetHandler: Encapsulates and returns data sets (ResultSets). It is very complex and fortunately not commonly used.

Borrow a piece of Mybatis execution process online to assist the illustration

The original blog.csdn.net/weixin_3949…

2. Plug-in registration

The above is just a custom plug-in, the next step is to make the plug-in work, and there are several different postures

2.1 Spring Bean

This works by defining the plug-in as a plain Spring Bean object

2.2 SqlSessionFactory

It is also very common to register plug-ins directly through SqlSessionFactory, as with TypeHandler previously, as shown below

@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
    bean.setDataSource(dataSource);
    bean.setMapperLocations(
            // Set the XML location of Mybatis
            new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/*.xml"));
    // Register TypeHandler for global use
    bean.setTypeHandlers(new Timestamp2LongHandler());
    bean.setPlugins(new SqlStatInterceptor());
    return bean.getObject();
}
Copy the code

2.3 the XML configuration

If you are used to XML configuration of Mybatis, you may prefer to use the following method in the mybatis-config. XML global XML configuration file


      
<! DOCTYPEconfiguration
        PUBLIC "- / / ibatis.apache.org//DTD Config / 3.1 / EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <settings>
        <! -- Hump underline format support -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
    </settings>
    <typeAliases>
        <package name="com.git.hui.boot.mybatis.entity"/>
    </typeAliases>

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

    <! -- Plug-in definition -->
    <plugins>
        <plugin interceptor="com.git.hui.boot.mybatis.interceptor.SqlStatInterceptor"/>
        <plugin interceptor="com.git.hui.boot.mybatis.interceptor.ExecuteStatInterceptor"/>
    </plugins>
</configuration>
Copy the code

3. Summary

This article mainly introduces the use of mybatis plug-in posture, a simple example demonstrates if the plug-in, to output the execution of SQL, and time

Custom plug-in implementation, focus on two steps

  • Implementing an interfaceorg.apache.ibatis.plugin.Interceptor
  • @InterceptsThe annotation modifier plug-in class,@SignatureDefine the point of tangency

The plugin registers three postures:

  • Register as a Spring Bean
  • SqlSessionFactory setup plug-in
  • Myabtis.xml file configuration

III. Can’t miss the source code and related knowledge points

0. Project

  • Project: github.com/liuyueyi/sp…
  • Source: github.com/liuyueyi/sp…
  • Source: github.com/liuyueyi/sp…

Mybatis series blog post

  • TypeHandler SpringBoo series Mybatis custom TypeHandler
  • 【DB series 】SpringBoot series Mybatis Mapper interface and Sql binding several posture
  • 【DB series 】SpringBoot series Mybatis Mapper registration of several ways
  • 【DB series 】Mybatis-Plus multi-data source configuration
  • Mybatis based on DB series 】 【 AbstractRoutingDataSource multiple source switch with AOP implementation
  • 【DB series 】Mybatis multi-data source configuration and use
  • 【DB series 】 Multi-data source configuration and use of JdbcTemplate
  • 【DB series 】Mybatis-Plus code automatic generation
  • 【DB series 】MybatisPlus Integration
  • 【DB series 】Mybatis+ annotations integration
  • 【DB series 】Mybatis+ XML integration

1. Wechat official account: Yash Blog

As far as the letter is not as good, the above content is purely one’s opinion, due to the limited personal ability, it is inevitable that there are omissions and mistakes, if you find bugs or have better suggestions, welcome criticism and correction, don’t hesitate to appreciate

Below a gray personal blog, record all the study and work of the blog, welcome everyone to go to stroll

  • A grey Blog Personal Blog blog.hhui.top
  • A Grey Blog-Spring feature Blog Spring.hhui.top