深入理解MyBatis——數據源和事務管理

在使用MyBatis是,我們需要配置數據源,常用的數據源有c3p0,dbcp,druid等。打開源碼會發現,他們都實現了javax.sql.DataSource接口。

實質上,MyBatis中數據源可以分爲三類,UNPOOLED、POOLED和JNDI類。使用UnpooledDataSource,PooledDataSource來分別表示前兩種數據源,兩者都實現了javax.sql.DataSource接口;而JNDI類的數據源則是則是通過JNDI上下文中取值。
UNPOOLED和POOLED分別表示不使用連接池與使用連接池。

在配置文件中:

<datasource type="POOLED">

我們可以修改type屬性的值,mybatis根據你設置的屬性產生不同類型的datasource。

獲得datasource是通過datasourceFactory來獲得的,不同的datasourceFactory都實現了DataSourceFactory接口。

/**
 * @author Clinton Begin
 */
public interface DataSourceFactory {

  void setProperties(Properties props);

  DataSource getDataSource();

}

值的注意的是PooledDataSourceFactory繼承了UnpooledDataSourceFactory。

/**
 * @author Clinton Begin
 */
public class PooledDataSourceFactory extends UnpooledDataSourceFactory {

  public PooledDataSourceFactory() {
    this.dataSource = new PooledDataSource();
  }

}

創建時機

MyBatis數據源DataSource對象的創建發生在MyBatis初始化的過程中,關於初始化可以看一看前一篇文章。

DataSource創建Connection對象是在需要執行一個SQL的時候,通過dataSource.getConnection()獲得一個Connection。

//此時創建Connection對象
sqlSession.selectList("SELECT * FROM table_name");  

UnpooledDataSource獲得Connection對象

public Connection getConnection() throws SQLException {
   //調用doGetConnection方法
    return doGetConnection(username, password);
  }


 private Connection doGetConnection(String username, String password) throws SQLException {
    Properties props = new Properties();
    //封裝username和password成Properties對象 
    if (driverProperties != null) {
      props.putAll(driverProperties);
    }
    if (username != null) {
      props.setProperty("user", username);
    }
    if (password != null) {
      props.setProperty("password", password);
    }
    return doGetConnection(props);
  }

  private Connection doGetConnection(Properties properties) throws SQLException {
  //初始化Driver
    initializeDriver();
    //根據properties創建Connection
    Connection connection = DriverManager.getConnection(url, properties);
    //對Connection進行配置
    configureConnection(connection);
    return connection;
  }

UnpooledDataSource獲得Connection做了如下的事情:

1.初始化Driver:通過調用initializeDriver()
2.創建Connection:由Driver來創建
3.配置Connection:調用configureConnection()方法實現,設置是否自動提交和隔離級別。

PooledDataSource獲得Connection

PooledDataSource類中有一個PoolState對象, 它是裝載連接池的容器。

這裏寫圖片描述

PoolState中有兩個List:idleConnections和activeConnections,分爲表示連接的兩種狀態: 空閒狀態(idle)和活動狀態(active)。

//空閒的
protected final List<PooledConnection> idleConnections = new ArrayList<PooledConnection>();
//活動的
  protected final List<PooledConnection> activeConnections = new ArrayList<PooledConnection>();

知道這些我們回過頭來看PooledDataSource是如何獲得Connection的。

public Connection getConnection() throws SQLException {
    return popConnection(dataSource.getUsername(), dataSource.getPassword()).getProxyConnection();
  }

public Connection getConnection(String username, String password) throws SQLException {
//獲得Connection代理
    return popConnection(username, password).getProxyConnection();
  }

