MyBatis core configuration overview of Executor

MyBatis core configuration overview of Executor

Executor of the four components of MyBatis

Each SqlSession will have an Executor object, which is responsible for the specific operations of adding, deleting, modifying and checking. We can simply understand it as the encapsulation version of Statement in JDBC.

Executor inheritance structure

As shown in the figure, at the top of the inheritance system is the Executor, which has two implementation classes, namely BaseExecutorand CachingExecutor.

BaseExecutorIt is an abstract class. This way of implementing the interface through abstraction is 适配器设计模式之接口适配the embodiment. It is the default implementation of Executor. It implements most of the functions defined by the Executor interface and reduces the difficulty of interface implementation. There are three subclasses of BaseExecutor, namely SimpleExecutor, ReuseExecutorand BatchExecutor.

SimpleExecutor : simple executor, which is the default executor used in MyBatis. Every time an update or select is executed, a Statement object is opened, and the Statement object is closed directly when it is used up (it can be a Statement or a PreparedStatment object)

ReuseExecutor : Reusable executor. Reuse here refers to reusing Statement. It will use a Map internally to cache the created Statement. Every time a SQL command is executed, it will judge whether there is a Statement based on the SQL. Object, if there is a Statement object and the corresponding connection has not been closed, continue to use the previous Statement object and cache it . Because each SqlSession has a new Executor object, the Statement scope we cache on ReuseExecutor is the same SqlSession.

BatchExecutor : Batch executor, used to output multiple SQL to the database at one time

CachingExecutor: cache executor, first query the result from the cache, and return it if it exists; if it does not exist, then entrust it to the Executor delegate to retrieve it from the database. The delegate can be any of the above executors

Executor creation process and source code analysis

After we have analyzed the preparations for the creation process of SqlSessionFactory above, we will start to analyze the creation of sessions and the execution process of Executor.

After creating the SqlSessionFactory, call its openSessionmethod:

SqlSession sqlSession = factory.openSession();

The default implementation of SqlSessionFactory is DefaultSqlSessionFactory, so what we need to care about is the openSession() method in DefaultSqlSessionFactory

openSession calls openSessionFromDataSourcethe method, passing the type of executor, method propagation level, whether to submit automatically, and then creates an executor in the openSessionFromDataSource method

public SqlSession openSession() {
    
    
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    
    
    Transaction tx = null;
    try {
    
    
      // 得到configuration 中的environment
      final Environment environment = configuration.getEnvironment();
      // 得到configuration 中的事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 获取执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      // 返回默认的SqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
    
    
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
    
    
      ErrorContext.instance().reset();
    }
  }

Call the newExecutor method, determine which executor it is based on the type of ExecutorType passed in, and then execute the corresponding logic

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    
    
    // defaultExecutorType默认是简单执行器, 如果不传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);
    }
    // 如果允许缓存,则使用缓存执行器
  	// 默认是true,如果不允许缓存的话,需要手动设置
    if (cacheEnabled) {
    
    
      executor = new CachingExecutor(executor);
    }
  	// 插件开发。
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

Selection of ExecutorType:

ExecutorType determines what type of executor the Configuration object creates, and its assignment can be assigned in two places:

  • You can set all SqlSession objects in the current project to use the default Executor by label
<settings>
<!--取值范围 SIMPLE, REUSE, BATCH -->
	<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
  • Another way to assign values ​​​​to methods directly through Java
session = factory.openSession(ExecutorType.BATCH);

ExecutorType is an enumeration with only three values ​​SIMPLE, REUSE, BATCH

After the Executor is created, the Executor will be put into a DefaultSqlSession object to assign values ​​to the four properties, which are configuration, executor, dirty, respectively autoCommit.

The main method of the Executor interface

There are still many methods of the Executor interface. Here we will give a brief description of several main methods and calling processes.

general flow

The call chains of most methods in Executor are actually similar. The following is an in-depth source code analysis and execution process. If you don’t have time or don’t want to study in depth for the time being, here’s the execution flow chart for your reference.

query() method

The query method has two forms, one is direct query ; the other is query from the cache , let's take a look at the source code

<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;

<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;

When there is a query request for access, it will first go through the implementation class of Executor, CachingExecutor , and first query whether the SQL is executed for the first time from the cache. If it is the first execution, then execute the SQL statement directly and create a cache . If the same SQL statement is accessed for the second time, it will be extracted directly from the cache

CachingExecutor.j

	// 第一次查询,并创建缓存
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    
    
  BoundSql boundSql = ms.getBoundSql(parameterObject);
  CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
  return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

MapperStatementMaintain a package of <select|update|delete|insert> node , including resource (resource), configuration (configuration), SqlSource (sql source file), etc. Use the getMappedStatement method of Configuration to get the MappedStatement object

BoundSqlThis class includes the basic information of SQL, basic SQL statements, parameter mapping, parameter types, etc.

The above query method will call the query query cache method in the CachingExecutor class

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
  throws SQLException {
    
    
  // 得到缓存
  Cache cache = ms.getCache();
  if (cache != null) {
    
    
    // 如果需要的话刷新缓存
    flushCacheIfRequired(ms);
    if (ms.isUseCache() && resultHandler == null) {
    
    
      ensureNoOutParams(ms, boundSql);
      @SuppressWarnings("unchecked")
      List<E> list = (List<E>) tcm.getObject(cache, key);
      if (list == null) {
    
    
        list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
        tcm.putObject(cache, key, list); 
      }
      return list;
    }
  }
  // 委托模式,交给SimpleExecutor等实现类去实现方法。
  return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}

