Source: blog.csdn.net/zwx900102/article/details/108941441

preface

When it comes to plug-ins, I believe we all know that the existence of plug-ins is mainly used to change or enhance the original function, MyBatis is the same. However, if we are not clear about the working principle of MyBatis, it is better not to use the plug-in easily. Otherwise, if the underlying working logic is changed due to the use of the plug-in, there may be many unexpected problems.

This article will mainly introduce the use of MyBatis plug-in and its implementation principle, believe that after reading this article, we can also write their own PageHelper page plug-in.

How is MyBatis plugin implemented

MyBatis plug-in is implemented by interceptor, so since it is implemented by interceptor, there is a question, which objects are allowed to be intercepted?

Recall from our Executor article that Sql is actually executed by four objects: Executor, StatementHandler, ParameterHandler, ResultSetHandler. MyBatis plug-in is based on intercepting these four objects to achieve.

It is important to note that although we can intercept these four objects, not all methods in these four objects can be blocked. The following is a summary of the objects and methods that can be blocked:

Use of MyBatis plugin

Let’s start with an example of how to use plug-ins.

First, create an Interceptor interface for MyPlugin, and then rewrite the three methods. (Note that the interface must be Interceptor, otherwise it cannot be intercepted.)

package com.lonelyWolf.mybatis.plugin; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import java.util.Properties; @Intercepts({@Signature(type = Executor.class,method = "query",args = {MappedStatement.class,Object.class, RowBounds.class, ResultHandler. Class})}) public class MyPlugin implements Interceptor {/** * this method will override the @Param Invocation * @return method  * @throws Throwable */ @Override public Object intercept(Invocation invocation) throws Throwable { System.out.println(" Successfully intercepted Executor's Query method, I can do something here "); return invocation.proceed(); } @override public Object plugin(Object target) {return plugin. wrap(target,this); Override public void setProperties(Properties Properties) {// Some Properties can be customized System.out.println(" custom attribute :userName->" + properties.getProperty("userName")); }}Copy the code

The @intercepts function declares that the current class is an interceptor, and the @signature function identifies the method Signature that needs to be intercepted, using the following three parameters

  • Type: the name of the intercepted class.
  • Method: specifies the name of the intercepted method
  • Args: Parameter type of annotation method

2. We also need to configure the plugin in mybatis-config.

< plugins > < plugin interceptor = "com. LonelyWolf. Mybatis. Plugin. MyPlugin" > < property name = "userName" value = "* *" / > < / plugin >  </plugins>Copy the code

If the property property is configured, we can get it in setProperties.

After completing the above two steps, we are ready to configure a plug-in. Let’s run it:

As you can see, the setProperties method is executed during the loading phase of the configuration file.

MyBatis plug-in implementation principle

Let’s take a look at how the plug-in is implemented, from loading to initialization to running.

Plug-in loading

Since a plug-in needs to be configured in a configuration file, it certainly needs to be parsed, and let’s see how the plug-in style is resolved. Let’s go to the XMLConfigBuilder class

Once resolved, the plug-in is stored in the List property of the InterceptorChain object

MyBatis plug-in is implemented through the responsibility chain mode.

How do plug-ins intercept

Now that the plug-in class has been loaded into the configuration file, the next question is, when will the plug-in class be intercepted for objects we need to intercept?

In fact, plug-in interception is related to the object, different object interception time will be inconsistent, next we will analyze one by one.

Intercepting Executor objects

We know that SqlSession objects are returned by the openSession() method, and executors are SqlSession internal objects, so let’s follow the openSession method to see how an Executor object is initialized.

As you can see, when the Executor is initialized, we call the pluginAll method of the interceptorChain. The pluginAll method itself is very simple. And call the Interceptor object’s plugin method:

Click in again:

If this sounds familiar, yes, this is the method we overwrote in the example above, and the plugin method is a default method in the interface.

This method is the key, let’s go in and see:

As you can see, the logic of this method is very simple, but it is important to note that MyBatis is implemented by JDK dynamic proxy, and the condition of JDK dynamic proxy is that the promended object must have an interface, which is different from Spring. In Spring, JDK dynamic proxies are used if there is an interface, and CGLIB dynamic proxies are used if there is no interface.

Because the MyBatis plugin only uses JDK dynamic proxies, it is important to implement the Interceptor interface.

After the invoke method of the Plugin, let’s finally look at the invoke method:

The intercept method that is ultimately executed is the one we overwrote in the example above.

Other object plug-in resolution

The StatementHandler is created in the Executor doQuery method. The StatementHandler works exactly the same way.

Once inside, the pluginAll method is executed:

The other two objects are no longer examples, in fact, the global search is obvious:

PS:

PluginAll is called when all four objects are initialized to determine whether they are proxied.

Plug-in execution process

Here is the execution sequence diagram after the plug-in is implemented:

Suppose an object is proxied many times

Can an object be proxied by multiple proxy objects? That is, can the same method of the same object be intercepted by multiple interceptors?

The answer is yes, because the proxied object is added to the list, so we configure the first interceptor to be proxied first, but execute the outermost interceptor first.

Specific points:

Suppose you define three plug-ins in turn: plug-in A, plug-in B, and plug-in C.

Plugins A, B, and C are stored in the same order as plugins A, B, and C. Plugins A, B, and C are parsed in the same order as plugins A, B, and C. Plug-in C, plug-in B, and plug-in A are executed in sequence.

Use of the PageHelper plugin

Now that we have seen how to define plug-ins in MyBatis and how to handle plug-ins in MyBatis, let’s take the classic pagination plugin PageHelper as an example to further understand.

First let’s look at the use of PageHelper:

package com.lonelyWolf.mybatis; import com.alibaba.fastjson.JSONObject; import com.github.pagehelper.Page; import com.github.pagehelper.PageHelper; import com.github.pagehelper.PageInfo; import com.lonelyWolf.mybatis.mapper.UserMapper; import com.lonelyWolf.mybatis.model.LwUser; import org.apache.ibatis.executor.result.DefaultResultHandler; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; import java.util.List; public class MyBatisByPageHelp { public static void main(String[] args) throws IOException { String resource = "mybatis-config.xml"; / / read mybatis config - configuration file InputStream InputStream = Resources. The getResourceAsStream (resource); SqlSessionFactory SqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); / / create the SqlSession objects SqlSession session. = sqlSessionFactory openSession (); PageHelper. StartPage (0, 10); UserMapper userMapper = session.getMapper(UserMapper.class); List<LwUser> userList = userMapper.listAllUser(); PageInfo<LwUser> pageList = new PageInfo<>(userList); System.out.println(null == pageList ? "": JSONObject.toJSONString(pageList)); }}Copy the code

The following output is displayed:

You can see that the object has been paginated, so how is this done?

PageHelper plugin principle

We mentioned above, in order to realize the plugin must implement MyBatis provides the Interceptor interface, so we went to look for and find PageHeler implements the Interceptor:

This class should be easy to read at a glance, but the key is to look at what SqlUtil’s Intercept method does:

This method has a lot of logic, and there are a lot of decisions to be made because different database dialects need to be taken into account. Let’s focus on where PageHelper overwrites the SQL statement. The red box in the figure above is where PageHelper overwrites the SQL statement:

This will fetch a Page object, and then set some Page parameters to the Page object when writing SQL. Let’s see where the Page object is fetched:

We saw that the object was retrieved from the LOCAL_PAGE object. What is this?

This is a local thread pool variable, so when is the Page stored in it?

Which brings us back to our example, where paging must start with a call:

PageHelper. StartPage (0, 10);Copy the code

Here a Page object is built and set into ThreadLocal.

Why is PageHelper only valid for the first SELECT statement after startPage

Intercept method: Intercept method: Intercept method: Intercept method: Intercept method:

Finally removes the paging data from ThreadLocal, so a single query removes the paging information and invalidates the following SELECT statement.

Can the core behavior of MyBatis be changed without plug-ins

Above we have introduced how to change the core behavior of MyBatis through plug-ins, so can it be realized without plug-ins?

The answer is yes, as mentioned in the official website, we can change the core behavior of MyBatis by overwriting the Configuration class, that is, we can write a class inherits the Configuration class, and then implement the methods in it. Finally, the SqlSessionFactory object is built with a custom Configuration method:

SqlSessionFactory build(MyConfiguration)
Copy the code

Of course, this method is not recommended, because it is the equivalent of building a house when the foundation is pulled out and rebuilt, a mistake, the house will collapse.

conclusion

This article will mainly introduce the use of MyBatis plug-in and the implementation principle of MyBatis. Finally, we also briefly introduce the main implementation principle of PageHelper plug-in. I believe that after reading this article and learning the principle of MyBatis plug-in, We can also write a simple PageHelper plugin of our own.