代碼跟進入popConnection()方法。

 private PooledConnection popConnection(String username, String password) throws SQLException {
    boolean countedWait = false;
    PooledConnection conn = null;
    long t = System.currentTimeMillis();
    int localBadConnectionCount = 0;

    while (conn == null) {
      synchronized (state) {
      //1.先去idleConnections查找是否有空閒的連接
        if (state.idleConnections.size() > 0) {
          // Pool has available connection
          conn = state.idleConnections.remove(0);
          if (log.isDebugEnabled()) {
            log.debug("Checked out connection " + conn.getRealHashCode() + " from pool.");
          }
        } else {
        //如果idleConnections沒有空閒的連接
          // Pool does not have available connection
          //查詢activeConnections中的連接是否滿了
          if (state.activeConnections.size() < poolMaximumActiveConnections) {
            // Can create new connection
            //如果沒滿就創建新的
            conn = new PooledConnection(dataSource.getConnection(), this);
            @SuppressWarnings("unused")
            //used in logging, if enabled
            Connection realConn = conn.getRealConnection();
            if (log.isDebugEnabled()) {
              log.debug("Created connection " + conn.getRealHashCode() + ".");
            }
          } else {
            // Cannot create new connection
            //如果activeConnections中連接滿了就取出活動連接池的第一個,也就是最早創建的

            PooledConnection oldestActiveConnection = state.activeConnections.get(0);
            long longestCheckoutTime = oldestActiveConnection.getCheckoutTime();
            //查詢最早創建的是否過期,如果過期了就移除他並創建新的
            if (longestCheckoutTime > poolMaximumCheckoutTime) {
              // Can claim overdue connection
              state.claimedOverdueConnectionCount++;
              state.accumulatedCheckoutTimeOfOverdueConnections += longestCheckoutTime;
              state.accumulatedCheckoutTime += longestCheckoutTime;
              state.activeConnections.remove(oldestActiveConnection);
              if (!oldestActiveConnection.getRealConnection().getAutoCommit()) {
                oldestActiveConnection.getRealConnection().rollback();
              }
              conn = new PooledConnection(oldestActiveConnection.getRealConnection(), this);
              oldestActiveConnection.invalidate();
              if (log.isDebugEnabled()) {
                log.debug("Claimed overdue connection " + conn.getRealHashCode() + ".");
              }
            } else {
            //還未過期,就必須等待,再次重複上述步驟
              // Must wait
              try {
                if (!countedWait) {
                  state.hadToWaitCount++;
                  countedWait = true;
                }
                if (log.isDebugEnabled()) {
                  log.debug("Waiting as long as " + poolTimeToWait + " milliseconds for connection.");
                }
                long wt = System.currentTimeMillis();
                state.wait(poolTimeToWait);
                state.accumulatedWaitTime += System.currentTimeMillis() - wt;
              } catch (InterruptedException e) {
                break;
              }
            }
          }
        }
        if (conn != null) {
          if (conn.isValid()) {
            if (!conn.getRealConnection().getAutoCommit()) {
              conn.getRealConnection().rollback();
            }
            conn.setConnectionTypeCode(assembleConnectionTypeCode(dataSource.getUrl(), username, password));
            conn.setCheckoutTimestamp(System.currentTimeMillis());
            conn.setLastUsedTimestamp(System.currentTimeMillis());
            state.activeConnections.add(conn);
            state.requestCount++;
            state.accumulatedRequestTime += System.currentTimeMillis() - t;
          } else {
            if (log.isDebugEnabled()) {
              log.debug("A bad connection (" + conn.getRealHashCode() + ") was returned from the pool, getting another connection.");
            }
            state.badConnectionCount++;
            localBadConnectionCount++;
            conn = null;
            if (localBadConnectionCount > (poolMaximumIdleConnections + 3)) {
              if (log.isDebugEnabled()) {
                log.debug("PooledDataSource: Could not get a good connection to the database.");
              }
              throw new SQLException("PooledDataSource: Could not get a good connection to the database.");
            }
          }
        }
      }

    }

    if (conn == null) {
      if (log.isDebugEnabled()) {
        log.debug("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
      }
      throw new SQLException("PooledDataSource: Unknown severe error condition.  The connection pool returned a null connection.");
    }

    return conn;
  }

整個流程可以看我的註釋。

此時返回的時Connection對象,但是getConnection方法獲得卻是最終的代理。

注意:
我們知道,當我們使用完一個connection對象以後,需要調用close()方法將連接關閉,但是當我們使用數據庫連接池的時候需要將關閉連接的操作替換爲將connection對象加入連接池中,以複用,因此我們使用了connection對象的代理,在不修改原有代碼的情況下將原本應該關閉連接的操作替換爲將connection對象加入連接池中。

我們看PooledConnection對象的結構:可以知道PooledConenction**實現了InvocationHandler接口**,並且,proxyConnection對象也是根據這個它來生成的代理對象。

這裏寫圖片描述

PooledDataSource獲得的Connection就是這個proxyConnection對象。

當我們調用此proxyConnection對象上的任何方法時,都會調用此對象的invoke()方法。

invoke()源碼:

/*
   * Required for InvocationHandler implementation.
   *
   * @param proxy  - not used
   * @param method - the method to be executed
   * @param args   - the parameters to be passed to the method
   * @see java.lang.reflect.InvocationHandler#invoke(Object, java.lang.reflect.Method, Object[])
   */
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String methodName = method.getName();
    if (CLOSE.hashCode() == methodName.hashCode() && CLOSE.equals(methodName)) {
    //當調用關閉的時候,其實只是將連接放回連接池而已
      dataSource.pushConnection(this);
      return null;
    } else {
      try {
        if (!Object.class.equals(method.getDeclaringClass())) {
          // issue #579 toString() should never fail
          // throw an SQLException instead of a Runtime
          checkConnection();
        }
        return method.invoke(realConnection, args);
      } catch (Throwable t) {
        throw ExceptionUtil.unwrapThrowable(t);
      }
    }
  }

