Handwriting mybatis from scratch (four)-detailed explanation of mybatis transaction management mechanism

Handwriting mybatis from scratch (four)-detailed explanation of mybatis transaction management mechanism

Prospect review

Section scratch handwriting mybatis (a) MVP version we realized mybatis a basic can run.

Section scratch handwriting mybatis (two) mybatis interceptor plug-in mechanism Detailed

A third scratch handwriting MyBatis (three) jdbc pool implemented database connection pool from zero

In this section, let's learn about transaction management in mybatis.

image

Transaction management in mybatis

There are two ways to use mybatis transaction:

  1. Use the JDBC transaction management mechanism: that is, use the java.Sql.Connection object to complete the commit, rollback and close operations of the transaction.

  2. Use MANAGED's transaction management mechanism: Mybatis itself will not implement transaction management related operations, but will hand over an external container to manage the transaction. When integrated with spring, spring is generally used to manage transactions.

TransactionFactory

Interface definition

This is a factory for transactions, the interface is as follows:

public interface TransactionFactory {

  /**
   * Sets transaction factory custom properties.
   * @param props
   */
  void setProperties(Properties props);

  /**
   * Creates a {@link Transaction} out of an existing connection.
   * @param conn Existing database connection
   * @return Transaction
   * @since 3.1.0
   */
  Transaction newTransaction(Connection conn);
  
  /**
   * Creates a {@link Transaction} out of a datasource.
   * @param dataSource DataSource to take the connection from
   * @param level Desired isolation level
   * @param autoCommit Desired autocommit
   * @return Transaction
   * @since 3.1.0
   */
  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);

}
 

The main thing is how to create a Transaction based on a DataSource.

In fact, the overall feeling is of little significance.

The core thing is to look at the implementation of Transaction.

Transaction interface

public interface Transaction {

  /**
   * Retrieve inner database connection
   * @return DataBase connection
   * @throws SQLException
   */
  Connection getConnection() throws SQLException;

  /**
   * Commit inner database connection.
   * @throws SQLException
   */
  void commit() throws SQLException;

  /**
   * Rollback inner database connection.
   * @throws SQLException
   */
  void rollback() throws SQLException;

  /**
   * Close inner database connection.
   * @throws SQLException
   */
  void close() throws SQLException;

  /**
   * Get transaction timeout if set
   * @throws SQLException
   */
  Integer getTimeout() throws SQLException;
  
}
 

The core here is actually only commit() and rollback(), the others can be ignored.

For getTimeout() we can provide an operation timeout mechanism for mybatis.

JdbcTransaction implementation

Some processing based on jdbc mechanism.

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

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

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

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

  @Override
  public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
      connection.commit();
    }
  }

  @Override
  public void rollback() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Rolling back JDBC Connection [" + connection + "]");
      }
      connection.rollback();
    }
  }

  @Override
  public void close() 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() {
    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() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommmit);
  }

  @Override
  public Integer getTimeout() throws SQLException {
    return null;
  }
  
}
 

The overall implementation here is actually very simple, that is, to actively set the properties of automatic submission.

ManagedDataSource

This is another implementation, which is actually simpler.

Both commit() and rollback() implementations are empty.

public class ManagedTransaction implements Transaction {

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

  private DataSource dataSource;
  private TransactionIsolationLevel level;
  private Connection connection;
  private boolean closeConnection;

  public ManagedTransaction(Connection connection, boolean closeConnection) {
    this.connection = connection;
    this.closeConnection = closeConnection;
  }

  public ManagedTransaction(DataSource ds, TransactionIsolationLevel level, boolean closeConnection) {
    this.dataSource = ds;
    this.level = level;
    this.closeConnection = closeConnection;
  }

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

  @Override
  public void commit() throws SQLException {
    //Does nothing
  }

  @Override
  public void rollback() throws SQLException {
    //Does nothing
  }

  @Override
  public void close() throws SQLException {
    if (this.closeConnection && this.connection != null) {
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + this.connection + "]");
      }
      this.connection.close();
    }
  }

  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    this.connection = this.dataSource.getConnection();
    if (this.level != null) {
      this.connection.setTransactionIsolation(this.level.getLevel());
    }
  }

  @Override
  public Integer getTimeout() throws SQLException {
    return null;
  }

}
 

effect

ManagedTransaction gives the container to the container to manage the commit and rollback of the transaction, and it does not do any processing itself.

How to use mybatis

If Mybatis is run alone and there is no other framework management, then the next piece of code will be implemented internally by Mybatis.

con.setAutoCommit(false);
//, Connection 
//SQL SQL 
//Transaction . Transaction (commit,rollback) Connection .
try{
    //sql ..;
    con.commit();//Transaction .
}catch(SQLException ex){
    con.rollback();//Transaction .
}
 

On the whole, this kind of writing is relatively primitive, and we can uniformly adjust the transaction that was originally handed over to the connection to be processed by the transaction manager.

spring integration

Of course, for mybatis, most of them are the execution of a single statement.

When used to use the connection, what actually gets is the connection encapsulated by the mybatis transaction manager.

In fact, the integration of spring may be more applicable.

Personal realization

After reading the realization principle of mybatis, our realization becomes very simple.

We can simplify some of the above implementations and keep the core part.

Interface definition

We only keep the 3 interfaces of the core.

/**
 *  
 */
public interface Transaction {

    /**
     * Retrieve inner database connection
     * @return DataBase connection
     */
    Connection getConnection();

    /**
     * Commit inner database connection.
     */
    void commit();

    /**
     * Rollback inner database connection.
     */
    void rollback();

}
 

ManageTransaction

In this implementation, our commit and rollback do nothing.

/**
 *  
 *
 * @since 0.0.18
 */
public class ManageTransaction implements Transaction {

    /**
     *  
     * @since 0.0.18
     */
    private final DataSource dataSource;

    /**
     *  
     * @since 0.0.18
     */
    private final TransactionIsolationLevel isolationLevel;

    /**
     *  
     * @since 0.0.18
     */
    private Connection connection;

    public ManageTransaction(DataSource dataSource, TransactionIsolationLevel isolationLevel) {
        this.dataSource = dataSource;
        this.isolationLevel = isolationLevel;
    }

    public ManageTransaction(DataSource dataSource) {
        this(dataSource, TransactionIsolationLevel.READ_COMMITTED);
    }

    @Override
    public Connection getConnection() {
        try {
            if(this.connection == null) {
                Connection connection = dataSource.getConnection();
                connection.setTransactionIsolation(isolationLevel.getLevel());
                this.connection = connection;
            }

            return connection;
        } catch (SQLException throwables) {
            throw new MybatisException(throwables);
        }
    }

    @Override
    public void commit() {
        //nothing
    }

    @Override
    public void rollback() {
        //nothing
    }

}
 

JdbcTransaction.java

Compared with the above, there is more logic processing of commit and rollback.

package com.github.houbb.mybatis.transaction.impl;

import com.github.houbb.mybatis.constant.enums.TransactionIsolationLevel;
import com.github.houbb.mybatis.exception.MybatisException;
import com.github.houbb.mybatis.transaction.Transaction;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 *  
 *
 * @since 0.0.18
 */
public class JdbcTransaction implements Transaction {

    /**
     *  
     * @since 0.0.18
     */
    private final DataSource dataSource;

    /**
     *  
     * @since 0.0.18
     */
    private final TransactionIsolationLevel isolationLevel;

    /**
     *  
     * @since 0.0.18
     */
    private final boolean autoCommit;

    /**
     *  
     * @since 0.0.18
     */
    private Connection connection;

    public JdbcTransaction(DataSource dataSource, TransactionIsolationLevel isolationLevel, boolean autoCommit) {
        this.dataSource = dataSource;
        this.isolationLevel = isolationLevel;
        this.autoCommit = autoCommit;
    }

    public JdbcTransaction(DataSource dataSource) {
        this(dataSource, TransactionIsolationLevel.READ_COMMITTED, true);
    }

    @Override
    public Connection getConnection(){
        try {
            if(this.connection == null) {
                Connection connection = dataSource.getConnection();
                connection.setTransactionIsolation(isolationLevel.getLevel());
                connection.setAutoCommit(autoCommit);
                this.connection = connection;
            }

            return connection;
        } catch (SQLException throwables) {
            throw new MybatisException(throwables);
        }
    }

    @Override
    public void commit() {
        try {
            // commit  
            if(connection != null && !this.autoCommit) {
                connection.commit();
            }
        } catch (SQLException throwables) {
            throw new MybatisException(throwables);
        }
    }

    @Override
    public void rollback() {
        try {
            // commit  
            if(connection != null && !this.autoCommit) {
                connection.rollback();
            }
        } catch (SQLException throwables) {
            throw new MybatisException(throwables);
        }
    }

}
 
image