Actuator of four objects under SqlSession in Mybatis (executor)

  • 2021-07-18 07:54:19
  • OfStack

First of all, I will explain that the title 4 objects under 1 refer to: executor, statementHandler, parameterHandler and resultHandler objects. (For the convenience of the following article, the four major objects refer to them.)

They are all the underlying class implementations of sqlSession, and they are also the four major objects that plug-ins can intercept. So the bottom of MyBATIS has been touched here, dynamic proxy, reflection can be seen at any time, without the first article as the basis, you will be 10 points difficult to understand it. Understanding their collaboration is one of the foundations of plug-in writing, so it is important to score 10 points.

Application of Executor in sqlSession

In the previous section, we talked about a problem. An mapper is executed through dynamic proxy, and then goes into the method of sqlSession. This is not difficult to understand, but how does sqlSession work internally? Answer 4: Collaboration of major objects. In SqlSession, it is still an interface, and mybatis provides services for us through the implementation class DefaultSqlSession. It is relatively long, but we don't need to see it all. We only see the commonly used selectList methods.


package org.apache.ibatis.session.defaults;
public class DefaultSqlSession implements SqlSession {
 private Configuration configuration;
 private Executor executor;
 private boolean autoCommit;
 private boolean dirty;
.......
@Override
 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
  try {
   MappedStatement ms = configuration.getMappedStatement(statement);
   return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
  } catch (Exception e) {
   throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
  } finally {
   ErrorContext.instance().reset();
  }
 }
......
}

We can see that it executes the method through executor to complete the query.

Initial recognition of Executor

So we are very interested in executor, so let's see what executor is like. First of all, there are three executor in MyBATIS:

SimpleExecutor--SIMPLE is an ordinary actuator.

ReuseExecutor-The executor reuses the preprocessing statement (prepared statements)

BatchExecutor--It is a batch executor

These are the three actuators of mybatis. You can configure it through the defaultExecutorType element in the settings of the configuration file. The default is SimpleExecutor. If you use it in Spring, you can configure it as follows:


<bean id="sqlSessionTemplateBatch" class="org.mybatis.spring.SqlSessionTemplate">   
<constructor-arg index="0" ref="sqlSessionFactory" /> 
<!-- Update with batch executor --> 
<constructor-arg index="1" value="BATCH"/> 
</bean> 

In this way, it is a batch actuator. All three executor of mybatis have a common parent class, BaseExecutor.

Executor initialization

First of all, let's understand how mybatis generates executor under 1. We see where Executor was generated (org. apache. ibatis. session. Configuration):


public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  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;
 }

Most of this is easy to understand, but there is one place that is easy to understand, and it is:

executor = (Executor) interceptorChain.pluginAll(executor);

This is a very important piece of code, which uses the responsibility chain pattern to generate proxy objects. We need to understand it more deeply and open its specific pluginAll method:


public Object pluginAll(Object target) {
  for (Interceptor interceptor : interceptors) {
   target = interceptor.plugin(target);
  }
  return target;
 }

Let's introduce this code here:

Interceptor This is the interface that the mybatis interceptor must implement, in other words, this traversal is to traverse the interceptor of mybatis.

Then call the plugin method, which is used to generate the proxy object (placeholder).

Therefore, we can imagine that the proxy objects of our plug-ins will be nested in layers, so any plug-ins (Interceptor) have the opportunity to intercept this real service object (executor), which is the responsibility chain mode. We can completely provide plug-ins (Interceptor) and enter the invoke method of proxy objects to change the behavior and method of executor.

But I want to stress here that when you use a plug-in, you will change the executor content implementation of mybatis, and you must use it carefully.

If we were to write our own dynamic proxy, it would be a lot of work, but mybatis provides us with the Plugin. java class to do what we need.


public class Plugin implements InvocationHandler {
 private Object target;
 private Interceptor interceptor;
 private Map<Class<?>, Set<Method>> signatureMap;
 private Plugin(Object target, Interceptor interceptor, Map<Class<?>, Set<Method>> signatureMap) {
  this.target = target;
  this.interceptor = interceptor;
  this.signatureMap = signatureMap;
 }
 public static Object wrap(Object target, Interceptor interceptor) {
  Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
  Class<?> 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;
 }
 @Override
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
   Set<Method> methods = signatureMap.get(method.getDeclaringClass());
   if (methods != null && methods.contains(method)) {
    return interceptor.intercept(new Invocation(target, method, args));
   }
   return method.invoke(target, args);
  } catch (Exception e) {
   throw ExceptionUtil.unwrapThrowable(e);
  }
 }
 ......
}

Here is an wrap method: it generates a proxy object for us. 1 Once our plug-in is bound to it, then we can expect to enter the invoke method.

invoke method: Very simple, it runs first through the filtering of class and method to see if this method needs to be intercepted, and if so, it runs intercept method of interceptor. So when we configure the signature, we can block our method.

Let's talk about that first. We know that we will mention it later when we talk about plug-ins. Here we know that it will generate 1 layer of proxy objects according to the number of plug-ins.

Implementation of executor

The execution of executor depends on the Statement object. Let's take the doQuery method of SimpleExecutor as an example:


public class SimpleExecutor extends BaseExecutor {
......
 @Override
 public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
  Statement stmt = null;
  try {
   Configuration configuration = ms.getConfiguration();
   StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
   stmt = prepareStatement(handler, ms.getStatementLog());
   return handler.<E>query(stmt, resultHandler);
  } finally {
   closeStatement(stmt);
  }
 }
 ......
 private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
  Statement stmt;
  Connection connection = getConnection(statementLog);
  stmt = handler.prepare(connection);
  handler.parameterize(stmt);
  return stmt;
 }
}

Obviously, this is a query method scheduled here

First of all, it becomes an StatementHandler object.

The prepare method is called through the prepareStatement method to initialize the parameters.

Then use the parameterize method to set the parameters to the runtime environment.

Then it goes through handler. < E > query (stmt, resultHandler); Method to complete the result assembly.

So our focus is on the StatementHandler object, which we will talk about in the next chapter.

Summarize


Related articles: