1. 程式人生 > >36---SpringAop事物管理簡介及程式設計式事物實現

36---SpringAop事物管理簡介及程式設計式事物實現

前面的幾個章節已經分析了spring基於@AspectJ的原始碼,那麼接下來我們分析一下Aop的另一個重要功能,事物管理。

1.資料庫事物特性
  • 原子性 多個數據庫操作是不可分割的,只有所有的操作都執行成功,事物才能被提交;只要有一個操作執行失敗,那麼所有的操作都要回滾,資料庫狀態必須回覆到操作之前的狀態
  • 一致性 事物操作成功後,資料庫的狀態和業務規則必須一致。例如:從A賬戶轉賬100元到B賬戶,無論資料庫操作成功失敗,A和B兩個賬戶的存款總額是不變的。
  • 隔離性 當併發操作時,不同的資料庫事物之間不會相互干擾(當然這個事物隔離級別也是有關係的)
  • 永續性 事物提交成功之後,事物中的所有資料都必須持久化到資料庫中。即使事物提交之後資料庫立刻崩潰,也需要保證資料能能夠被恢復。
2.事物隔離級別

當資料庫併發操作時,可能會引起髒讀、不可重複讀、幻讀、第一類丟失更新、第二類更新丟失等現象。

  • 髒讀 事物A讀取事物B尚未提交的更改資料,並做了修改;此時如果事物B回滾,那麼事物A讀取到的資料是無效的,此時就發生了髒讀。
  • 不可重複讀 一個事務執行相同的查詢兩次或兩次以上,每次都得到不同的資料。如:A事物下查詢賬戶餘額,此時恰巧B事物給賬戶裡轉賬100元,A事物再次查詢賬戶餘額,那麼A事物的兩次查詢結果是不一致的。
  • 幻讀 A事物讀取B事物提交的新增資料,此時A事物將出現幻讀現象。幻讀與不可重複讀容易混淆,如何區分呢?幻讀是讀取到了其他事物提交的新資料,不可重複讀是讀取到了已經提交事物的更改資料(修改或刪除)
  • 第一類丟失更新 A事物的回滾覆蓋了B事物已經提交的資料。如:賬戶有1000元,A事物執行取款100元操作,但未提交事物;此時B事物向賬戶存入100元並提交事物,賬戶餘額改為1100元。此時A事物回滾了取款操作,賬戶餘額被恢復成了1000元。
  • 第二類更新丟失 A事物的提交覆蓋了B事物已經提交的資料。如:賬戶有1000元,A事物操作向賬戶存入100元,但未提交事物;此時B事物從賬戶取出100元並提交事物,賬戶餘額改為900元;此時A事物提交事物,賬戶餘額變為1100元。

對於以上問題,可以有多個解決方案,設定資料庫事物隔離級別就是其中的一種,資料庫事物隔離級別分為四個等級,通過一個表格描述其作用。

隔離級別 髒讀 不可重複讀 幻象讀 第一類丟失更新 第二類丟失更新
READ UNCOMMITTED 允許 允許 允許 允許 允許
READ COMMITTED 髒讀 允許 允許 允許 允許
REPEATABLE READ 不允許 不允許 允許 不允許 不允許
SERIALIZABLE 不允許 不允許 不允許 不允許 不允許
2.Spring事物支援核心介面

Spring事物管理核心介面關係

  • TransactionDefinition–>定義與spring相容的事務屬性的介面
