Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Detailed analysis of Mybatis plug-in mechanism

2025-01-19 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Database >

Share

Shulou(Shulou.com)05/31 Report--

This article introduces the knowledge of "detailed analysis of Mybatis plug-in mechanism". In the operation of actual cases, many people will encounter such a dilemma, so let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!

The Mybatis plug-in is also called the interceptor, and the interceptors that appear in this article all represent the plug-in.

Mybatis uses the chain of responsibility model, through dynamic agents to organize multiple plug-ins (interceptors), through these plug-ins can change the default behavior of Mybatis (such as SQL rewriting, etc.), because plug-ins will go deep into the core of Mybatis, it is best to understand its principles before writing their own plug-ins, in order to write safe and efficient plug-ins.

MyBatis allows you to intercept calls at some point during the execution of a mapped statement. By default, the method calls that MyBatis allows you to intercept with plug-ins include:

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)

ParameterHandler (getParameterObject, setParameters)

ResultSetHandler (handleResultSets, handleOutputParameters)

StatementHandler (prepare, parameterize, batch, update, query)

The overall summary is as follows:

The method of intercepting actuators

Processing of interception parameters

Intercept result set processing

Intercept the processing of Sql syntax construction

Mybatis is intercepted by dynamic proxy. To read this article, you need to understand the dynamic proxy mechanism of Java.

Four interfaces of Mybatis

Since Mybatis intercepts the four interfaces, we first need to know what the four interfaces of Mybatis are: Executor, StatementHandler, ResultSetHandler, and ParameterHandler.

The whole implementation process of the Mybatis framework is shown above. The Mybatis plug-in can intercept these four objects, which can be said to include all the operations performed by Mybatis SQL at a time. It can be seen that the plug-in of Mybatis is very powerful.

Hongmeng official Strategic Cooperation to build HarmonyOS Technology Community

Executor is the internal executor of Mybatis, which is responsible for calling StatementHandler to operate the database and automatically mapping the result set through ResultSetHandler. In addition, it also handles the operation of secondary cache. As can be seen here, we can also implement custom secondary caching through plug-ins.

StatementHandler is the object for Mybatis to execute sql scripts directly with the database. In addition, it also implements the first-level cache of Mybatis. Here, we can use plug-ins to implement operations on first-level caching (disable, etc.).

ParameterHandler is the object that Mybatis implements the input parameter setting of Sql. The plug-in can change the default settings of our Sql parameters.

ResultSetHandler is an interface object for Mybatis to map ResultSet collections to POJO. We can define plug-ins to modify the result set automatic mapping of Mybatis.

Plug-in Interceptor

For the plug-in implementation of Mybatis to implement the Interceptor interface, let's look at the methods defined by this interface.

Public interface Interceptor {Object intercept (Invocation invocation) throws Throwable; Object plugin (Object target); void setProperties (Properties properties);}

This interface declares only three methods:

The setProperties method is used to configure custom properties when Mybatis configures the plug-in, that is, the parameter configuration of the object implemented by the API.

The plugin method is used by the plug-in to encapsulate the target object, through which we can return either the target object itself or its proxy, and we can decide whether to intercept and then decide what kind of target object to return. The official example is return Plugin.wrap (target, this).

The intercept method is the method to be executed when you want to intercept.

To understand the definition of this interface, you must first understand the java dynamic proxy mechanism. The plugin interface is the proxy object that returns the parameter target object (Executor/ParameterHandler/ResultSetHander/StatementHandler). When calling the interface of the corresponding object, it can be intercepted and processed.

The method of creating four Interface objects in Mybatis

The plug-in of Mybatis is realized by generating dynamic proxy objects for objects with four interfaces. So now let's take a look at how Mybatis creates these four interface objects.

