一、數據庫事務
1. 事務的基本概念
事務(Transaction)是訪問並可能更新數據庫中各項數據項的一個執行單元(unit)。是恢復和並發控制的基本單位。
事物的ACID4個特性:原子性、一致性、隔離性、持久性
原子性(Automicity):一個事務是一個不可分割的工作單位,事務中的事情要么都做、要么都不做。
一致性(Consistency):事務必須是使數據庫從一個一致性狀態到另一個一致性狀態,一致性與原子性是密切相關的。
隔離性(Isolation):一個事務的執行不能被其它事務所干擾。即一個事物內所修改及使用得數據對其它並行的事務來說是隔離的,並發執行的各個單位不能互相干擾。
持久性(Durability):持久性也成永久性,指一個事物一旦提交,它對數據庫的改變應該是永久性的。
2. 數據庫隔離級別
讀未提交、讀已提交、可重復讀、串行化。
隔離級別 | 隔離級別的值 | 導致的問題 |
---|---|---|
Read-Uncommited | 0 | 導致臟讀 |
Read-Committed | 1 | 避免臟讀;允許不可重復讀、幻讀; |
Repeatable-Read | 2 | 避免臟讀、不可重復讀;允許幻讀;- 類似 行鎖 |
Serializable | 3 | 串行化,一個事務執行完才能執行下一個,避免臟讀、不可重復讀、幻讀; 執行效率慢,使用時慎重;- 類似表鎖 |
臟讀:事務可以讀取別的事務還未提交的數據。比如同一條數據,A事務先讀取到然后做了修改但事務並未提交,此時B事務去讀取數據會讀取到A事務修改后的數據,此時A事務回滾,B事務也就讀取到了臟數據。
不可重復讀:針對單條數據。一個事物中發生了兩次讀操作,在第一次讀操作和第二次讀操作中,另一個事務對數據做了修改並提交了事務,則在第一個事務內兩次讀取到的數據時不同的。
幻讀:針對多條數據。A事務執行批量更新操作,更新了一批數據;B事務在這個范圍內新增了一條數據,此時A事務並不會對這條數據做修改。
數據庫隔離級別設置的越高,越能保證數據的完整性和一致性,但是對並發的性能影響也越大。
大部分數據庫的隔離級別默認是讀已提交(Read-Commited),如SqlServer、Oracle。
少數數據庫的默認隔離級別是可重復讀(Repeatable-Read),如Mysql、InnoDB
二、Spirng事務
1. Spring事物的基礎配置
先來看下常用的Spring事物的基礎配置
<aop:aspectj-autoproxy proxy-target-class="true"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置事務通知屬性 -->
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="add*" propagation="REQUIRED" rollback-for="Exception,RuntimeException,SQLException"/>
<tx:method name="remove*" propagation="REQUIRED" rollback-for="Exception,RuntimeException,SQLException"/>
<tx:method name="edit*" propagation="REQUIRED" rollback-for="Exception,RuntimeException,SQLException"/>
<tx:method name="login" propagation="NOT_SUPPORTED"/>
<tx:method name="query*" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:advisor advice-ref="transactionAdvice" pointcut-ref="transactionPointcut"/>
<aop:aspect ref="dataSource">
<aop:pointcut id="transactionPointcut" expression="execution(public * com.gupaoedu..*.service..*Service.*(..))" />
</aop:aspect>
</aop:config>
Spring事物的管理基於Spring AOP實現,同一封裝非功能性需求。
2. Spring事物的基本原理
Spring事物的本質其實是數據庫對事務的支持,沒有數據庫的事務支持,Spring是無法提供事務功能的。對於底層使用JDBC操作數據庫,用到事務的邏輯,可以參考下面的demo:
public void testJdbc() {
Connection conn = null;
Statement stmt = null;
try {
// 注冊 JDBC 驅動
// Class.forName("com.mysql.jdbc.Driver");
// 1. 打開連接
conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis", "root", "");
// 2. 開啟手動提交事務
conn.setAutoCommit(false);
// 3. 執行CURD
stmt = conn.createStatement();
String sql = "update blog set name='測試' where bid = 1";
int i = stmt.executeUpdate(sql);
// 4.提交事務
conn.commit();
System.out.println(i);
} catch (Exception e) {
try {
// 4.回滾事務
conn.rollback();
} catch (SQLException e1) {
e1.printStackTrace();
}
e.printStackTrace();
} finally {
try {
// 5.釋放資源
if (stmt != null) stmt.close();
} catch (SQLException se2) {
}
try {
if (conn != null) conn.close();
} catch (SQLException se) {
se.printStackTrace();
}
}
}
Spring項目日常開發我們經常使用@Transactional
注解來來進行事物管理,而不再需要我們寫步驟2和步驟4,那么Spring又是怎么實現的呢?
首先在程序啟動時,Spring會根據我們配置的component-scan掃描包路徑,解析相關的bean,這時候會查看這個類以及方法上的注解,針對使用了@Transactional的類和方法會重新生成代理,並根據@Transactional的相關參數進行配置注入,在代理類中字節碼重組重新為原邏輯添加了開啟事務,正常執行提交事務、異常回滾事務的邏輯。真正的數據庫事務的提交和回滾則是由其內部機制,通過binlog或者redo log實現的。
3. Spring事務的傳播屬性
在使用@Transactional
注解時,默認傳播屬性為Propagation propagation() default Propagation.REQUIRED;
常量名稱 | 解釋 |
---|---|
Propagation.REQUIRED | 支持當前事務,如果當前沒有事務就新建一個事務。這種是Spring最常見的傳播屬性,也是Spring默認的事務傳播。 |
Propagation.REQUIRES_NEW | 使用一個新的事務,如果當前沒有事務就新建一個事務,如果有事務則將當前事務掛起再新建事務。新的事物和原事務沒有任何關系,是兩個獨立的事務,外層事務出錯回滾之后不能回滾內層事務的結果,內層事務失敗回滾外層捕獲到也可以忽略回滾操作。 |
Propagation.SUPPORTS | 支持當前事務,如果當前沒有事務則以非事務方式執行。 |
Propagation.MANDATORY | 支持當前事務,如果當前沒有事務則拋出異常。必須在有外部事務的方法內執行。 |
Propagation.NOT_SUPPORTED | 不支持事務,如果當前有事務則將當前事務掛起再執行 |
Propagation.NEVER | 以非事務方式執行,如果當前存在事務則拋出異常。 |
Propagation.NESTED | 如果沒有活動事務,則按REQUIRED方式執行。如果一個活動的事務存在,則運行在一個嵌套的事務中,內部事務是一個依賴於外部事務的子事務,此時外部事務擁有多個可以單獨回滾的保存點。內部事務的回滾不會對外部事務造成影響。內部事務不能單獨commit和rollback,會隨外部事務一起commit或rollback。 |
NESTED 事務嵌套
NESTED和REQUIRES_NEW的卻別在於,如果外部方法存在事務,當內部方法是NESTED時,會先在外部事務產生一個savepoint,然后開啟一個子事務,子事務是依賴於外部事務的,外部事務commit子事務也隨之commit,外部事務rollback子事務也會rollback,子事務內發生異常,會先回滾到之前的savepoint,外部事務捕獲到子事務異常后可以根據真實需求來做后續處理;而REQUIRES_NEW在外部方法存在事務時,子方法會先將外部事務掛起,然后新建一個完全獨立的事務來執行,內部事務和外部事務互不影響。
4. Spring中的隔離級別
在使用@Transactional
注解時,默認隔離級別為Isolation isolation() default Isolation.DEFAULT;
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
public void updateLog(BatchPreviousEntity bpe) {
...
}
常量名稱 | 解釋 |
---|---|
Isolation.DEFAULT | 是PlatformTransactionManager的默認隔離級別,使用數據庫默認的事務隔離級別。另外4個與數據庫的隔離級別相對應。 |
Isolation.READ_UNCOMMITTED | 讀未提交 |
Isolation.READ_COMMITTED | 讀已提交 |
Isolation.REPEATABLE_READ | 可重復讀 |
Isolation.SERIALIZABLE | 串行化 |