事務

事務的概念

事務指邏輯上的一組操作,組成這組操作的各個單位,要不全部成功,要不全部不成功。

事務的特性

事務應該具有4個屬性:原子性、一致性、隔離性、持久性。這個四個屬性統稱爲ACID特性。

  • 原子性(atomicty): 一個事務是一個不可分割的工作單位,事務中包括的諸多操作,要麼做,要麼不做。

  • 一致性(consistency):事務必須是使數據庫從一個一致性變到另一個一致性。

  • 隔離性(isotation):一個事務的執行不能被其它事務干擾。即一個事務內部的操作及使用的數據對併發的其他事務是隔離的,併發執行的各個事務之間不能相互干擾。

  • 持久性(durability):持久性也稱爲永久性,指一個事務一旦提交,它對數據庫中數據就應該是永久性的。接下來其他操作或故障不應該對其有任何影響。

      舉個例子:銀行裏有4個賬號,這四個賬號的錢加起來有10000元。這四個賬號進行相互轉賬,但是加起來的錢必須是10000元,這是事務的一致性。
      A轉賬500到賬號B——>A=A-500,B=B+500,這個操作必須捆綁在一起,要麼執行,要麼不執行。事務的一致性。
      C轉賬1000到賬號D——>C=C-100,D=D+1000,A和B之中的轉賬,不影響C和D之間的轉賬,這是事務的隔離性。
    

併發事務可能引起的問題

  • 髒讀(dirty read): 一個事務讀取了另一個事務尚未提交的信息。
  • 不可重複讀(non-repeatable read):一個事務的操作導致另一個事務前後讀取都不同的數據
  • 幻讀(phantom read): 一個事務的操作導致另一個事務前後查詢的的結果數據
    事務A:把數據庫中某個字段進行統一更改,事務B插入一條數據。啓動事務A啓動,啓動事務B,發現事務B插入的這一條數據未進行修改,這就是所謂的幻讀,幻讀出現的前提是併發的事務中有事務發生了插入、刪除操作。

事務的隔離級別

數據庫事務的隔離級別有4個,由低到高依次爲Read uncommitted 、Read committed 、Repeatable read 、Serializable ,這四個級別可以逐個解決髒讀 、不可重複讀 、幻讀 這幾類問題。

隔離級 髒讀可能性 不可重複讀可能性 幻讀可能性 加鎖讀
READ_UNCOMMITTED
READ_COMMITED
REPEATABLE_READ
SERLALIZABLE
隔離級 說明
READ_UNCOMMITTED 讀未提交,即能夠讀取到沒有被提交的數據,所以很明顯這個級別的隔離機制無法解決髒讀、不可重複讀、幻讀中的任何一種,因此很少使用。
READ_COMMITED 讀已提交,即能夠讀到那些已經提交的數據,自然能夠防止髒讀,但是無法限制不可重複讀和幻讀。
REPEATABLE_READ 重複讀取,即在數據讀出來之後加鎖,類似"select * from XXX for update",明確數據讀取出來就是爲了更新用的,所以要加一把鎖,防止別人修改它。REPEATABLE_READ的意思也類似,讀取了一條數據,這個事務不結束,別的事務就不可以改這條記錄,這樣就解決了髒讀、不可重複讀的問題,但是幻讀的問題還是無法解決
SERLALIZABLE 串行化,最高的事務隔離級別,不管多少事務,挨個運行完一個事務的所有子事務之後纔可以執行另外一個事務裏面的所有子事務,這樣就解決了髒讀、不可重複讀和幻讀的問題了

Mybatis事務

Mybatis的包下事務接口Transaction:

public interface Transaction {
	//創建
    Connection getConnection() throws SQLException;

	//提交
    void commit() throws SQLException;

	//回滾
    void rollback() throws SQLException;

	//關閉
    void close() throws SQLException;

    Integer getTimeout() throws SQLException;
}

在Mybatis中實現這個接口的類有2個,JdbcTransaction和ManagedTransaction。
Mybatis的事務管理分爲兩種形式:
1、使用JDBC的事務管理機制,即使用java.sql.Connection對象來完成事務。
2、使用MANAGED的事務管理機制:這種機制Mybatis 自身不會實現事務管理,而是讓程序的容器如(JBOSS、Weblogic)來實現事務管理
在這裏插入圖片描述事務的配置、創建和使用

1、事務的配置
我們在使用Mybatis時,一般會在MybatisXML配置文件中定義類似如下信息。
<environment>節點定義了連接某個數據庫的信息,其子節點<transactionManager> 的type 會決定我們用什麼類型的事務管理機制。
2、事務的創建
Mybatis事務的創建是交給TransactionFactory事務工廠來創建的,如果我們將<transactionManager>的type配置爲“JDBC”,那麼,Mybatis初始化解析<environment>節點時,會根據type="JDBC"創建一個JdbcTransactionFactory工廠,其源碼如下:

    /**
     * 解析<transactionManager>節點,創建對應的TransactionFactory
     * @param context
     * @return
     * @throws Exception
     */
  private TransactionFactory transactionManagerElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      /*
            在Configuration初始化的時候,會通過以下語句,給JDBC和MANAGED對應的工廠類
            typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
            typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);
            下述的resolveClass(type).newInstance()會創建對應的工廠實例
       */
      TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a TransactionFactory.");
  }

Environment表示着一個數據庫的連接,生成後的Environment對象會被設置到Configuration實例中,以供後續的使用。
在這裏插入圖片描述
在Mybatis中Configuration這個類非常複雜,Mybatis中所有的配置都在這個類中。
在這裏插入圖片描述
查看Environment類與String一樣被final修飾。
Environment源碼:

package org.apache.ibatis.mapping;
import javax.sql.DataSource;
import org.apache.ibatis.transaction.TransactionFactory;
/**
* 環境
* 決定加載哪種環境(開發環境/生產環境)
*/
public final class Environment {
 //環境id
 private final String id;
 //事務工廠
 private final TransactionFactory transactionFactory;
 //數據源
 private final DataSource dataSource;
 public Environment(String id, TransactionFactory transactionFactory, DataSource dataSource) {
   if (id == null) {
     throw new IllegalArgumentException("Parameter 'id' must not be null");
   }
   if (transactionFactory == null) {
       throw new IllegalArgumentException("Parameter 'transactionFactory' must not be null");
   }
   this.id = id;
   if (dataSource == null) {
     throw new IllegalArgumentException("Parameter 'dataSource' must not be null");
   }
   this.transactionFactory = transactionFactory;
   this.dataSource = dataSource;
 }
 //一個靜態內部類Builder
 //建造模式
 //用法應該是new Environment.Builder(id).transactionFactory(xx).dataSource(xx).build();
 public static class Builder {
     private String id;
     private TransactionFactory transactionFactory;
     private DataSource dataSource;
   public Builder(String id) {
     this.id = id;
   }
   public Builder transactionFactory(TransactionFactory transactionFactory) {
     this.transactionFactory = transactionFactory;
     return this;
   }
   public Builder dataSource(DataSource dataSource) {
     this.dataSource = dataSource;
     return this;
   }
   public String id() {
     return this.id;
   }
   public Environment build() {
     return new Environment(this.id, this.transactionFactory, this.dataSource);
   }
 }
 public String getId() {
   return this.id;
 }
 public TransactionFactory getTransactionFactory() {
   return this.transactionFactory;
 }
 public DataSource getDataSource() {
   return this.dataSource;
 }
}

3、事務工廠的使用
事務工廠TransactionFactory定義了創建Transaction的兩個方法:一個是通過指定的Connection對象來創建Transaction,另一個是通過數據源DataSource來創建Transaction。JDBC和MANAGED兩種Transaction相對應,TransactionFactory有兩個對應的實現子類。
TransactionFactory源碼

public interface TransactionFactory {
    void setProperties(Properties var1);

    Transaction newTransaction(Connection var1);

    Transaction newTransaction(DataSource var1, TransactionIsolationLevel var2, boolean var3);
}

在這裏插入圖片描述4、事務Transaction的創建
通過事務工廠TransactionFactory很容易獲取到Transaction對象實例。我們以JdbcTransaction爲例,看一下JdbcTransactionFactory是怎樣生成JdbcTransaction的,代碼如下:

public class JdbcTransactionFactory implements TransactionFactory {
 
  public void setProperties(Properties props) {
  }
 
    /**
     * 根據給定的數據庫連接Connection創建Transaction
     * @param conn Existing database connection
     * @return
     */
  public Transaction newTransaction(Connection conn) {
    return new JdbcTransaction(conn);
  }
 
    /**
     * 根據DataSource、隔離級別和是否自動提交創建Transacion
     *
     * @param ds
     * @param level Desired isolation level
     * @param autoCommit Desired autocommit
     * @return
     */
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
  }
}

如上說是,JdbcTransactionFactory會創建JDBC類型的Transaction,即JdbcTransaction。類似地,ManagedTransactionFactory也會創建ManagedTransaction。下面我們會分別深入JdbcTranaction 和ManagedTransaction,看它們到底是怎樣實現事務管理的。
5、JdbcTransaction
JdbcTransaction直接使用JDBC的提交和回滾事務管理機制 。它依賴與從dataSource中取得的連接connection 來管理transaction 的作用域,connection對象的獲取被延遲到調用getConnection()方法。如果autocommit設置爲on,開啓狀態的話,它會忽略commit和rollback。
直觀地講,就是JdbcTransaction是使用的java.sql.Connection 上的commit和rollback功能,JdbcTransaction只是相當於對java.sql.Connection事務處理進行了一次包裝(wrapper),Transaction的事務管理都是通過java.sql.Connection實現的。JdbcTransaction的代碼實現如下:

/**
 * @see JdbcTransactionFactory
 */
/**
 * @author Clinton Begin
 */
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;
  }
 
    /**
     * commit()功能 使用connection的commit()
     * @throws SQLException
     */
  public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
      connection.commit();
    }
  }
 
    /**
     * rollback()功能 使用connection的rollback()
     * @throws SQLException
     */
  public void rollback() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Rolling back JDBC Connection [" + connection + "]");
      }
      connection.rollback();
    }
  }
 
    /**
     * close()功能 使用connection的close()
     * @throws SQLException
     */
  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) {
      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);
  }
 
}

6、ManagedTransaction
ManagedTransaction讓容器來管理事務Transaction的整個生命週期,意思就是說,使用ManagedTransaction的commit和rollback功能不會對事務有任何的影響,它什麼都不會做,它將事務管理的權利移交給了容器來實現。看如下Managed的實現代碼大家就會一目瞭然:

/**
 * 
 * 讓容器管理事務transaction的整個生命週期
 * connection的獲取延遲到getConnection()方法的調用
 * 忽略所有的commit和rollback操作
 * 默認情況下,可以關閉一個連接connection,也可以配置它不可以關閉一個連接
 * 讓容器來管理transaction的整個生命週期
 * @see ManagedTransactionFactory
 */
/**
 * @author Clinton Begin
 */
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 {
    // Does nothing
  }
 
  public void rollback() throws SQLException {
    // Does nothing
  }
 
  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());
    }
  }
 
}

作者:亦山
來源:CSDN
原文:https://blog.csdn.net/luanlouis/article/details/37992171
版權聲明:本文爲博主原創文章,轉載請附上博文鏈接!

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