關於JNDI類型的數據源DataSource比較簡單,大家可以看源碼。

這裏寫圖片描述


事務管理

MyBatis中關於事務的包結構如下:

這裏寫圖片描述

MyBatis的事務管理分爲兩種:
1.JDBC的事務管理機制:利用JDBC實現事務管理
2.MANAGED的事務管理機制:程序的容器來實現對事務的管理。

MyBatis時,一般會在MyBatisXML配置文件配置事務。

<transactionManager type="JDBC">

改變type來改變事務管理機制。

從包結構中我們看到,除了兩種事務管理機制還有一個類—TransactionFactory。TransactionFactory是創建MyBatis事務的。

public interface TransactionFactory {


  void setProperties(Properties props);


  Transaction newTransaction(Connection conn);


  Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);

}

在MyBatis初始化時,會根據不同的類型配置生成相應的TransactionFactory。

public class JdbcTransactionFactory implements TransactionFactory {

  public void setProperties(Properties props) {
  }

//JdbcTransactionFactory創建JdbcTransaction
  public Transaction newTransaction(Connection conn) {
    return new JdbcTransaction(conn);
  }

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

JdbcTransaction
我們來看一下JdbcTransaction具體如何實現事務管理的。

JdbcTransaction.java

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

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

//調用java.sql.Connection.commit()實現
  public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
      connection.commit();
    }
  }

//調用java.sql.Connection.rollback()實現
  public void rollback() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Rolling back JDBC Connection [" + connection + "]");
      }
      connection.rollback();
    }
  }

//調用java.sql.Connection.close()實現
  public void close() throws SQLException {
    if (connection != null) {
      resetAutoCommit();
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + connection + "]");
      }
      connection.close();
    }
  }


//調用java.sql.Connection.setAutoCommit()實現
  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) {
      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);
  }

}

我們可以知道,JdbcTransaction只是相當於對java.sql.Connection事務處理進行了一次包裝,其事務管理都是通過java.sql.Connection實現的。

ManagedTransaction

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

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

  public void commit() throws SQLException {
    // 什麼都沒有做
  }

  public void rollback() throws SQLException {
    // 什麼都沒有做
  }

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

}

我們可以知道,ManagedTransaction的commit和rollback方法什麼都不會做,而是將事務管理的權利移交給了容器來實現

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章