public interface TransactionDefinition {
	// 如果當前沒有事物,則新建一個事物;如果已經存在一個事物,則加入到這個事物中。
	int PROPAGATION_REQUIRED = 0;
	// 支援當前事物,如果當前沒有事物,則以非事物方式執行。
	int PROPAGATION_SUPPORTS = 1;
	// 使用當前事物,如果當前沒有事物,則丟擲異常。
	int PROPAGATION_MANDATORY = 2;
	// 新建事物,如果當前已經存在事物,則掛起當前事物。
	int PROPAGATION_REQUIRES_NEW = 3;
	// 以非事物方式執行,如果當前存在事物,則掛起當前事物。
	int PROPAGATION_NOT_SUPPORTED = 4;
	// 以非事物方式執行,如果當前存在事物,則丟擲異常。
	int PROPAGATION_NEVER = 5;
	// 如果當前存在事物,則在巢狀事物內執行;如果當前沒有事物,則與PROPAGATION_REQUIRED傳播特性相同
	int PROPAGATION_NESTED = 6;
	// 使用後端資料庫預設的隔離級別。
	int ISOLATION_DEFAULT = -1;
	// READ_UNCOMMITTED 隔離級別
	int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
	// READ_COMMITTED 隔離級別
	int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
	// REPEATABLE_READ 隔離級別
	int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
	// SERIALIZABLE 隔離級別
	int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
	// 預設超時時間
	int TIMEOUT_DEFAULT = -1;
	// 獲取事物傳播特性
	int getPropagationBehavior();
	// 獲取事物隔離級別
	int getIsolationLevel();
	// 獲取事物超時時間
	int getTimeout();
	// 判斷事物是否可讀
	boolean isReadOnly();
	// 獲取事物名稱
	@Nullable
	String getName();
}
  1. Spring事物傳播特性表:
傳播特性名稱 說明
PROPAGATION_REQUIRED 如果當前沒有事物,則新建一個事物;如果已經存在一個事物,則加入到這個事物中
PROPAGATION_SUPPORTS 支援當前事物,如果當前沒有事物,則以非事物方式執行
PROPAGATION_MANDATORY 使用當前事物,如果當前沒有事物,則丟擲異常
PROPAGATION_REQUIRES_NEW 新建事物,如果當前已經存在事物,則掛起當前事物
PROPAGATION_NOT_SUPPORTED 以非事物方式執行,如果當前存在事物,則掛起當前事物
PROPAGATION_NEVER 以非事物方式執行,如果當前存在事物,則丟擲異常
PROPAGATION_NESTED 如果當前存在事物,則在巢狀事物內執行;如果當前沒有事物,則與PROPAGATION_REQUIRED傳播特性相同
  1. Spring事物隔離級別表:
隔離級別 髒讀 不可重複讀 幻象讀 第一類丟失更新 第二類丟失更新
ISOLATION_DEFAULT 同後端資料庫 同後端資料庫 同後端資料庫 同後端資料庫 同後端資料庫
ISOLATION_READ_UNCOMMITTED 允許 允許 允許 允許 允許
ISOLATION_READ_COMMITTED 髒讀 允許 允許 允許 允許
ISOLATION_REPEATABLE_READ 不允許 不允許 允許 不允許 不允許
ISOLATION_SERIALIZABLE 不允許 不允許 不允許 不允許 不允許
  • PlatformTransactionManager–>Spring事務基礎結構中的中心介面
public interface PlatformTransactionManager {
	// 根據指定的傳播行為,返回當前活動的事務或建立新事務。
	TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
	// 就給定事務的狀態提交給定事務。
	void commit(TransactionStatus status) throws TransactionException;
	// 執行給定事務的回滾。
	void rollback(TransactionStatus status) throws TransactionException;
}

Spring將事物管理委託給底層的持久化框架來完成,因此,Spring為不同的持久化框架提供了不同的PlatformTransactionManager介面實現。列舉幾個Spring自帶的事物管理器:

事物管理器 說明
org.springframework.jdbc.datasource.DataSourceTransactionManager 提供對單個javax.sql.DataSource事務管理,用於Spring JDBC抽象框架、iBATIS或MyBatis框架的事務管理
org.springframework.orm.jpa.JpaTransactionManager 提供對單個javax.persistence.EntityManagerFactory事務支援,用於整合JPA實現框架時的事務管理
org.springframework.transaction.jta.JtaTransactionManager 提供對分散式事務管理的支援,並將事務管理委託給Java EE應用伺服器事務管理器
  • TransactionStatus–>事物狀態描述
  1. TransactionStatus介面
