關(guān)于Spring中聲明式事務(wù)的使用詳解
一、前言
在Spring中,數(shù)據(jù)庫事務(wù)是通過AOP技術(shù)來提供服務(wù)的。使用原生的JDBC操作時(shí)會(huì)存在大量的try{}catch{}finally{}語句,所以會(huì)存在大量的冗余代碼,例如打開和關(guān)閉數(shù)據(jù)庫連接和數(shù)據(jù)庫事務(wù)回滾等。通過Spring的AOP之后,這些冗余的代碼就都被處理了。
二、回顧JDBC的數(shù)據(jù)庫事務(wù)
接下來我們一起回顧一下,剛?cè)腴T使用JDBC操作的時(shí)候,寫得讓人煩躁代碼片段吧。
@Service public class JdbcTransaction { @Autowired private DataSource dataSource; public int insertStudent(Student student) { Connection connection = null; int result = 0; try { // 獲取數(shù)據(jù)連接 connection = dataSource.getConnection(); // 開始事務(wù) connection.setAutoCommit(false); // 設(shè)置隔離級(jí)別 connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); // 執(zhí)行sql PreparedStatement prepareStatement = connection.prepareStatement("insert into t_student(name,gender,age) values (?,?,?)"); prepareStatement.setString(1, student.getName()); prepareStatement.setString(2, student.getGender()); prepareStatement.setInt(3, student.getAge()); result = prepareStatement.executeUpdate(); // 提交事務(wù) connection.commit(); } catch (Exception e1) { if (connection != null) { try { // 事務(wù)回滾 connection.rollback(); } catch (Exception e2) { e2.printStackTrace(); } } e1.printStackTrace(); } finally { try { if (connection != null && !connection.isClosed()) { // 關(guān)閉連接 connection.close(); } } catch (SQLException e) { e.printStackTrace(); } } return result; } }
在上述的一大串代碼中也就一下已行的業(yè)務(wù)代碼是我們最為關(guān)注的,在每個(gè)使用JDBC的業(yè)務(wù)代碼中,都經(jīng)??梢钥吹綌?shù)據(jù)庫連接的獲取和關(guān)閉以及事務(wù)的提交和回滾,大量的try...catch...finally..語句會(huì)充斥在代碼塊中。而我們也僅是想執(zhí)行簡(jiǎn)單的一條sql代碼而已。如果執(zhí)行多條sql,這些代碼顯然更加的就難以控制。
PreparedStatement prepareStatement = connection.prepareStatement("insert into t_student(name,gender,age) values (?,?,?)");
prepareStatement.setString(1, student.getName());
prepareStatement.setString(2, student.getGender());
prepareStatement.setInt(3, student.getAge());
result = prepareStatement.executeUpdate();
隨著不斷地的發(fā)展和優(yōu)化,使用像MyBatis或Hibernate這種ORM框架可以減少一些代碼,但是依舊不能減少打開和關(guān)閉數(shù)據(jù)庫連接和事務(wù)控制的代碼,但是這些我們可以通過AOP把這些公共代碼抽取出來,單獨(dú)實(shí)現(xiàn)。
三、數(shù)據(jù)庫事務(wù)隔離級(jí)別
3.1 數(shù)據(jù)庫事務(wù)的基本特征
- Atomicity(原子性):一個(gè)事務(wù)(transaction)中的所有操作,要么全部完成,要么全部不完成,不會(huì)結(jié)束在中間某個(gè)環(huán)節(jié)。事務(wù)在執(zhí)行過程中發(fā)生錯(cuò)誤,會(huì)被恢復(fù)(Rollback)到事務(wù)開始前的狀態(tài),就像這個(gè)事務(wù)從來沒有執(zhí)行過一樣。
- Consistency(一致性):在事務(wù)開始之前和事務(wù)結(jié)束以后,數(shù)據(jù)庫的完整性沒有被破壞。這表示寫入的資料必須完全符合所有的預(yù)設(shè)規(guī)則,這包含資料的精確度、串聯(lián)性以及后續(xù)數(shù)據(jù)庫可以自發(fā)性地完成預(yù)定的工作。
- Isolation(隔離性):數(shù)據(jù)庫允許多個(gè)并發(fā)事務(wù)同時(shí)對(duì)其數(shù)據(jù)進(jìn)行讀寫和修改的能力,隔離性可以防止多個(gè)事務(wù)并發(fā)執(zhí)行時(shí)由于交叉執(zhí)行而導(dǎo)致數(shù)據(jù)的不一致。事務(wù)隔離分為不同級(jí)別,包括讀未提交(Read uncommitted)、讀提交(read committed)、可重復(fù)讀(repeatable read)和串行化(Serializable)。這也是本節(jié)要講的內(nèi)容。
- Durability(持久性):事務(wù)處理結(jié)束后,對(duì)數(shù)據(jù)的修改就是永久的,即便系統(tǒng)故障也不會(huì)丟失。
這里引用一下網(wǎng)上用的很多的講述隔離性的例子。假設(shè)有一種商品有庫存100,每次都只能搶購一件商品。就會(huì)出現(xiàn)各種情況:
第一類丟失更新
時(shí)刻 | 事務(wù)1 | 事務(wù)2 |
---|---|---|
T1 | 初始庫存100 | 初始庫存100 |
T2 | 扣減庫存,剩余99 | |
T3 | 扣減庫存,剩余99 | |
T4 | 提交事務(wù),商品庫存為99 | |
T5 | 回滾事務(wù),商品庫存為100 |
像上面所述,對(duì)于一個(gè)事務(wù)回滾了另外一個(gè)事務(wù)提交,而引發(fā)的數(shù)據(jù)不一致的情況,被稱為第一類丟失更新。目前的數(shù)據(jù)庫已經(jīng)解決了第一類丟失更新的問題,即上述表中描述的問題。
第二類丟失更新
時(shí)刻 | 事務(wù)1 | 事務(wù)2 |
---|---|---|
T1 | 初始庫存100 | 初始庫存100 |
T2 | 扣減庫存,剩余99 | |
T3 | 扣減庫存,剩余99 | |
T4 | 提交事務(wù),剩余庫存99 | |
T5 | 提交事務(wù),剩余庫存99 |
在事務(wù)1中無法感知事務(wù)2的操作,所以事務(wù)1并不知道事務(wù)2修改過數(shù)據(jù),因此它認(rèn)為只是發(fā)生了一個(gè)業(yè)務(wù),所以庫存還是被事務(wù)1修改成99。T5時(shí)刻事務(wù)1的提交,導(dǎo)致事務(wù)2提交結(jié)果丟失,這樣多個(gè)事務(wù)都提交引發(fā)的數(shù)據(jù)丟失更新稱為第二類丟失更新。這是互聯(lián)網(wǎng)系統(tǒng)重點(diǎn)關(guān)注的內(nèi)容,為了克服這個(gè)問題,數(shù)據(jù)庫提出了事務(wù)之間的隔離級(jí)別的概念。
3.2 詳解數(shù)據(jù)庫隔離級(jí)別
為了壓制更新丟失,數(shù)據(jù)庫標(biāo)準(zhǔn)踢輸了四種隔離級(jí)別,在不同程度上壓制丟失更新。這四類隔離級(jí)別就是上面提到的:未提交讀,讀寫提交,可重復(fù)讀和串行化,它們會(huì)在不同程度上壓制丟失更新的情況。
3.2.1 未提交讀
未提交讀是數(shù)據(jù)庫最低的隔離級(jí)別,它允許一個(gè)事務(wù)讀取另外一個(gè)事務(wù)沒有提交的數(shù)據(jù)。未提交讀是很危險(xiǎn)的隔離級(jí)別,一般在實(shí)際開發(fā)中并沒有廣泛使用,它的最大的有點(diǎn)就是并發(fā)能力高,適合一些對(duì)數(shù)據(jù)一致性沒有要求但追求高并發(fā)的場(chǎng)景,最大的缺點(diǎn)就是會(huì)造成臟讀。
臟讀現(xiàn)象
時(shí)刻 | 事務(wù)1 | 事務(wù)2 | 備注 |
---|---|---|---|
T0 | 商品的庫存初始化數(shù)量為100 | ||
T1 | 讀取庫存100 | ||
T2 | 扣減庫存 | 此時(shí)庫存為99 | |
T3 | 扣減庫存 | 此時(shí)庫存為98,讀取到了事務(wù)1沒提交的數(shù)據(jù) | |
T4 | 提交事務(wù) | 庫存保存為98 | |
T5 | 回滾事務(wù) | 因?yàn)榈谝活悂G失更新已經(jīng)解決,所以庫存不會(huì)回滾到100,此時(shí)庫存為98 |
如果數(shù)據(jù)使用了未提交讀的隔離級(jí)別,就可能出現(xiàn)上述表格中的問題。這種現(xiàn)象被稱為臟讀,事務(wù)2讀取到了事務(wù)1還沒提交的數(shù)據(jù),當(dāng)事務(wù)1回滾之后,這數(shù)據(jù)便成為了臟數(shù)據(jù)。在讀寫提交的隔離級(jí)別中就克服了臟讀的現(xiàn)象。
3.2.2 讀提交
讀寫提交隔離級(jí)別,是指一個(gè)事務(wù)只能讀取到另外一個(gè)事務(wù)已經(jīng)提交的數(shù)據(jù),不能讀取未提交的數(shù)據(jù)。
時(shí)刻 | 事務(wù)1 | 事務(wù)2 | 備注 |
---|---|---|---|
T0 | 商品的庫存初始化數(shù)量為100 | ||
T1 | 讀取庫存100 | ||
T2 | 扣減庫存 | 此時(shí)庫存為99 | |
T3 | 扣減庫存 | 此時(shí)庫存為99,讀取不了事務(wù)1沒提交的數(shù)據(jù) | |
T4 | 提交事務(wù) | 庫存保存為99 | |
T5 | 回滾事務(wù) | 因?yàn)榈谝活悂G失更新已經(jīng)解決,所以庫存不會(huì)回滾到100,此時(shí)庫存為99 |
這就是讀提交,若有事務(wù)對(duì)數(shù)據(jù)進(jìn)行更新操作時(shí),讀操作事務(wù)要等待這個(gè)更新操作事務(wù)提交后才能讀取數(shù)據(jù),可以解決臟讀問題。但在這個(gè)事例中,如果事務(wù)2先不提交,事務(wù)1未提交是查詢的庫存是99,事務(wù)1提交了事務(wù)2再去查詢庫存此時(shí)庫存是98,這就出現(xiàn)了一個(gè)事務(wù)范圍內(nèi)兩個(gè)相同的查詢卻返回了不同數(shù)據(jù),這就是不可重復(fù)讀。
這是各種系統(tǒng)中最常用的一種隔離級(jí)別,也是SQL Server和Oracle的默認(rèn)隔離級(jí)別。這種隔離級(jí)別能夠有效的避免臟讀,但除非在查詢中顯示的加鎖,如:
select * from T where ID=2 lock in share mode; select * from T where ID=2 for update;
很明顯讀提交隔離級(jí)別會(huì)引起不可重復(fù)讀現(xiàn)象,而可重復(fù)讀隔離級(jí)別就可以解決不可重復(fù)讀。
3.2.3 可重復(fù)讀
可重復(fù)讀的目標(biāo)就是克服讀提交中出現(xiàn)的不可重復(fù)讀的現(xiàn)象,因?yàn)樵谧x提交的時(shí)候,可能出現(xiàn)一些值的變化,影響當(dāng)前事務(wù)的執(zhí)行。
解決不可重復(fù)度
時(shí)刻 | 事務(wù)1 | 事務(wù)2 | 備注 |
---|---|---|---|
T0 | 商品的庫存初始化數(shù)量為100 | ||
T1 | 讀取庫存100 | ||
T2 | 扣減庫存 | 此時(shí)庫存為99 | |
T3 | 讀取庫存 | 不允許讀取,事務(wù)1還沒提交 | |
T4 | 提交事務(wù) | 庫存保存為99 | |
T5 | 讀取庫存 | 此時(shí)庫存為99 |
幻讀
時(shí)刻 | 事務(wù)1 | 事務(wù)2 | 備注 |
---|---|---|---|
T0 | 商品的庫存初始化數(shù)量為100 | ||
T1 | 讀取庫存100 | ||
T2 | 查詢訂單記錄 | 0筆訂單記錄 | |
T3 | 扣庫存 | 庫存保存為99 | |
T4 | 插入一個(gè)訂單記錄 | 新增1條訂單記錄 | |
T5 | 提交事務(wù) | 此時(shí)庫存為99,1條訂單記錄 | |
T6 | 查詢訂單記錄 | 有1條訂單記錄,出現(xiàn)了查詢不一致,在事務(wù)2看來出現(xiàn)了和之前查詢不一致的結(jié)果 |
3.2.4 串行化
串行化是數(shù)據(jù)庫最高的隔離級(jí)別,它要求所有的sql按照順序執(zhí)行,這樣就可以克服上面所述的所有問題,所以能夠保證數(shù)據(jù)的一致性。但是性能也是最差的。
3.2.5 各個(gè)隔離級(jí)別的總結(jié)
隔離級(jí)別 | 臟讀 | 不可重復(fù)讀 | 歡度 |
---|---|---|---|
未提交讀 | √ | √ | √ |
讀提交 | × | √ | √ |
可重復(fù)讀 | × | × | √ |
串行化 | × | × | × |
隔離級(jí)別臟讀不可重復(fù)讀歡度未提交讀√√√讀提交×√√可重復(fù)讀××√串行化×××
對(duì)于不同的數(shù)據(jù)庫的支持是不一樣的,Oracle只能支持讀提交和串行化,MySQL則是能夠支持上面四種。Oracle默認(rèn)的隔離級(jí)別是讀提交,MySQL則是可重復(fù)讀。
四、數(shù)據(jù)庫事務(wù)傳播行為
傳播行為是方法之間調(diào)用事務(wù)采取的策略問題。在通常情況下數(shù)據(jù)庫事務(wù)要么全部成功,要么全部失敗。在Spring中當(dāng)一個(gè)方法調(diào)用另外一個(gè)方法時(shí),可以讓事務(wù)采取不同的策略,例如新建一個(gè)事務(wù)處理或者掛起當(dāng)前事務(wù)等,這就是事務(wù)的傳播。例如下面deleteStudent()調(diào)用了findStudentById(id)檢查Student是否存在。
@Service public class StudentServiceImpl implements IStudentService { @Autowired private StudentRepository studentRepository; @Transactional(rollbackFor = RuntimeException.class) @Override public Student findStudentById(Long id) { return studentRepository.getOne(id); } ... @Transactional(rollbackFor = RuntimeException.class) @Override public void deleteStudent(Long id) { Student student = findStudentById(id); if(student == null){ // data not exists } studentRepository.deleteById(id); } ... }
在Spring中支持的事務(wù)傳播行為:
package org.springframework.transaction.annotation; import org.springframework.transaction.TransactionDefinition; public enum Propagation { /** * 支持當(dāng)前事務(wù),如果不存在則創(chuàng)建一個(gè)新事務(wù)。 類似于同名的 EJB 事務(wù)屬性。 * 這是事務(wù)注釋的默認(rèn)設(shè)置。 */ REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED), /** * 支持當(dāng)前事務(wù),如果不存在則以非事務(wù)方式執(zhí)行。 類似于同名的 EJB 事務(wù)屬性。 * 注意:對(duì)于具有事務(wù)同步的事務(wù)管理器, SUPPORTS與根本沒有事務(wù)略有不同,因?yàn)樗x了同步將應(yīng)用的事務(wù)范圍。 * 因此,相同的資源(JDBC 連接、Hibernate 會(huì)話等)將在整個(gè)指定范圍內(nèi)共享。 請(qǐng)注意,這取決于事務(wù)管理器的實(shí)際同步配置 */ SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS), /** * 支持當(dāng)前事務(wù),如果不存在則拋出異常。 類似于同名的 EJB 事務(wù)屬性。 */ MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY), /** * 創(chuàng)建一個(gè)新事務(wù),如果存在,則暫停當(dāng)前事務(wù)。 類似于同名的 EJB 事務(wù)屬性。 * 注意:實(shí)際的事務(wù)暫停不會(huì)在所有事務(wù)管理器上開箱即用。 * 這尤其適用于org.springframework.transaction.jta.JtaTransactionManager * 它需要javax.transaction.TransactionManager對(duì)其可用(這在標(biāo)準(zhǔn) Java EE 中是特定于服務(wù)器的) */ REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW), /** * 以非事務(wù)方式執(zhí)行,如果存在則暫停當(dāng)前事務(wù)。 類似于同名的 EJB 事務(wù)屬性。 * 注意:實(shí)際的事務(wù)暫停不會(huì)在所有事務(wù)管理器上開箱即用。 這尤其適用于org.springframework.transaction.jta.JtaTransactionManager * 它需要javax.transaction.TransactionManager對(duì)其可用(這在標(biāo)準(zhǔn) Java EE 中是特定于服務(wù)器的)。 */ NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED), /** * 以非事務(wù)方式執(zhí)行,如果存在事務(wù)則拋出異常。 類似于同名的 EJB 事務(wù)屬性。 */ NEVER(TransactionDefinition.PROPAGATION_NEVER), /** * 如果當(dāng)前事務(wù)存在,則在嵌套事務(wù)中執(zhí)行,否則行為類似于REQUIRED 。 EJB 中沒有類似的特性。 * 注意:嵌套事務(wù)的實(shí)際創(chuàng)建僅適用于特定的事務(wù)管理器。 開箱即用, * 這僅適用于 JDBC DataSourceTransactionManager。 一些 JTA 提供者也可能支持嵌套事務(wù)。 * @see org.springframework.jdbc.datasource.DataSourceTransactionManager */ NESTED(TransactionDefinition.PROPAGATION_NESTED); private final int value; Propagation(int value) { this.value = value; } public int value() { return this.value; } }
事務(wù)的傳播行為,默認(rèn)值為 Propagation.REQUIRED。可以手動(dòng)指定其他的事務(wù)傳播行為,如下:
(1)Propagation.REQUIRED
如果當(dāng)前存在事務(wù),則加入該事務(wù),如果當(dāng)前不存在事務(wù),則創(chuàng)建一個(gè)新的事務(wù)。
(2)Propagation.SUPPORTS
如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前不存在事務(wù),則以非事務(wù)的方式繼續(xù)運(yùn)行。
(3)Propagation.MANDATORY
如果當(dāng)前存在事務(wù),則加入該事務(wù);如果當(dāng)前不存在事務(wù),則拋出異常。
(4)Propagation.REQUIRES_NEW
重新創(chuàng)建一個(gè)新的事務(wù),如果當(dāng)前存在事務(wù),延緩當(dāng)前的事務(wù)。
(5)Propagation.NOT_SUPPORTED
以非事務(wù)的方式運(yùn)行,如果當(dāng)前存在事務(wù),暫停當(dāng)前的事務(wù)。
(6)Propagation.NEVER
以非事務(wù)的方式運(yùn)行,如果當(dāng)前存在事務(wù),則拋出異常。
(7)Propagation.NESTED
如果沒有,就新建一個(gè)事務(wù);如果有,就在當(dāng)前事務(wù)中嵌套其他事務(wù)。
五、Spring中的聲明式事務(wù)的使用
在Spring中使用事務(wù)很簡(jiǎn)單,只需要在方法上添加@Transactional注解即可,Spring的事務(wù)管理幫我們做了如,在使用JDBC的時(shí)候的那些繁瑣的try/catch代碼。
@Service public class StudentServiceImpl implements IStudentService { ... @Transactional @Override public Student insertStudent(Student student) { return studentRepository.save(student); } ... }
當(dāng)Spring上下文開始調(diào)用被@Transactional修飾的方法或者類時(shí),Spring就會(huì)產(chǎn)生AOP的功能,Spring中的事務(wù)管理是基于AOP的。當(dāng)啟動(dòng)事務(wù)時(shí),會(huì)根據(jù)事務(wù)定義器內(nèi)的配置去設(shè)置事務(wù),首先是根據(jù)傳播行為來確定事務(wù)的策略,上一節(jié)說道,Spring中默認(rèn)的傳播行為是Propagation.REQUIRED(如果當(dāng)前存在事務(wù),則加入該事務(wù),如果當(dāng)前不存在事務(wù),則創(chuàng)建一個(gè)新的事務(wù))。然后是隔離級(jí)別、超時(shí)時(shí)間、只讀內(nèi)容等內(nèi)容的設(shè)置,這些Spring中的事務(wù)管理器都有默認(rèn)的設(shè)置,開發(fā)者只需要直接使用@Transactional注解即可,如果不滿足也可以自行配置。
通過@Transactional注解的屬性配置去設(shè)置數(shù)據(jù)庫的事務(wù),在程序執(zhí)行到開發(fā)者編寫的程序事,如果發(fā)生異常,Spring數(shù)據(jù)庫事務(wù)的流程中,它會(huì)根據(jù)是否發(fā)生異常來才去不同的策略。無論是否發(fā)生異常,Spring事務(wù)管理器會(huì)釋放事務(wù)資源,這樣就可以保證數(shù)據(jù)庫連接的正??捎?。這樣就減少了
5.1 @Transactional的配置屬性
我們來看一下@Transactiona注解的源碼:
@Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Inherited @Documented public @interface Transactional { // 通過bean name執(zhí)行事務(wù)管理器 @AliasFor("transactionManager") String value() default ""; // 同value屬性 @AliasFor("value") String transactionManager() default ""; // 指定事務(wù)傳播行為,默認(rèn)是Propagation.REQUIRED Propagation propagation() default Propagation.REQUIRED; // 指定事務(wù)的隔離級(jí)別,默認(rèn)是使用底層數(shù)據(jù)存儲(chǔ)的默認(rèn)隔離級(jí)別。如MySQL是可重復(fù)讀 Isolation isolation() default Isolation.DEFAULT; // 指定超時(shí)時(shí)間,單位時(shí)間秒 int timeout() default -1; // 是否是只讀事務(wù) boolean readOnly() default false; // 在發(fā)生指定的異常是回滾事務(wù),默認(rèn)是所有異常都回滾 Class<? extends Throwable>[] rollbackFor() default {}; // 方法在發(fā)生指定異常名稱時(shí)回滾事務(wù),默認(rèn)是所有異常都回滾 String[] rollbackForClassName() default {}; // 在發(fā)生指定的異常是不回滾事務(wù) Class<? extends Throwable>[] noRollbackFor() default {}; // 方法在發(fā)生指定異常名稱時(shí)不回滾事務(wù) String[] noRollbackForClassName() default {}; }
5.2 Spring的事務(wù)管理器
Spring中的事務(wù)的打開、回滾和提交是由事務(wù)管理器來完成的。Spring中事務(wù)的頂層接口是TransactionManager這是個(gè)空接口,真正定義了方法的PlatformTransactionManager接口。當(dāng)引入了其他框架的時(shí)候還會(huì)有其他的事務(wù)管理器的類,例如HibernateTransactionManager和JpaTransactionManager就是spring-orm這依賴?yán)锩妫荢pring官方編寫的提供的。如果引入的Redisson,還會(huì)有RedissonTransactionManager。最常用的就是DataSourceTransactionManger,它
也是實(shí)現(xiàn)中用的最多的的事務(wù)管理器。
PlatformTransactionManager接口中只有三個(gè)方法,獲取事務(wù),提交事務(wù)和回滾事務(wù)。這是事務(wù)的最基本的。不同的事務(wù)管理就可以在此基礎(chǔ)上實(shí)現(xiàn)自己的功能。例如在Spring Boot中引入了spring-boot-starter-data-jpa依賴之后,就會(huì)自動(dòng)創(chuàng)建JpaTransactionManager作為事務(wù)管理器,所以一般是不需要我們自己創(chuàng)建事務(wù)管理器,除非有特定的需求。
public interface PlatformTransactionManager extends TransactionManager { // 獲取事務(wù)m還可以設(shè)置數(shù)據(jù)屬性 TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException; // 提交事務(wù) void commit(TransactionStatus status) throws TransactionException; // 回滾事務(wù) void rollback(TransactionStatus status) throws TransactionException; }
5.3 配置事務(wù)的傳播行為和隔離級(jí)別
現(xiàn)在配置測(cè)試一下BatchServiceImpl調(diào)用StudentServiceImpl的方法。
插入一個(gè)Student
@Service public class StudentServiceImpl implements IStudentService { @Autowired private StudentRepository studentRepository; @Transactional(rollbackFor = RuntimeException.class, isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRES_NEW) @Override public Student insertStudent(Student student) { return studentRepository.save(student); } }
批量插入
@Service public class BatchServiceImpl implements IBatchService { @Autowired private IStudentService studentService; @Transactional(rollbackFor = RuntimeException.class, isolation = Isolation.REPEATABLE_READ, propagation = Propagation.REQUIRED) @Override public void batchInsertStudent(List<Student> students) { for (Student student : students) { studentService.insertStudent(student); } } }
batchInsertStudent方法使用Propagation.REQUIRED的傳播行為,數(shù)據(jù)庫隔離級(jí)別使用REPEATABLE_READ。調(diào)用的insertStudent方法,其傳播行為是REQUIRES_NEW,被調(diào)用時(shí)會(huì)新開一個(gè)事務(wù)。
關(guān)于@Transactional自調(diào)用,傳播行為失效的問題
如下代碼,在同一個(gè)類中相互調(diào)用的方法,會(huì)導(dǎo)致@Transactional中定義的傳播行為失效。在自調(diào)用的過程中,是類的自身的調(diào)用,而不是代理對(duì)象去調(diào)用,那么就不會(huì)產(chǎn)生AOP,這樣自調(diào)用就不會(huì)被事務(wù)管理器管理,就不能把代碼織入到約定的流程中。像一個(gè)service調(diào)用另外一個(gè)service的,這樣就是代理對(duì)象的調(diào)用,Spring才把代碼織入到AOP的流程中。
@Service public class StudentServiceImpl implements IStudentService { @Autowired private StudentRepository studentRepository; @Transactional(rollbackFor = RuntimeException.class, propagation = Propagation.REQUIRES_NEW) @Override public Student findStudentById(Long id) { return studentRepository.getOne(id); } @Transactional(rollbackFor = RuntimeException.class, isolation = Isolation.REPEATABLE_READ) @Override public void deleteStudent(Long id) { // 自調(diào)用 Student db = findStudentById(id); if (db == null) { throw new RuntimeException("數(shù)據(jù)不存在"); } studentRepository.deleteById(id); } }
解決自調(diào)用是事務(wù)傳播行為失效的問題
通過在Spring上下文中獲取IStudentService對(duì)象,此時(shí)該對(duì)象就是個(gè)代理對(duì)象,這樣通過代理對(duì)象去調(diào)用就可以出發(fā)傳播行為了。
@Service public class StudentServiceImpl implements IStudentService { @Autowired private StudentRepository studentRepository; // 注入Spring上下文對(duì)象 @Autowired private ApplicationContext context; @Transactional(rollbackFor = RuntimeException.class, propagation = Propagation.REQUIRES_NEW) @Override public Student findStudentById(Long id) { return studentRepository.getOne(id); } @Transactional(rollbackFor = RuntimeException.class, isolation = Isolation.REPEATABLE_READ) @Override public void deleteStudent(Long id) { // 從上下文中獲取IStudentService IStudentService studentService = context.getBean(IStudentService.class); Student db = findStudentById(id); if (db == null) { throw new RuntimeException("數(shù)據(jù)不存在"); } studentRepository.deleteById(id); } }
六、總結(jié)
本文從原生JDBC的事務(wù)管理開始到介紹數(shù)據(jù)庫的隔離級(jí)別和Spring中的傳播行為,最后使用Spring的聲明式事務(wù),其實(shí)在正常的使用中,Spring的聲明式事務(wù)用起來很簡(jiǎn)單和簡(jiǎn)潔,這是Spring內(nèi)部幫我們做好了很多的事情。
到此這篇關(guān)于Spring中聲明式事務(wù)使用的文章就介紹到這了,更多相關(guān)Spring聲明式事務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
通過反射注解批量插入數(shù)據(jù)到DB的實(shí)現(xiàn)方法
今天小編就為大家分享一篇關(guān)于通過反射注解批量插入數(shù)據(jù)到DB的實(shí)現(xiàn)方法,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-03-03Java使用easyExcel實(shí)現(xiàn)導(dǎo)入功能
這篇文章介紹了Java使用easyExcel實(shí)現(xiàn)導(dǎo)入功能的方法,文中通過示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-10-10Java通過反射,如何動(dòng)態(tài)修改注解的某個(gè)屬性值
這篇文章主要介紹了Java通過反射,動(dòng)態(tài)修改注解的某個(gè)屬性值操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-07-07SpringBoot 整合 Lettuce Redis的實(shí)現(xiàn)方法
這篇文章主要介紹了SpringBoot 整合 Lettuce Redis的實(shí)現(xiàn)方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07Spring中使用copyProperties方法進(jìn)行對(duì)象之間的屬性賦值詳解
這篇文章主要介紹了Spring中使用copyProperties方法進(jìn)行對(duì)象之間的屬性賦值詳解,使用org.springframework.beans.BeanUtils.copyProperties方法進(jìn)行對(duì)象之間屬性的賦值,避免通過get、set方法一個(gè)一個(gè)屬性的賦值,需要的朋友可以參考下2023-12-12Java中數(shù)組轉(zhuǎn)List的三種方法與對(duì)比分析
這篇文章主要給大家介紹了關(guān)于Java中數(shù)組轉(zhuǎn)List的三種方法與對(duì)比分析的相關(guān)資料,分別介紹了最常見方式、數(shù)組轉(zhuǎn)為L(zhǎng)ist后,支持增刪改查的方式以及通過集合工具類Collections.addAll()方法,需要的朋友可以參考下2018-07-07Springboot使用ResponseBody漢字返回問號(hào)問題
這篇文章主要介紹了Springboot使用ResponseBody漢字返回問號(hào)問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-06-06Java實(shí)現(xiàn)文件上傳的兩種方法(uploadify和Spring)
這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)文件上傳的兩種方法,uploadify和Spring實(shí)現(xiàn)文件上傳,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11淺談Java中Spring Boot的優(yōu)勢(shì)
在本篇文章中小編給大家分析了Java中Spring Boot的優(yōu)勢(shì)以及相關(guān)知識(shí)點(diǎn)內(nèi)容,興趣的朋友們可以學(xué)習(xí)參考下。2018-09-09以Java?Web項(xiàng)目為例淺談前后端分離開發(fā)模式
這篇文章主要介紹了以Java?Web項(xiàng)目為例淺談前后端分離開發(fā)模式,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-08-08