Obtaining SqlSession from MyBatis principle analysis

Front blog: Obtaining SqlSessionFactory for MyBatis principle analysis , this is a sequel.

The sqlsession is obtained mainly through several overloaded methods of SqlSessionFactory, and the Transaction is obtained by obtaining the datasource and transactionFactory from the environment in the configuration. Then get Transaction, Executor and DefaultSqlSession.

The environments node in the mybatis global configuration file is configured as follows

<environments default="development">
		<environment id="development">
			<transactionManager type="JDBC" />
			<dataSource type="POOLED">
				<property name="driver" value="com.mysql.jdbc.Driver" />
				<property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
				<property name="username" value="root" />
				<property name="password" value="123456" />
			</dataSource>
		</environment>
	</environments>

The sequence diagram is as follows:

[1]DefaultSqlSessionFactory

SqlSessionFactory has six methods to create SqlSession instances. Generally speaking, when you choose one of the methods, you need to consider the following points:

  • Transaction processing: do you want to use the transaction scope in the session scope or auto commit? (for many databases and / or JDBC drivers, it is equivalent to turning off transaction support)
  • Database connection: do you want MyBatis to help you get a connection from the configured data source, or use the connection provided by yourself?
  • Statement execution: do you want MyBatis to reuse PreparedStatement and / or batch update statements (including insert and delete statements)?

Based on the above requirements, the following overloaded openSession() methods are available.

SqlSession openSession()
SqlSession openSession(boolean autoCommit)
SqlSession openSession(Connection connection)
SqlSession openSession(TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType)
SqlSession openSession(ExecutorType execType, boolean autoCommit)
SqlSession openSession(ExecutorType execType,TransactionIsolationLevel level)
SqlSession openSession(ExecutorType execType, Connection connection)
Configuration getConfiguration();

The default openSession() method has no parameters. It creates a SqlSession with the following characteristics:

  • The transaction scope will be turned on (that is, it will not be committed automatically).
  • The Connection object will be obtained from the DataSource instance configured by the current environment.
  • The transaction isolation level will use the default settings of the driver or data source.
  • Preprocessing statements are not reused and updates are not processed in batches.

I believe you can already know the difference between these methods from the method signature. Pass a true value to the autoCommit optional parameter to enable the autoCommit function. To use your own connection instance, just pass a connection instance to the connection parameter. Note that we do not provide a method to set connection and autoCommit at the same time, because MyBatis will decide whether to enable autoCommit based on the incoming connection. For the transaction isolation level, MyBatis uses a Java enumeration wrapper called TransactionIsolationLevel. The transaction isolation level supports five JDBC isolation levels (NONE, READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ and SERIALIZABLE), and is consistent with the expected behavior.

You may be unfamiliar with the ExecutorType parameter. This enumeration type defines three values:

  • ExecutorType.SIMPLE: this type of actuator has no special behavior. It creates a new preprocessing statement for the execution of each statement.
  • ExecutorType.REUSE: this type of executor will reuse preprocessing statements.
  • ExecutorType.BATCH: this type of executor will execute all update statements in batch. If SELECT is executed among multiple updates, multiple update statements will be separated if necessary to facilitate understanding.

Tip: there is another method in SqlSessionFactory that we haven't mentioned, that is getConfiguration(). This method will return a Configuration instance, which you can use at runtime to check the Configuration of MyBatis.

Tip if you have used the old version of MyBatis, you may remember that session, transaction and batch operation are independent of each other. This is not the case in the new version. All three are included in the session scope. You don't have to deal with transactions separately or batch operations to get all the results you want.

For more details about SqlSessionFactory and SqlSession, please refer to the blog: Simple solutions of SqlSessionFactory and SqlSession in MyBatis

Let's analyze openSessionFromDataSource.

① Core method openSessionFromDataSource

 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
   Transaction tx = null;
   try {
     final Environment environment = configuration.getEnvironment();
     final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
     tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
     final Executor executor = configuration.newExecutor(tx, execType);
     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();
   }
 }

The code is explained as follows:

  • ① Obtain the environment instance from the configuration, mainly including the attributes datasource, id and transactionFactory
  • ② transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit) gets the Transaction instance object Transaction
  • ③ Executor executor = configuration.newExecutor(tx, execType) gets the executor instance object;
  • ④ Create DefaultSqlSession instance object, new DefaultSqlSession(configuration, executor, autoCommit)

② Core method getTransactionFactoryFromEnvironment

The default configuration of DataSource and TransactionFactory in TypeAliasRegistry of configuration instance is as follows:

jdbc=class org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory, 
managed=class org.apache.ibatis.transaction.managed.ManagedTransactionFactory, 

unpooled=class org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory, 
pooled=class org.apache.ibatis.datasource.pooled.PooledDataSourceFactory, 
jndi=class org.apache.ibatis.datasource.jndi.JndiDataSourceFactory,

As shown in the following method, if the environments node in the mybatis global configuration file is not configured with transactionManager, a ManagedTransactionFactory instance object will be created by default.

