Transaction is a word that we come across many times, in Executor, in SqlSession, in the level 2 cache. MyBatis (MyBatis) uses transaction management to manage transactions. MyBatis (MyBatis) uses transaction management to manage transactions.

1. Getting to know things

A database transaction is defined by treating multiple execution units as a whole, and either all of them succeed or fail. Transaction with ACID properties, uncommitted transaction isolation level is read, read committed and repeatable read, serial, etc., these involves the knowledge of the database we do not do, the hope that the students have this aspect of the foundation.

MyBatis’ Transaction management interface is Transaction, which encapsulates a database connection and handles the connection lifecycle as follows

/** * wrap a database connection, handle the life cycle of a database connection, including create, prepare, commit, roll back, close *@author Clinton Begin
 */
public interface Transaction {

  /** * Get the internal database connection */
  Connection getConnection(a) throws SQLException;

  /** * submit */
  void commit(a) throws SQLException;

  /** * Roll back */
  void rollback(a) throws SQLException;

  /** * Close connection */
  void close(a) throws SQLException;

  /** * Get timeout */
  Integer getTimeout(a) throws SQLException;

}
Copy the code

Transaction has two implementations, JdbcTransaction and ManagedTransaction, which provide two management mechanisms to manage transactions

  • JdbcTransaction: Use java.sql.Connection in Jdbc to manage transactions, including commit rollback
  • ManagedTransaction: In this mechanism, MyBatis will not manage transactions, but the program run container (WebLogic, Tomcat) to manage.

2. Create a transaction

In myBatis -config. XML, you can configure the transaction management type, where the transactionManager type is set to JDBC, and transactions will be managed using the JdbcTransaction management mechanism.

 <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://xxxx:3306/xxxx? useUnicode=true"/>
                <property name="username" value="xxxx"/>
                <property name="password" value="xxxx"/>
            </dataSource>
        </environment>
    </environments>
Copy the code

The environmentsElement in XMLConfigBuilder parses the transactionManager type and creates a TransactionFactory of type TransactionFactory. This factory is used to create Transaction objects.

// XMLConfigBuilder
  private void environmentsElement(XNode context) throws Exception {
    if(context ! =null) {
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        if (isSpecifiedEnvironment(id)) {
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = newEnvironment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); configuration.setEnvironment(environmentBuilder.build()); }}}}Copy the code

The TransactionFactory is an object factory that creates a Transaction object. There are two ways to create it

  • Create a Transaction object from an existing connection
  • Automatically commit the creation of Transaction objects based on the data source and database isolation level
public interface TransactionFactory {

  /** * Set transaction factory private */
  default void setProperties(Properties props) {
    // NOP
  }

  /** * Create Transaction objects from existing connections */
  Transaction newTransaction(Connection conn);

  /** * Automatically commit the creation of Transaction objects based on the data source, database isolation level */
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);

}
Copy the code

TransactionFactory have two implementations, one is JdbcTransactionFactory, another is ManagedTransactionFactory, They create JdbcTransaction and ManagedTransaction objects, respectively. In the case of JdbcTransactionFactory, the implementation is also very simple, with little logic.

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 newJdbcTransaction(ds, level, autoCommit); }}Copy the code

Once the TransactionFactory is created above, it will be referenced by the Environment. When obtaining the SqlSession, a Transaction Transaction object is created based on the Transaction factory, and then a specific Executor is created based on this object. Finally, the SqlSession object is created.

// DefaultSqlSessionFactory
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      // Get the transaction factory
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // Create the transaction object
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // Create an executor
      final Executor executor = configuration.newExecutor(tx, execType);
      // Create SqlSession object
      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(); }}Copy the code

JdbcTransaction is basically a wrapper for JDBC Connection without any complicated logic

/ * * * {@linkTransaction} that makes use of the JDBC commit and rollback facilities directly. * It relies on the connection retrieved  from the dataSource to manage the scope of the transaction. * Delays connection retrieval until getConnection() is called. * Ignores commit or rollback requests when autocommit is on. * *@author Clinton Begin
 *
 * @seeJdbcTransactionFactory * * * Transaction fully utilizes JDBC's commit and rollback management mechanisms directly. It relies on the connection obtained from the data source to manage the life cycle of a transaction. * If auto commit is enabled, commit and rollback are ignored. * /
public class JdbcTransaction implements Transaction {

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

  protected Connection connection;
  protected DataSource dataSource;
  protected TransactionIsolationLevel level;
  protected boolean autoCommit;

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

  public JdbcTransaction(Connection connection) {
    this.connection = connection;
  }

  @Override
  public Connection getConnection(a) throws SQLException {
    if (connection == null) {
      openConnection();
    }
    return connection;
  }

  @Override
  public void commit(a) throws SQLException {
    if(connection ! =null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]"); } connection.commit(); }}@Override
  public void rollback(a) throws SQLException {
    if(connection ! =null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Rolling back JDBC Connection [" + connection + "]"); } connection.rollback(); }}@Override
  public void close(a) throws SQLException {
    if(connection ! =null) {
      resetAutoCommit();
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + connection + "]"); } connection.close(); }}protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
    try {
      if(connection.getAutoCommit() ! = desiredAutoCommit) {if (log.isDebugEnabled()) {
          log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]"); } connection.setAutoCommit(desiredAutoCommit); }}catch (SQLException e) {
      // Only a very poorly implemented driver would fail here,
      // and there's not much we can do about that.
      throw new TransactionException("Error configuring AutoCommit. "
          + "Your driver may not support getAutoCommit() or setAutoCommit(). "
          + "Requested setting: " + desiredAutoCommit + ". Cause: "+ e, e); }}protected void resetAutoCommit(a) {
    try {
      if(! connection.getAutoCommit()) {// MyBatis does not call commit/rollback on a connection if just selects were performed.
        // Some databases start transactions with select statements
        // and they mandate a commit/rollback before closing the connection.
        // A workaround is setting the autocommit to true before closing the connection.
        // Sybase throws an exception here.
        if (log.isDebugEnabled()) {
          log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
        }
        connection.setAutoCommit(true); }}catch (SQLException e) {
      if (log.isDebugEnabled()) {
        log.debug("Error resetting autocommit to true "
            + "before closing the connection. Cause: "+ e); }}}protected void openConnection(a) throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    connection = dataSource.getConnection();
    if(level ! =null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommit);
  }

  @Override
  public Integer getTimeout(a) throws SQLException {
    return null; }}Copy the code

3. Summary

MyBatis provides these two mechanisms to manage transactions

  • JdbcTransaction: Use java.sql.Connection in Jdbc to manage transactions, including committing rollback
  • ManagedTransaction: In this mechanism, MyBatis does not manage transactions. Instead, the application’s running container (WebLogic, Tomcat) does the management.