Public Executor newExecutor (Transaction transaction, ExecutorType executorType) {/ / make sure ExecutorType is not empty (defaultExecutorType may be empty) executorType = executorType = = null? DefaultExecutorType: executorType; executorType = executorType = = null? ExecutorType.SIMPLE: executorType; Executor executor; if (ExecutorType.BATCH = = executorType) {executor = new BatchExecutor (this, transaction);} else if (ExecutorType.REUSE = = executorType) {executor = new ReuseExecutor (this, transaction);} else {executor = new SimpleExecutor (this, transaction);} if (cacheEnabled) {executor = new CachingExecutor (executor);} executor = (Executor) interceptorChain.pluginAll (executor); return executor } public StatementHandler newStatementHandler (Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {StatementHandler statementHandler = new RoutingStatementHandler (executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); statementHandler = (StatementHandler) interceptorChain.pluginAll (statementHandler); return statementHandler;} public ParameterHandler newParameterHandler (MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {ParameterHandler parameterHandler = mappedStatement.getLang (). CreateParameterHandler (mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll (parameterHandler); return parameterHandler } public ResultSetHandler newResultSetHandler (Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) {ResultSetHandler resultSetHandler = new DefaultResultSetHandler (executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll (resultSetHandler); return resultSetHandler;}

Looking at the source code, you can see that the Mybatis framework calls the InterceptorChain.pluginAll () method after creating instances of these four interface objects. The InterceptorChain object is the plug-in execution chain object. If you look at the source code, you can see that all the plug-in (Interceptor) objects configured by Mybatis are maintained in it.

/ / target-- > Executor/ParameterHandler/ResultSetHander/StatementHandler public Object pluginAll (Object target) {for (Interceptor interceptor: interceptors) {target = interceptor.plugin (target);} return target;}

In fact, the plugin method of our plug-in is executed sequentially, and the proxy object that returns our original object (Executor/ParameterHandler/ResultSetHander/StatementHandler) layer by layer. When we call the methods of the four interfaces, we actually call the corresponding methods of the proxy object, and the proxy object calls the instances of the four interfaces.

Plugin object

As we know, the official recommended plug-in implementation plugin method is: Plugin.wrap (target, this)

Public static Object wrap (Object target, Interceptor interceptor) {/ / get the Intercepts annotations of the plug-in Map type = target.getClass (); Class [] interfaces = getAllInterfaces (type, signatureMap); if (interfaces.length > 0) {return Proxy.newProxyInstance (type.getClassLoader (), interfaces, new Plugin (target, interceptor, signatureMap);} return target;}

This method is actually a tool for Mybatis to simplify our plug-in implementation. In fact, a dynamic proxy object is created based on the currently intercepted object. The InvocationHandler processor of the proxy object is the newly created Plugin object.

Plug-in configuration annotations @ Intercepts

Plug-ins for Mybatis must have Intercepts annotations to specify which method of which object to intercept. We know that the Plugin.warp method returns the proxy object of the four interface objects (the IvocationHandler processor created by new Plugin ()), intercepting all execution methods. When the proxy object executes the corresponding method, the invoke method of the InvocationHandler processor is called. Annotated configuration is used in Mybatis to specify which methods to intercept. The details are as follows:

Public Object invoke (Object proxy, Method method, Object [] args) throws Throwable {try {Set methods = signatureMap.get (method.getDeclaringClass ()); if (methods! = null & & methods.contains (method)) {return interceptor.intercept (target, method, args);} return method.invoke (target, args);} catch (Exception e) {throw ExceptionUtil.unwrapThrowable (e);}}

As you can see, only the method specified by the Intercepts annotation executes the intercept method of our custom plug-in. Our intercept method will not be executed if not specified by the Intercepts comment.

Official plug-in development method

@ Intercepts ({@ Signature (type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) public class TestInterceptor implements Interceptor {public Object intercept (Invocation invocation) throws Throwable {Object target = invocation.getTarget (); / / proxied object Method method = invocation.getMethod (); / / proxy method Object [] args = invocation.getArgs (); / method parameter / / do something. Execute the code block Object result = invocation.proceed (); / / do something before method interception. Code block return result;} public Object plugin (Object target) {return Plugin.wrap (target, this);}} is executed after method interception

This is the method implemented by the plug-in officially recommended by Mybatis to create a dynamic proxy object of the proxied object through the Plugin object. As you can see, plug-in development for Mybatis is still very simple.

Custom development method

Plug-in development for Mybatis can be easily developed through internally provided Plugin objects. Only by understanding the principle of plug-in implementation, we can still implement plug-in development by ourselves without using Plugin objects. The following is a way for me to realize it by myself after I understand it.

Public class TestInterceptor implements Interceptor {public Object intercept (Invocation invocation) throws Throwable {Object target = invocation.getTarget (); / / proxied object Method method = invocation.getMethod (); / / proxy method Object [] args = invocation.getArgs (); / / method parameter / / do something. Execute the code block Object result = invocation.proceed (); / / do something before method interception. Execute code block return result;} public Object plugin (final Object target) {return Proxy.newProxyInstance (Interceptor.class.getClassLoader (), target.getClass (). GetInterfaces (), new InvocationHandler () {public Object invoke (Object proxy, Method method, Object [] args) throws Throwable {return intercept (new Invocation (target, method, args);}});} public void setProperties (Properties properties) {}} after method interception

Of course, Intercepts's annotations won't work at this time in the Mybatis plug-in.

Summary

We configured a plug-in in MyBatis and what happened when it was running

All processing classes that may be intercepted generate a proxy

When the processing class agent executes the corresponding method, it determines whether or not to execute the interception method in the plug-in.

After executing the interception method in insertion, advance the execution of the target.

If there are N plug-ins, there are N agents, each of which performs the above logic. The layers of agents have to generate dynamic agents many times, which affects the performance. Although the location of the plug-in intercept can be specified, this is determined dynamically when the method is executed, and the plug-in is simply packaged to all the places where it can be intercepted during initialization.

Therefore, there are several principles to pay attention to when writing plug-ins:

Do not write unnecessary plug-ins

When implementing the plugin method, determine the target type. It is the object that the plug-in wants to intercept before executing the Plugin.wrap method, otherwise it directly returns the target itself, which can reduce the number of times the target is proxied.

/ / if we only intercept Executor objects, then we should do this public Object plugin (final Object target) {if (target instanceof Executor) {return Plugin.wrap (target, this);} else {return target;}}

The Mybatis plug-in is powerful and can be greatly extended to the Mybatis framework. Of course, if you don't understand the principle of the Mybatis plug-in, it can only be developed as a simulation. In the actual development process, we can refer to plug-ins written by others. The following is a Mybatis paging plug-in, which can be used as a reference for future development.

/ * Mybatis-Universal paging plug-in (note if secondary cache is enabled) * / @ Intercepts ({@ Signature (type = StatementHandler.class, method = "prepare", args = {Connection.class}), @ Signature (type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class}) @ Log4j public class PageHelper implements Interceptor {public static final ThreadLocal localPage = new ThreadLocal () / * start paging * * @ param pageNum * @ param pageSize * / public static void startPage (int pageNum, int pageSize) {localPage.set (new Page (pageNum, pageSize)) } / * ends paging and returns the result. The method must be called, otherwise localPage will be saved until the next time startPage * * @ return * / public static Page endPage () {Page page = localPage.get (); localPage.remove (); return page } public Object intercept (Invocation invocation) throws Throwable {if (localPage.get () = = null) {return invocation.proceed ();} if (invocation.getTarget () instanceof StatementHandler) {StatementHandler statementHandler = (StatementHandler) invocation.getTarget (); MetaObject metaStatementHandler = SystemMetaObject.forObject (statementHandler) / / detach the proxy object chain (because the target class may be intercepted by multiple plug-ins, thus forming multiple proxies. The original target class can be separated through the following two cycles) while (metaStatementHandler.hasGetter ("h")) {Object object = metaStatementHandler.getValue ("h"); metaStatementHandler = SystemMetaObject.forObject (object) } / / detach the target class while (metaStatementHandler.hasGetter ("target")) of the last proxy object {Object object = metaStatementHandler.getValue ("target"); metaStatementHandler = SystemMetaObject.forObject (object);} MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue ("delegate.mappedStatement"); / / paging information if (localPage.get ()! = null) {Page page = localPage.get () BoundSql boundSql = (BoundSql) metaStatementHandler.getValue ("delegate.boundSql"); / / paging parameter as an attribute of parameter object parameterObject String sql = boundSql.getSql (); / / rewrite sql String pageSql = buildPageSql (sql, page); / / rewrite paging sql metaStatementHandler.setValue ("delegate.boundSql.sql", pageSql); Connection connection = (Connection) invocation.getArgs () [0] / / reset the total number of pages in the paging parameters, such as setPageParameter (sql, connection, mappedStatement, boundSql, page); / / transfer the execution power to the next plug-in return invocation.proceed ();} else if (invocation.getTarget () instanceof ResultSetHandler) {Object result = invocation.proceed (); Page page = localPage.get (); page.setResult ((List) result); return result;} return null } / * * only intercept these two types of *

StatementHandler *

ResultSetHandler * * @ param target * @ return * / public Object plugin (Object target) {if (target instanceof StatementHandler | | target instanceof ResultSetHandler) {return Plugin.wrap (target, this);} else {return target }} public void setProperties (Properties properties) {} / * modify the original SQL to paging SQL * * @ param sql * @ param page * @ return * / private String buildPageSql (String sql, Page page) {StringBuilder pageSql = new StringBuilder; pageSql.append ("select * from ("); pageSql.append (sql) PageSql.append (") temp limit") .append (page.getStartRow ()); pageSql.append (",") .append (page.getPageSize ()); return pageSql.toString () } / * get the total number of records * * @ param sql * @ param connection * @ param mappedStatement * @ param boundSql * @ param page * / private void setPageParameter (String sql, Connection connection, MappedStatement mappedStatement, BoundSql boundSql, Page page) {/ / Total records String countSql = "select count (0) from (" + sql + ") temp"; PreparedStatement countStmt = null ResultSet rs = null; try {countStmt = connection.prepareStatement (countSql); BoundSql countBS = new BoundSql (mappedStatement.getConfiguration (), countSql, boundSql.getParameterMappings (), boundSql.getParameterObject ()); setParameters (countStmt, mappedStatement, countBS, boundSql.getParameterObject ()); rs = countStmt.executeQuery (); int totalCount = 0; if (rs.next ()) {totalCount = rs.getInt (1) } page.setTotal (totalCount); int totalPage = totalCount / page.getPageSize () + ((totalCount% page.getPageSize () = = 0)? 0: 1); page.setPages (totalPage);} catch (SQLException e) {log.error ("Ignore this exception", e);} finally {try {rs.close () } catch (SQLException e) {log.error ("Ignore this exception", e);} try {countStmt.close ();} catch (SQLException e) {log.error ("Ignore this exception", e) Substitute parameter values * * @ param ps * @ param mappedStatement * @ param boundSql * @ throws SQLException * / private void setParameters (PreparedStatement ps, MappedStatement mappedStatement, BoundSql boundSql, Object parameterObject) throws SQLException {ParameterHandler parameterHandler = new DefaultParameterHandler (mappedStatement, parameterObject, boundSql); parameterHandler.setParameters (ps) } @ Data / / compile public static class Page {private int pageNum; private int pageSize; private int startRow; private int endRow; private long total; private int pages; private List result; public Page (int pageNum, int pageSize) {this.pageNum = pageNum; this.pageSize = pageSize; this.startRow = pageNum > 0 with the lombok plug-in. (pageNum-1) * pageSize: 0; this.endRow = pageNum * pageSize;} "detailed analysis of Mybatis plug-in mechanism" ends here. Thank you for reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Database

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report