36---SpringAop事物管理簡介及程式設計式事物實現
阿新 • • 發佈:2018-12-20
前面的幾個章節已經分析了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事物支援核心介面
- 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();
}
- Spring事物傳播特性表:
傳播特性名稱 | 說明 |
---|---|
PROPAGATION_REQUIRED | 如果當前沒有事物,則新建一個事物;如果已經存在一個事物,則加入到這個事物中 |
PROPAGATION_SUPPORTS | 支援當前事物,如果當前沒有事物,則以非事物方式執行 |
PROPAGATION_MANDATORY | 使用當前事物,如果當前沒有事物,則丟擲異常 |
PROPAGATION_REQUIRES_NEW | 新建事物,如果當前已經存在事物,則掛起當前事物 |
PROPAGATION_NOT_SUPPORTED | 以非事物方式執行,如果當前存在事物,則掛起當前事物 |
PROPAGATION_NEVER | 以非事物方式執行,如果當前存在事物,則丟擲異常 |
PROPAGATION_NESTED | 如果當前存在事物,則在巢狀事物內執行;如果當前沒有事物,則與PROPAGATION_REQUIRED傳播特性相同 |
- 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–>事物狀態描述
- TransactionStatus介面
public interface TransactionStatus extends SavepointManager, Flushable {
// 返回當前事務是否為新事務(否則將參與到現有事務中,或者可能一開始就不在實際事務中執行)
boolean isNewTransaction();
// 返回該事務是否在內部攜帶儲存點,也就是說,已經建立為基於儲存點的巢狀事務。
boolean hasSavepoint();
// 設定事務僅回滾。
void setRollbackOnly();
// 返回事務是否已標記為僅回滾
boolean isRollbackOnly();
// 將會話重新整理到資料儲存區
@Override
void flush();
// 返回事物是否已經完成,無論提交或者回滾。
boolean isCompleted();
}
- SavepointManager介面
public interface SavepointManager {
// 建立一個新的儲存點。
Object createSavepoint() throws TransactionException;
// 回滾到給定的儲存點。
// 注意:呼叫此方法回滾到給定的儲存點之後,不會自動釋放儲存點,
// 可以通過呼叫releaseSavepoint方法釋放儲存點。
void rollbackToSavepoint(Object savepoint) throws TransactionException;
// 顯式釋放給定的儲存點。(大多數事務管理器將在事務完成時自動釋放儲存點)
void releaseSavepoint(Object savepoint) throws TransactionException;
}
- 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();
}
}
執行測試類,在丟擲異常之後手動回滾事物,所以資料庫表中不會增加記錄。