Take you to know Mybatis interceptor and handwriting paging plug-in

Keywords: Mybatis SQL JDK xml

The principle of Mybatis interceptor is a bit convoluted and simple. The principle is to implement agents for our customized interceptor class through the dynamic agent technology of JDK, and there can be multiple agents. Therefore, Mybatis interceptor will exist in the form of a chain, and each agent will be processed in one. The paging principle is to get the old SQL in the interceptor, and then splice the limit statement to let Mybatis continue processing.

For example, you are going to a factory, but there is a big man at the door who stops you, so you have to put on a foot cover before you can enter. So you put on a foot cover and wrap yourself up. You didn't walk a few steps, and a big man asked you to take a head cover, so you put on a head cover again. Here you are a sql statement, and a big man is an interceptor, constantly decorating yourself through the interceptor (for example, adding limit Finally, do your work in a gorgeous way.

Analyze the source code first and then do the work.

Source code analysis

1, Load interceptor class

First of all, Mybatis needs to know that we have configured those interceptors. Under the pluginElement method under the XMLConfigBuilder class, it reads the configuration file, which is called interceptor, in fact, plug-in.

 private void pluginElement(XNode parent) throws Exception {
     if (parent != null) {
         Iterator var2 = parent.getChildren().iterator();
         while(var2.hasNext()) {
             XNode child = (XNode)var2.next();
             String interceptor = child.getStringAttribute("interceptor");
             Properties properties = child.getChildrenAsProperties();
             Interceptor interceptorInstance = (Interceptor)this.resolveClass(interceptor).newInstance();
             interceptorInstance.setProperties(properties);
             this.configuration.addInterceptor(interceptorInstance);
         }
     }
 }

Mybatis has its own dtd. plugins nodes need to be configured in order

After loading and instantiation, it is finally put into the List of InterceptorChain class through a series of calls. In other words, to implement an Interceptor, we need to inherit the Interceptor interface.

2, Set up proxy

This is the most important step. It is completed under the newExecutor method of Configuration and the pluginAll method of InterceptorChain,
When the pluginAll method here is called, the target object is the implementation class of other executors. If the cache switch is not turned off, it should be an instance of the cachexector class. After that, every time the interceptor.plugin is called, a proxy object is generated for the target, that is, a dynamic proxy object is returned from the plugin method under the custom interceptor. Generally, the Plugin.wrap() method is used to return. If there are multiple interceptors, they may end up in the form of proxy objects (proxy objects (proxied executors)), just like grandpa asked Dad to buy snacks, dad asked his son to buy snacks, and his son asked other runners to buy snacks, layer by layer. This place may be a little winding.

Just like after two interceptors are configured, his structure is like this, and the innermost layer is the real son.

3, Handling custom logic

After inheriting the Interceptor, first specify the object to be intercepted (here, the object can be Executor, StatementHandler, pagerhandler and ResultSetHandler), and tell Mybatis by annotation.