public interface TransactionStatus extends SavepointManager, Flushable {
	// 返回當前事務是否為新事務(否則將參與到現有事務中,或者可能一開始就不在實際事務中執行)
	boolean isNewTransaction();
	// 返回該事務是否在內部攜帶儲存點,也就是說,已經建立為基於儲存點的巢狀事務。
	boolean hasSavepoint();
    // 設定事務僅回滾。
	void setRollbackOnly();
	// 返回事務是否已標記為僅回滾
	boolean isRollbackOnly();
	// 將會話重新整理到資料儲存區
	@Override
	void flush();
	// 返回事物是否已經完成,無論提交或者回滾。
	boolean isCompleted();
}
  1. SavepointManager介面
public interface SavepointManager {
	// 建立一個新的儲存點。
	Object createSavepoint() throws TransactionException;
	// 回滾到給定的儲存點。
	// 注意:呼叫此方法回滾到給定的儲存點之後,不會自動釋放儲存點,
	// 可以通過呼叫releaseSavepoint方法釋放儲存點。
	void rollbackToSavepoint(Object savepoint) throws TransactionException;
	// 顯式釋放給定的儲存點。(大多數事務管理器將在事務完成時自動釋放儲存點)
	void releaseSavepoint(Object savepoint) throws TransactionException;
}
  1. Flushable介面
public interface Flushable {
    // 將會話重新整理到資料儲存區
    void flush() throws IOException;
}
3.Spring程式設計式事物
CREATE TABLE `account` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增主鍵',
  `balance` int(11) DEFAULT NULL COMMENT '賬戶餘額',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COMMENT='--賬戶表'
  • 實現
package com.lyc.cn.v2.day08;

import org.apache.commons.dbcp.BasicDataSource;
import org.springframework.dao.DataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.DefaultTransactionDefinition;

import javax.sql.DataSource;

/**
 * Spring程式設計式事物
 * @author: LiYanChao
 * @create: 2018-11-09 11:41
 */
public class MyTransaction {

	private JdbcTemplate jdbcTemplate;
	private DataSourceTransactionManager txManager;
	private DefaultTransactionDefinition txDefinition;
	private String insert_sql = "insert into account (balance) values ('100')";

	public void save() {

		// 1、初始化jdbcTemplate
		DataSource dataSource = getDataSource();
		jdbcTemplate = new JdbcTemplate(dataSource);

		// 2、建立物管理器
		txManager = new DataSourceTransactionManager();
		txManager.setDataSource(dataSource);

		// 3、定義事物屬性
		txDefinition = new DefaultTransactionDefinition();
		txDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

		// 3、開啟事物
		TransactionStatus txStatus = txManager.getTransaction(txDefinition);

		// 4、執行業務邏輯
		try {
			jdbcTemplate.execute(insert_sql);
			//int i = 1/0;
			jdbcTemplate.execute(insert_sql);
			txManager.commit(txStatus);
		} catch (DataAccessException e) {
			txManager.rollback(txStatus);
			e.printStackTrace();
		}

	}

	public DataSource getDataSource() {
		BasicDataSource dataSource = new BasicDataSource();
		dataSource.setDriverClassName("com.mysql.jdbc.Driver");
		dataSource.setUrl("jdbc:mysql://localhost:3306/my_test?useSSL=false&useUnicode=true&characterEncoding=UTF-8");
		dataSource.setUsername("root");
		dataSource.setPassword("[email protected]");
		return dataSource;
	}

}
  • 增加Gradle模組和包
// 引入spring-jdbc模組
optional(project(":spring-jdbc"))
// https://mvnrepository.com/artifact/commons-dbcp/commons-dbcp
compile group: 'commons-dbcp', name: 'commons-dbcp', version: '1.4'
// https://mvnrepository.com/artifact/mysql/mysql-connector-java
compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.38'
  • 測試類及結果
package com.lyc.cn.v2.day08;

import org.junit.Test;

/**
 * @author: LiYanChao
 * @create: 2018-11-07 18:45
 */
public class MyTest {
	@Test
	public void test1() {
		MyTransaction myTransaction = new MyTransaction();
		myTransaction.save();
	}
}

執行測試類,在丟擲異常之後手動回滾事物,所以資料庫表中不會增加記錄。