private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
	 if (environment == null || environment.getTransactionFactory() == null) {
	   return new ManagedTransactionFactory();
	 }
	 return environment.getTransactionFactory();
}

③ JdbcTransactionFactory and JdbcTransaction

public class JdbcTransactionFactory implements TransactionFactory {

  @Override
  public Transaction newTransaction(Connection conn) {
    return new JdbcTransaction(conn);
  }

  @Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
  }
}

Main properties and construction method of JdbcTransaction

public class JdbcTransaction implements Transaction {

  private static final Log log = LogFactory.getLog(JdbcTransaction.class);

  protected Connection connection;
  protected DataSource dataSource;
  //Lost property isolation level
  protected TransactionIsolationLevel level; 
  //Auto submit
  protected boolean autoCommit;

  public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
    dataSource = ds;
    level = desiredLevel;
    autoCommit = desiredAutoCommit;
  }
  //...
}  

[2]configuration.newExecutor(tx, execType)

This is a key step. The Executor corresponding to the current sqlsession will be created and packaged with interceptor chain (plug-in support provided by mybatis).

Four objects: Executor,StatementHandler,ParameterHandler,ResultSetHandler
When each of the four objects is created, interceptorchain. Is executed Pluginall() passes through the plugin() method of each plug-in object to create a proxy for the current four objects. The proxy object can intercept the execution of the methods related to the four objects, because the methods to execute the four objects need to pass through the proxy.

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

The code is explained as follows:

  • ① Obtain the actuator type. The default is SIMPLE (there are three types: SIMPLE, reuse and batch);
  • ② Create the corresponding Executor instance object according to the executorType;
  • ③ If L2 cache configuration is enabled, a cacheingexecution is created;

    Cacheingexecution has the member attribute Executor delegate, and the main work is left to delegate. Cacheingexecution caches the Executor instance object created in ②. Here is the application of delegation mode

  • ④ interceptorChain.pluginAll(executor) wraps the interceptor chain of the last Executor instance object

BatchExecutor|ReuseExecutor|SimpleExecutor|CachingExecutor

Main properties and construction method of simpleexecution

public class SimpleExecutor extends BaseExecutor {

  public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }
//...
}  

Main properties and construction method of BatchExecutor

public class BatchExecutor extends BaseExecutor {

  public static final int BATCH_UPDATE_RETURN_VALUE = Integer.MIN_VALUE + 1002;

  private final List<Statement> statementList = new ArrayList<>();
  private final List<BatchResult> batchResultList = new ArrayList<>();
  private String currentSql;
  private MappedStatement currentStatement;

  public BatchExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }
//...
}  

Main properties and construction method of reuseexecution

public class ReuseExecutor extends BaseExecutor {
  private final Map<String, Statement> statementMap = new HashMap<>();
  public ReuseExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }
  //...
}  

It can be found that the construction methods of the above several exciters call super(configuration, transaction);, Its parent class is the abstract class BaseExecutor:

public abstract class BaseExecutor implements Executor {

  private static final Log log = LogFactory.getLog(BaseExecutor.class);
  //affair
  protected Transaction transaction;
  //An actuator wrapper, such as cacheingexecution, may be a wrapper object of simpleexecution (or something else)
  protected Executor wrapper;

  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;
  //Local cache - L1 cache
  protected PerpetualCache localCache;
  //OutputParameter local cache of this type
  protected PerpetualCache localOutputParameterCache;
  //Global configuration instance object configuration
  protected Configuration configuration;

  protected int queryStack;
  private boolean closed;

  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    this.deferredLoads = new ConcurrentLinkedQueue<>();
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }
 //...
} 

Main properties and construction method of cachengexecution

public class CachingExecutor implements Executor {

  private final Executor delegate;
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();

  //Delegation mode
  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }
  //...
}  

Cacheingexecution is wrapped on the basis of other Executor instance objects, and the instance has its own transaction manager, TransactionalCacheManager. Cacheingexecution usually delegates the data behavior * * to its member instance Executor delegate.

interceptorChain.pluginAll(executor)

InterceptorChain interceptor chain object, the source code is as follows

public class InterceptorChain {

  private final List<Interceptor> interceptors = new ArrayList<>();

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

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }
}

The core method Object pluginAll(Object target) is to wrap the interceptor chain of the target, and finally return a proxy (wrapping) object. As shown in the following code, the executor is wrapped in the interceptor chain and then returned, which is also mybatis's support for plug-in development.

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

Not only the executor is instantiated with plug-in packaging, as shown in the figure below, ParameterHandler, ResultSetHandler and StatementHandler are all instantiated with plug-in packaging.

target = interceptor.plugin(target);

PaginationInterceptor

@Setter
@Accessors(chain = true)
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class PaginationInterceptor extends AbstractSqlParserHandler implements Interceptor {
//...
}
   public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

Reference blog: Analysis of MyBatis Plus plug-in mechanism and execution process principle

Keywords: Mybatis

Added by lala on Sat, 01 Jan 2022 19:28:46 +0200