As the following example, tell Mybatis that I want to intercept the query method under the Executor class and overload the methods whose parameters are {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class

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

After that, when Mybatis calls its query, it first transfers to the intercept method of Interceptor for processing.

The processing of custom logic is under the intercept method, and the parameter of intercept is very important, so we need to know it first. The parameter type is Invocation, which is transferred when dynamic agent invoke s. But we need to look at the wrap method first. warp himself has a new look. target is the object being proxied, that is, the implementation class of Executor. When there are multiple agents, he may be the proxy Manage the proxy object of Executor implementation class, or more layers, and interceptor is our interceptor.

When the interceptor's intercept method is called under invoke, a new Invocation object is passed in. In args comparison, it is the parameter when the method of the agent object is called. For example, the query method of Executor, args [] is the parameter list collection of this method, which will be of great use in the future.

Four, paging

First, define a PageInfo, where toString passes (page size × page number) - page number formula and other limit statements.
If you take 10 items from 2 pages and get (10 * 2) - 10 = "limit 10,10"

public class PageInfo {
    private int page;
    private int pageCount;
    public PageInfo(int page, int pageCount) {
        this.page = page;
        this.pageCount = pageCount;
    }
    @Override
    public String toString() {
        return" limit "+( (pageCount*page)-pageCount)+","+pageCount;
    }
}

The idea of paging is basically like this. First, get the query parameters of the DAO layer, including PageInfo, then get the old sql, splice limit, and set the new sql statement to Mybatis to let it continue to execute, so as to achieve interception.

public interface IUserDao {
     List<UserEntity> select(@Param("page")PageInfo pageInfo);
}

First, get the pageInfo parameter above and call invocation.getArgs()[1]. Here, the getArgs() array is the second parameter of the executor ා query method, which represents the parameter array. But it may not be encapsulated as MapperMethod.ParamMap, so the following should be judged.
Mybatis made a judgment in getNamedParams under ParamNameResolver. When there is no annotation on the parameter and the number of parameters is 1, the String type is returned directly. Otherwise, pack it. It's harmless here.

1. Get the request parameters
ParamMap also inherits HashMap. First, judge whether there is page paging. If there is, get the paging statement first

  String limitPage = "";
 Object argObject = invocation.getArgs()[1];
 if (argObject instanceof MapperMethod.ParamMap) {
     MapperMethod.ParamMap arg = (MapperMethod.ParamMap) invocation.getArgs()[1];
     if (arg.get("page") != null) {
         PageInfo pageInfo = (PageInfo) arg.get("page");
         limitPage = pageInfo.toString();
     }
 }

2. Get the old sql
MappedStatement object is the encapsulated information of insert, select, delete and update. It is also represented in executor query parameter 1. Then get the sql statement through getBoundSql to complete the splicing of old sql and paging data

MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
Class<?> type = mappedStatement.getParameterMap().getType();
String oldSql = mappedStatement.getBoundSql(argObject).getSql();
String newSql = oldSql + limitPage;

3. Set a new sql for Mybatis
The following is to set a new sql statement to MappedStatement. The knowledge point here is available. The SqlSource object is used to save the sql statement in MappedStatement, but MappedStatement does not provide the method to modify it.

So there are three ways
1: Through reflection
2: Create a new MappedStatement and replace the original one.
3:SystemMetaObject
Among them, SystemMetaObject is a very powerful class provided by Mybatis. Two lines of code can modify internal properties.

If you know the way, it's easy

//Building new sql objects
SqlSource sqlSource = new RawSqlSource(mappedStatement.getConfiguration(), newSql, type);

MetaObject metaObject = SystemMetaObject.forObject(mappedStatement);
//Replace the original
metaObject.setValue("sqlSource", sqlSource);

At last, return invocation. Processed(); continue to let Mybatis process. If there are other interceptors, continue to call. When the interceptor is finished, the query is sql statement.

Pagination all codes are as follows

@Intercepts({
        @Signature(
                type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        )
})
public class MybatisIntercepts implements Interceptor {
    private Properties properties = new Properties();
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        String limitPage = "";
        Object argObject = invocation.getArgs()[1];
        if (argObject instanceof MapperMethod.ParamMap) {
            MapperMethod.ParamMap arg = (MapperMethod.ParamMap) invocation.getArgs()[1];
            if (arg.get("page") != null) {
                PageInfo pageInfo = (PageInfo) arg.get("page");
                limitPage = pageInfo.toString();
            }
        }
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Class<?> type = mappedStatement.getParameterMap().getType();
        String oldSql = mappedStatement.getBoundSql(argObject).getSql();
        System.out.println(oldSql);
        String newSql = oldSql + limitPage;
        System.out.println("new Sql Sentence" + newSql);
        SqlSource sqlSource = new RawSqlSource(mappedStatement.getConfiguration(), newSql, type);
        MetaObject metaObject = SystemMetaObject.forObject(mappedStatement);
        metaObject.setValue("sqlSource", sqlSource);
//       Field field = mappedStatement.getClass().getDeclaredField("sqlSource");
//        field.setAccessible(true);
//        field.set(mappedStatement,sqlSource);
        Object o = invocation.proceed();
        return o;
    }
    @Override
    public void setProperties(Properties properties) {
    }
    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o, this);
    }
}

In fact, the setProperties method is used to set properties. The parameters are written in the configuration file, which is not used in this example

   <plugins>
        <plugin interceptor="com.hxl.intercepts.MybatisIntercepts">
            <property name="test" value="666"/>
        </plugin>
    </plugins>

test

 public static void main( String[] args )
{
    String resource = "mybatis-config.xml";
    try {
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession2 = build.openSession();
        List<UserEntity> select = sqlSession2.getMapper(IUserDao.class).select(new PageInfo(2, 10));
        System.out.println(select);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

end...

Published 14 original articles, won praise 7, visited 5772
Private letter follow

Posted by ctsiow on Wed, 29 Jan 2020 03:10:09 -0800