The query method is executed by the delegate, which is the BaseExecutor , and then the specific executor actually executes the query method

Note: Generally, the methods starting with do** in the source code are the methods that are actually loaded and executed

// 经过一系列的调用,会调用到下面的方法(与主流程无关,故省略)
// 以SimpleExecutor简单执行器为例
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,解析SQL语句
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
    stmt = prepareStatement(handler, ms.getStatementLog());
    // 由handler来对SQL语句执行解析工作
    return handler.<E>query(stmt, resultHandler);
  } finally {
    
    
    closeStatement(stmt);
  }
}

As can be seen from the source code above, the role of the Executor is equivalent to managing the entire life cycle of the StatementHandler, including creation, initialization, parsing, and closing.

The doQuery work done by ReuseExecutor is almost the same as the work done by SimpleExecutor. Internally, it uses a Map to store the query statement executed each time, preparing for subsequent SQL reuse.

doQuery work done by BatchExecutor : same as that done by SimpleExecutor.

update() method

After analyzing the above query method, let's talk about the update() method. The update() method not only refers to the **update()** method, it is an update chain. What does it mean? That is, *insert, update, and delete actually mean update in semantics, and query is only a query in semantics, so let’s take a peek at the execution process of the update method, which is very similar to the main execution process of select, so once sex posted.

// 首先在顶级接口中定义update 方法,交由子类或者抽象子类去实现

// 也是首先去缓存中查询是否具有已经执行过的相同的update语句
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    
    
  flushCacheIfRequired(ms);
  return delegate.update(ms, parameterObject);
}

// 然后再交由BaseExecutor 执行update 方法
public int update(MappedStatement ms, Object parameter) throws SQLException {
    
    
  ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
  if (closed) {
    
    
    throw new ExecutorException("Executor was closed.");
  }
  clearLocalCache();
  return doUpdate(ms, parameter);
}

// 往往do* 开头的都是真正执行解析的方法,所以doUpdate 应该就是真正要执行update链的解析方法了
// 交给具体的执行器去执行
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    
    
  Statement stmt = null;
  try {
    
    
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.update(stmt);
  } finally {
    
    
    closeStatement(stmt);
  }
}

The doUpdate work done by ReuseExecutor is almost the same as the work done by SimpleExecutor. Internally, it uses a Map to store the update statement executed each time, preparing for subsequent SQL reuse.

The doUpdate work done by BatchExecutor : It is similar to the work done by SimpleExecutor, except that there is a List list inside it to store multiple Statements at a time, which is used to transport multiple SQL statements to the database for execution at one time.

queryCursor() method

When we checked its source code, we did not find any difference between it and the query method during the execution of the executor, but in the doQueryCursor method, we can see that it returns a cursor object. Search the cursor related information online and find After consulting its basic structure, the conclusion drawn is: it is used to read SQL statements one by one to deal with the amount of data

// 查询可以返回Cursor<T>类型的数据,类似于JDBC里的ResultSet类,
// 当查询百万级的数据的时候,使用游标可以节省内存的消耗,不需要一次性取出所有数据,可以进行逐条处理或逐条取出部分批量处理。
public interface Cursor<T> extends Closeable, Iterable<T> {
    
    

    boolean isOpen();

    boolean isConsumed();
  
    int getCurrentIndex();
}

flushStatements() method

The main execution process of flushStatement() is similar to the execution process of query and update. We will not post the code in detail here. Briefly talk about the main function of flushStatement(). flushStatement() is mainly used to release the statement, or for ReuseExecutor and BatchExecutor to refresh the cache

createCacheKey() method

The createCacheKey() method is mainly executed by BaseExecutor and creates a cache. The cache in MyBatis is divided into a first-level cache and a second-level cache. We will discuss the cache in the cache chapter of the Mybatis series

Other methods in Executor

There are other methods in Executor, such as submitting commit, rolling back rollback, judging whether to cache isCached, closing close, obtaining transaction getTransaction, clearing local cache, clearLocalCache, etc.

Executor's realistic abstraction

In the above analysis process, we learned that Executor is a very important component in MyBatis. Executor is equivalent to the outsourced boss, which defines the work that Party A (SQL) needs to do (the main method of Executor). This outsourced The company is a small company with not many people. Everyone needs to do a lot of work. When the boss receives a development task, he usually looks for the project manager (CachingExecutor). The project manager hardly understands technology. It mainly deals with the technology leader (BaseExecutor). The technical leader is mainly responsible for the construction of the framework. The specific work will be handed over to the following programmers. The programmers' skills are also good or bad. Senior programmers (BatchExecutor), intermediate programmers (ReuseExecutor), and junior programmers (SimpleExecutor), What they do is also different. Generally, new project requirements are communicated to the project manager. The project manager first judges whether he has a ready-made class library or a project to apply directly (Cache). It is necessary to build a framework, then store the framework in the local class library, and then analyze it.

Guess you like

Origin blog.csdn.net/zy_dreamer/article/details/132642058