MyBatis source analysis of the SqlSession interface and Executor classes

  • 2020-06-03 06:26:38
  • OfStack

The mybatis framework cannot operate without the function of the SqlSession interface instance class. It can be said that the SqlSession interface instance is one of the classes that you deal with most during development. That's the DefaultSqlSession class. If I remember correctly, there was no getMapper method in the early days. There are corresponding methods for adding, deleting, modifying and checking entries. While much has been improved, much has been retained. We can still see methods like selectList. Source code can be found in the example. The following


SqlSession session = sqlMapper.openSession(TransactionIsolationLevel.SERIALIZABLE);
  try {
   List<Author> authors = session.selectList("org.apache.ibatis.domain.blog.mappers.AuthorMapper.selectAllAuthors");
   assertEquals(2, authors.size());
  } finally {
   session.close();

Of course, in a sense, it's a little annoying to write. But there's no denying that it does exist. This is why I prefer to use dynamic proxy for data manipulation. At least the author thinks it's better to be observant.

The SqlSession interface instance is indispensable for any operation on the data. So it's important to take a closer look at the qlSession interface instance. The author chooses the selectList method of DefaultSqlSession class as the entry point. The code is as follows.


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();
  }
 }

The source code is obtained from the configuration variation and the corresponding result is obtained through the query method of the Executor class. Let's recall from the previous chapter that the MappedStatement class is used to hold information about the select node or update node. That is, the statement parameter passed in here indicates which select node to execute. The parameter passed in is, of course, the value corresponding to the select node, id. So what is the Executor class. If serious people look at the source code, you will find that Executor class instances are almost always used. Sigh at one time -- Architectural Beauty.

executor, a member of the DefaultSqlSession class, is assigned a value in the constructor. So let's go back to see when we instantiated the DefaultSqlSession class. I also talked about the Executor class at the end of Chapter 1. From the source we can see that it is obtained through the newExecutor method of the Configuration class. The following code


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;
 }

Finished. I didn't realize that the Executor class has different types. To tell you the truth, I would not have known that there are different types of Executor classes if I hadn't looked at the source code. Looking at the source code above, the author initially thought there were four. However, it is found that the last one is a little different from the three examples above. I had to check API on the official website for further confirmation. The author copies the content over. As follows.

ExecutorType. SIMPLE: This actuator type does not do special things. It creates a new pre-processed statement for each statement execution.

ExecutorType.REUSE: This executor type multiplexes preprocessed statements.

ExecutorType. BATCH: This executor executes all update statements in batches and flags them as necessary if SELECT executes between them to ensure a simple and understandable behavior.

Looks like the website explains it pretty clearly. I am not here to say more. Which mybatis subclass does the Executor framework call by default? In fact, this 1 point can be found on the source code. The following

Configuration class:


protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;

I'm sure you see that the mybatis framework calls the SimpleExecutor class if none of the corresponding Executor class types are specified.

So for the Executor class I'm going to stop here. Because it will be used a lot later, it is only in combination with the following that the Executor class is understood. From the selectList method of the DefaultSqlSession class above we can see that the query method of the Executor class is called at the end.


return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);

I won't say much about the variable ms. The wrapCollection method is noteworthy. He means to first determine whether the variant parameter is type Collection or not. If so, create a new type StrictMap and store it with "collection" for key. Check 1 again if it is type List. If so, store "list" for key. If so, create a new StrictMap type and store "array" as key. Finally, the StrictMap class instance is returned. Of course, if none of the above is true, we just return parameter. If it is not clear why the designer wants to do so does not matter, I do not know why at this time. So I just have to remember what he did. RowBounds type variant rowBounds1 is commonly used for paging. The default value is 0 to 2147483647, and I believe that's enough for you. ResultHandler is used to process the returned results, so don't process the returned results here. So the author need not speak.

BaseExecutor class:


 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  BoundSql boundSql = ms.getBoundSql(parameter);
  CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
  return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
 }

A subclass like SimpleExecutor does not directly inherit from the Executor class. Instead, it passes through the BaseExecutor class layer and then inherits the Executor class. So when the query method is called, it's not calling the SimpleExecutor class. It calls the BaseExecutor class instead. Of course, you won't find the query method from the SimpleExecutor class. The Executor subclass's doQuery method is obviously called after the BaseExecutor class has processed the relevant information. So we can find the doQuery method of the SimpleExecutor class. This process author does not want to elaborate. The main purpose of the author is to guide you to see.

SimpleExecutor class:


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);
  }
 }

From the source code, I only know two classes: Configuration class and Statement class. Statement is the knowledge point belonging to JDBC. The Configuration class is the configuration information part of the mybatis framework. This means getting Configuration class information from MappedStatement. Create a new instance of the StatementHandler interface through the Configuration class. The StatementHandler interface instance is used to handle generating the Statement class. The SQL statement is obviously executed in the StatementHandler interface instance.

To summarize, the dynamic proxy approach actually ends up using an instance of the SqlSession interface. So the only way to understand an instance of the SqlSession interface is to go one step further. The SqlSession interface instance executes the methods of the Executor class. The role of the Configuration class can be said to be the task of the first use. For now, the last task is handed to a subclass of the Executor class.

Ok. At this point, there is a general image space for the SqlSession interface and the Executor class. Of course, only looking at the SqlSession interface and Executor class, I think there is a point difficult. At least I don't have the ability to do that. In my opinion, the SqlSession interface and the Executor class work is done when the doQuery method is used. The author did not say what the SqlSession interface and the Executor class do, though. It's mostly up to you to understand. The next step is to understand the function of the StatementHandler interface.


Related articles: