Interceptor, a plug-in mechanism of springboot + mybatis series

Time:2021-12-24

Interceptor, a plug-in mechanism of springboot + mybatis series

[springboot + mybatis series] plug-in mechanism interceptor

In mybatis, the plug-in mechanism provides a very powerful extension capability. Before the final execution of SQL, it provides four interception points to support function expansion in different scenarios

  • 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 usage posture of user-defined interceptor, and give a time-consuming case to output and execute SQL through user-defined plug-ins

<!– more –>

1. Environmental preparation

1. Database preparation

Use MySQL as the example database of this article and add a new table

CREATE TABLE `money` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `Name ` varchar (20) not null default '' comment 'user name',
  `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;

2. Project environment

With the help ofSpringBoot 2.2.1.RELEASE + maven 3.5.3 + IDEADevelop

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>

DB configuration informationapplication.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. Example demonstration

For the supporting entity / mapper related content of myabtis, it is recommended to check the previous series of blog posts. They will not be posted here. They will mainly focus on the implementation of interceptor

1. Customize the interceptor

Implementing a custom plug-in is still relatively simple. Try itorg.apache.ibatis.plugin.InterceptorInterface is enough

For example, define an interceptor to realize SQL output and execute time-consuming 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) {

    }
}

Note the above implementation. The core logic isinterceptMethod, internal implementation, SQL acquisition, parameter analysis, time-consuming statistics

1.1 SQL parameter parsing description

In the above case, for parameter parsing, mybatis implements parameter replacement with the help of ognl. Therefore, the above directly uses ognl expression to obtain SQL parameters. Of course, this implementation method is rough

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

In addition to the above posture, we know that eventually mybatis will also implement SQL parameter parsing. If there are small partners who have analyzed the source code, they should be familiar with the following posture

Source code reference from: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 pose is the same as the parameter analysis pose in the mybatis source code
//
MetaObject mo = configuration.newMetaObject(sql.getParameterObject());
List<Object> args = new ArrayList<>();
for (ParameterMapping key : sql.getParameterMappings()) {
    args.add(mo.getValue(key.getProperty()));
}

However, when using the above posture, it should be noted that not all tangent points can take effect; This involves the four tangent points provided by mybatis, which will not be expanded in detail here. In the later source code, these are points that cannot be bypassed

1.2 annotations to intercepts

Next, focus on the class@InterceptsAnnotation, which indicates that this class is a mybatis plug-in class@SignatureTo specify the tangent point

The type, method and args are used to accurately hit the tangent point

As described in the example case above

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

First, the tangent point isExecutorThen, the execution of the two methods will be intercepted; The method names of these two methods arequery, update, parameter types are also defined. Through this information, you can accurately matchExecutorThe classes defined on the interface are as follows

// org.apache.ibatis.executor.Executor

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

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

1.3 tangent point description

Mybatis provides four tangent points. What are the differences between them, what kind of scene and what kind of tangent point to choose?

Generally speaking, interceptionParameterHandlerIs the most common, although the above example is interceptionExecutor, the choice of pointcut is mainly related to its function. To better understand it, we need to start from the working principle of mybatis. Here we will only make the most basic introduction, which will be analyzed in detail in the follow-up source code

  • Executor: represents the executor, which schedules statementhandler, parameterhandler, resultsethandler, etc. to execute the corresponding SQL, of which statementhandler is the most important.
  • Statementhandler: it is used to perform operations using the statement (Preparedstatement) of the database. It is the core of the four objects and plays a connecting role. Many important plug-ins are implemented by intercepting it.
  • Parameterhandler: used to process SQL parameters.
  • Resultsethandler: it is used to encapsulate and return data sets. It is very complex, but it is not commonly used.

Borrow an online mybatis execution process to assist in the description

Interceptor, a plug-in mechanism of springboot + mybatis series

Original text https://blog.csdn.net/weixin_39494923/article/details/91534658

2. Plug in registration

The above is just a custom plug-in. The next step is to make the plug-in effective. There are also several different gestures below

2.1 Spring Bean

The plug-in can take effect if it is defined as an ordinary spring bean object

2.2 SqlSessionFactory

Directly throughSqlSessionFactoryIt is also a very common practice to register plug-ins, just like registering typehandler before, as follows

@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
    bean.setDataSource(dataSource);
    bean.setMapperLocations(
            //Set the location of mybatis XML. Here, the mybatis annotation method is used, and the XML file is not configured
            new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/*.xml"));
    //Register typehandler for global use
    bean.setTypeHandlers(new Timestamp2LongHandler());
    bean.setPlugins(new SqlStatInterceptor());
    return bean.getObject();
}

2.3 XML configuration

Small partners who are used to configuring with mybatis XML may prefer to use the following method inmybatis-config.xmlDefined in the global XML configuration file

<?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>
        <!--  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>

3. Summary

This article mainly introduces the use posture of mybatis plug-in. A simple example demonstrates that if the plug-in is used to output and execute SQL, and it takes time

Customized plug-in implementation, focusing on two steps

  • Implementation interfaceorg.apache.ibatis.plugin.Interceptor
  • @InterceptsAnnotations decorate plug-in classes,@SignatureDefine tangent point

The plug-in registers three poses:

  • Register as spring bean
  • Sqlsessionfactory settings plug-in
  • myabtis. XML file configuration

3. Can not miss the source code and related knowledge points

0. Project

  • Project: https://github.com/liuyueyi/spring-boot-demo
  • Source code: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/104-mybatis-ano
  • Source code: https://github.com/liuyueyi/spring-boot-demo/tree/master/spring-boot/103-mybatis-xml

Mybatis blog series

  • [DB series] springboo series mybatis custom type conversion typehandler
  • [DB series] the mapper interface of the spring boot series mybatis binds several postures to SQL
  • [DB series] several ways of mapper registration of mybatis in springboot series
  • [DB series] mybatis plus multi data source configuration
  • [DB series] mybatis realizes multi data source switching based on abstractroutingdatasource and AOP
  • [DB series] mybatis multi data source configuration and use
  • [DB series] multi data source configuration and use of jdbctemplate
  • [DB series] mybatis plus code is automatically generated
  • [DB series] mybatisplus integration
  • [DB series] mybatis + annotation integration
  • [DB series] mybatis + XML integration

1. WeChat official account: a gray Blog

The above contents are only the words of one family. Due to limited personal ability, it is inevitable that there are omissions and mistakes. If you find a bug or have better suggestions, you are welcome to criticize and correct and be grateful

The following is a gray personal blog, which records all blog posts in study and work. Welcome to visit

  • A personal blog https://blog.hhui.top
  • A gray blog spring special blog http://spring.hhui.top

Interceptor, a plug-in mechanism of springboot + mybatis series

Recommended Today

MySQL high availability architecture

1. Why do you need high availability? High availability ha (high availability) is to solve single machine failure. There is no service or machine in the world that is absolutely safe and reliable, but we can achieve high availability of the system through certain schemes, so as to reduce the unacceptable risk of destruction of […]