一文詳解Spring事務(wù)的實(shí)現(xiàn)與本質(zhì)
一、Spring事務(wù)的基礎(chǔ)知識(shí)
先回憶下Spring事務(wù)的基礎(chǔ)知識(shí)事務(wù)的隔離級(jí)別與Spring事務(wù)的傳播機(jī)制。我們知道數(shù)據(jù)庫(kù)層面是不支持事務(wù)的傳播機(jī)制的,這個(gè)是Spring獨(dú)有的
1.臟讀、不可重復(fù)讀、幻讀
臟讀:對(duì)于同一條數(shù)據(jù)在一個(gè)事務(wù)中多次讀取的結(jié)果不一致,原因是第二次讀取數(shù)據(jù)時(shí)讀取的數(shù)據(jù)被其他未提交的事務(wù)修改了,當(dāng)隔離級(jí)別是讀未提交時(shí)就會(huì)有這種問(wèn)題存在。
不可重復(fù)讀:對(duì)于同一條數(shù)據(jù)在一個(gè)事務(wù)中多次讀取的結(jié)果不一致,原因是第二次讀取數(shù)據(jù)時(shí)讀取的數(shù)據(jù)被其他已提交的事務(wù)修改了,當(dāng)隔離級(jí)別是讀已提交時(shí)就會(huì)有這種問(wèn)題存在。
幻讀:臟讀和不可重復(fù)讀都是針對(duì)一條數(shù)據(jù)來(lái)說(shuō)的,而我們使用重復(fù)讀的隔離級(jí)別就可以解決臟讀和不可重復(fù)讀的問(wèn)題了,但是還是會(huì)有幻讀的問(wèn)題,那什么是幻讀呢?幻讀指的是范圍查詢前后檢索結(jié)果不一致,比如說(shuō)事務(wù)里第一次查詢customer表有10萬(wàn)記錄,同一個(gè)事務(wù)第二次查詢時(shí)有11萬(wàn)記錄,這就是幻讀?;米x產(chǎn)生的原因不是事務(wù)并發(fā)修改導(dǎo)致的(不可),而是查詢時(shí)有其他事務(wù)在做插入,導(dǎo)致了數(shù)據(jù)在量上出現(xiàn)了變化。
2.事務(wù)的隔離級(jí)別
Spring支持的事務(wù)隔離級(jí)別與數(shù)據(jù)的隔離級(jí)別其實(shí)沒(méi)有任何區(qū)別都是四種,說(shuō)隔離級(jí)別必須要說(shuō)事務(wù)的四大特性原子性、一致性、隔離性、持久性。需要拿出來(lái)說(shuō)的便是隔離性,事務(wù)在支持隔離性時(shí)并不是將事務(wù)之間直接徹底隔離,而是給我們提供了幾個(gè)級(jí)別來(lái)劃分隔離的程度,也就是下面四種了。只有串行化才可以做到事務(wù)之間的完全隔離,而其他的隔離級(jí)別自然就會(huì)產(chǎn)生不同的問(wèn)題了,因?yàn)槭聞?wù)之間有交叉。
- 讀未提交:這是最低的隔離級(jí)別,相當(dāng)于事務(wù)之間的隔離性基本沒(méi)有,所以這種隔離級(jí)別什么問(wèn)題都解決不了,使用這種隔離級(jí)別會(huì)伴隨臟讀、不可重復(fù)讀、幻讀等問(wèn)題。
- 讀已提交:同一個(gè)事務(wù)里讀取的數(shù)據(jù)是其他事務(wù)里已經(jīng)提交的數(shù)據(jù),所以不會(huì)讀取到其他事務(wù)未提交的數(shù)據(jù),所有不會(huì)有臟讀的問(wèn)題,但是還是可能發(fā)生不可重復(fù)讀、幻讀的問(wèn)題。
- 重復(fù)讀:同一個(gè)事務(wù)里支持重復(fù)讀取,也就是同一個(gè)事務(wù)里前后讀取的某條數(shù)據(jù)肯定一致(只針對(duì)某一條數(shù)據(jù)而言),但是仍然解決不了幻讀的問(wèn)題,幻讀是因?yàn)椴l(fā)插入導(dǎo)致的。重復(fù)讀只能解決并發(fā)修改的問(wèn)題。
- 串行化:串行化可以解決隔離性產(chǎn)生的所有問(wèn)題,但是他的效率特別的低,所有任務(wù)都會(huì)排隊(duì)進(jìn)行處理,在并發(fā)系統(tǒng)中效率非常低下。
3.事務(wù)的傳播機(jī)制
事務(wù)的傳播機(jī)制是Spring特有的機(jī)制,各個(gè)數(shù)據(jù)庫(kù)是不支持的,那Spring的傳播機(jī)制是什么呢,他們有什么作用呢?
- PROPAGATION_REQUIRED(默認(rèn)):如果當(dāng)前方法沒(méi)有事務(wù),就創(chuàng)建一個(gè)新事務(wù);如果當(dāng)前方法已經(jīng)有事務(wù),就加入到當(dāng)前事務(wù)中。
- PROPAGATION_SUPPORTS:如果當(dāng)前方法有事務(wù),就加入到當(dāng)前事務(wù)中;如果當(dāng)前方法沒(méi)有事務(wù),就以非事務(wù)的方式執(zhí)行。
- PROPAGATION_MANDATORY:如果當(dāng)前方法有事務(wù),就加入到當(dāng)前事務(wù)中;如果當(dāng)前方法沒(méi)有事務(wù),就拋出異常。
- PROPAGATION_REQUIRES_NEW:無(wú)論當(dāng)前方法是否有事務(wù),都創(chuàng)建一個(gè)新事務(wù);如果當(dāng)前方法已經(jīng)有事務(wù),就掛起當(dāng)前事務(wù)。
- PROPAGATION_NOT_SUPPORTED:以非事務(wù)的方式執(zhí)行當(dāng)前方法;如果當(dāng)前方法有事務(wù),就掛起當(dāng)前事務(wù)。
- PROPAGATION_NEVER:以非事務(wù)的方式執(zhí)行當(dāng)前方法;如果當(dāng)前方法有事務(wù),就拋出異常。
- PROPAGATION_NESTED:在當(dāng)前事務(wù)中創(chuàng)建一個(gè)嵌套事務(wù);如果當(dāng)前方法沒(méi)有事務(wù),就相當(dāng)于PROPAGATION_REQUIRED。
二、Spring事務(wù)的實(shí)現(xiàn)方式
Spring提供了兩種事務(wù)的支持方式一種常用的聲明式事務(wù),所謂聲明式事務(wù)就是我們直接使用注解聲明即可,而無(wú)需手動(dòng)寫(xiě)事務(wù)的開(kāi)啟提交和回滾,這種事務(wù)的實(shí)現(xiàn)方式是AOP,AOP底層則是JDK的動(dòng)態(tài)代理和CGLIB的動(dòng)態(tài)代理。另一種支持的事務(wù)則是編程式事務(wù),這種實(shí)現(xiàn)方式則是直接編寫(xiě)事務(wù)代碼,底層通過(guò)ORM框架調(diào)用到數(shù)據(jù)庫(kù)實(shí)現(xiàn)的事務(wù),編程式事務(wù)具有更加靈活的特點(diǎn),同時(shí)Spring為我們提供了兩種編程式事務(wù)的實(shí)現(xiàn)方式,一種是TransactionTemplate,一種是PlatformTransactionManager。他們都能實(shí)現(xiàn)編程式事務(wù),不過(guò)使用TransactionTemplate無(wú)需我們手動(dòng)提交或者回滾,Spring會(huì)根據(jù)異常拋出與否進(jìn)行提交或者回滾。使用PlatformTransactionManager就需要我們自己提交或者回滾了。下面看下他們的實(shí)現(xiàn)區(qū)別吧
1.編程式事務(wù)
使用TransactionTemplate實(shí)現(xiàn)編程式事務(wù)
下面是使用TransactionTemplate的偽代碼,我們可以為T(mén)ransactionTemplate指明他的隔離級(jí)別和傳播機(jī)制,注意這里并沒(méi)有配置數(shù)據(jù)源相關(guān)操作,數(shù)據(jù)源仍需要單獨(dú)在配置文件中進(jìn)行聲明數(shù)據(jù)源的類型和驅(qū)動(dòng)類以及其他的數(shù)據(jù)庫(kù)訪問(wèn)的賬號(hào)路徑超時(shí)時(shí)間最大連接等信息。
@Component public class TestTransactionTemplate { private TransactionTemplate transactionTemplate; @Inject public void setTransactionTemplate(TransactionTemplate transactionTemplate) { this.transactionTemplate = transactionTemplate; transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ); transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); } public void transferMoney(final String fromAccount, final String toAccount, final double amount) { transactionTemplate.execute(new TransactionCallback<Void>() { public Void doInTransaction(TransactionStatus status) { try { // 執(zhí)行轉(zhuǎn)賬操作,將金額從fromAccount轉(zhuǎn)到toAccount // accountService.transfer(fromAccount, toAccount, amount); // 如果沒(méi)有發(fā)生異常,則提交事務(wù) return null; } catch (Exception ex) { // 如果發(fā)生異常,則回滾事務(wù) status.setRollbackOnly(); throw new RuntimeException(ex); } } }); } }
使用PlatformTransactionManager實(shí)現(xiàn)
使用PlatformTransactionManager則必須我們手動(dòng)進(jìn)行提交或者回滾,下面是他的偽代碼
@Component public class TestPlatformTransactionManager { PlatformTransactionManager transactionManager; @Inject public void setTransactionManager(PlatformTransactionManager transactionManager){ this.transactionManager = transactionManager; } public void transfer(String fromAccount, String toAccount, double amount) { DefaultTransactionDefinition txDef = new DefaultTransactionDefinition(); txDef.setIsolationLevel(TransactionDefinition.ISOLATION_REPEATABLE_READ); txDef.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); TransactionStatus txStatus = transactionManager.getTransaction(txDef); try { //執(zhí)行轉(zhuǎn)賬操作,將金額從fromAccount轉(zhuǎn)到toAccount // accountService.transfer(fromAccount, toAccount, amount); //如果沒(méi)有發(fā)生異常,則提交事務(wù) transactionManager.commit(txStatus); } catch (Exception ex) { //如果發(fā)生異常,則回滾事務(wù) transactionManager.rollback(txStatus); throw ex; } } }
2.聲明式事務(wù)
聲明式事務(wù)底層利用AOP的方式對(duì)我們的事務(wù)對(duì)象進(jìn)行代理,然后通過(guò)前后的增強(qiáng)操作實(shí)現(xiàn)了事務(wù)的管理,其實(shí)底層他們還是一樣的,使用聲明式事務(wù)的偽代碼如下,我們可以為注解聲明需要的各種屬性,常用的就是事務(wù)的傳播機(jī)制、隔離級(jí)別、超時(shí)時(shí)間、回滾異常、非回滾異常等。
@Component public class TestTransactional { @Transactional(propagation = Propagation.REQUIRES_NEW //設(shè)置傳播機(jī)制 ,isolation = Isolation.REPEATABLE_READ //設(shè)置隔離級(jí)別 ,readOnly = false //設(shè)置是否只讀 ,timeout = 30 //設(shè)置數(shù)據(jù)庫(kù)連接的超時(shí)時(shí)間 ,transactionManager = "defaultTransactionManager" //設(shè)置事務(wù)管理器,一個(gè)工程多個(gè)時(shí)可以使用該方式 ,rollbackFor = IllegalArgumentException.class // 指定回滾異常 ,noRollbackFor = IndexOutOfBoundsException.class) // 指定非回滾異常 public Boolean transfer(String fromAccount, String toAccount, double amount){ // 業(yè)務(wù)操作... return Boolean.TRUE; } }
三、Spring事務(wù)的本質(zhì)
Spring雖然提供了多種事務(wù)的實(shí)現(xiàn)方式,其實(shí)最底層都是有一樣的。他都必須依賴數(shù)據(jù)源來(lái)對(duì)數(shù)據(jù)庫(kù)進(jìn)行訪問(wèn),根據(jù)數(shù)據(jù)源來(lái)進(jìn)行不同的封裝就實(shí)現(xiàn)了Spring的不同的事務(wù)實(shí)現(xiàn)方式。編程式事務(wù)是直接獲取數(shù)據(jù)源后進(jìn)行手動(dòng)操作,我們使用的PlatformTransactionManager、或者TransactionTemplate都是需要利用數(shù)據(jù)源來(lái)進(jìn)行操作的。Spring通過(guò)數(shù)據(jù)源來(lái)和數(shù)據(jù)庫(kù)建立連接,開(kāi)啟連接后我們就可以為這個(gè)連接設(shè)置他的隔離級(jí)別和一些超時(shí)信息等。這樣就會(huì)建立起一個(gè)事務(wù)了,最底層利用的還是數(shù)據(jù)庫(kù)的事務(wù)的動(dòng)作。聲明式事務(wù)與編程式事務(wù)原理都是一致,只不過(guò)Spring通過(guò)AOP將我們的業(yè)務(wù)代碼進(jìn)行了代理,產(chǎn)生了一個(gè)代理對(duì)象,具體使用什么代理技術(shù)Spring會(huì)根據(jù)我們的實(shí)現(xiàn)類進(jìn)行選擇使用JDK還是CGLIB。產(chǎn)生的代理對(duì)象我們就可以在被Transactional注解修飾的方法的前后添加事務(wù)處理的相關(guān)代碼了,這個(gè)代碼和使用編程式事務(wù)的代碼區(qū)別不大。所以說(shuō)Spring事務(wù)的本質(zhì)其實(shí)還是利用數(shù)據(jù)源打開(kāi)和數(shù)據(jù)庫(kù)的連接,在連接上進(jìn)行事務(wù)的操作。Spring根據(jù)不同需要又封裝了不同的事務(wù)實(shí)現(xiàn),底層卻都是一致的。
四、Spring中事務(wù)常碰到的問(wèn)題
這里總結(jié)兩個(gè)常見(jiàn)的事務(wù)問(wèn)題事務(wù)的回滾和不回滾,以及事務(wù)嵌套的場(chǎng)景
1.事務(wù)回滾
在不聲明回滾異常類時(shí),只要被事務(wù)管理的方法發(fā)生異常,那么事務(wù)就是會(huì)回滾的。Spring根據(jù)拋出的異常來(lái)進(jìn)行事務(wù)回滾。如果我們對(duì)異常進(jìn)行了cath那Spring是無(wú)法進(jìn)行事務(wù)回滾的,因?yàn)闆](méi)有異常拋出了。如果想要回滾我們可以手動(dòng)聲明一個(gè)自定義異?;蛘咧付ǖ钠渌惓?。這樣就可以實(shí)現(xiàn)回滾。當(dāng)然即使拋出了異常也不一定會(huì)回滾。這個(gè)還需要依賴我們指定的異?;貪L類,一般可以為T(mén)ransactional指明noRollBackFor。通過(guò)他可以指明在哪些異常下不回滾。通過(guò)rollBackFor指明哪些異常下回滾,需要滿足回滾異常時(shí)才會(huì)去回滾。
那如果把異常catch了,又沒(méi)有拋出異常我們有沒(méi)有其他方式進(jìn)行回滾呢(使用聲明式事務(wù)時(shí))?其實(shí)還有一種方式進(jìn)行回滾,如下所示:
@Transactional public void someMethod() { if (condition) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
這種操作就是拿到當(dāng)前事務(wù)的狀態(tài)手動(dòng)更改為回滾狀態(tài),當(dāng)執(zhí)行到AOP的后置增強(qiáng)時(shí),就會(huì)調(diào)用回滾方法,從而達(dá)到了回滾的目的。
2.事務(wù)嵌套
有沒(méi)有思考過(guò)這個(gè)問(wèn)題:Spring的事務(wù)的傳播機(jī)制到底是做什么用的呢?
其實(shí)一般場(chǎng)景下Spring的事務(wù)傳播機(jī)制很少用得到,我們通常都是不顯示指定傳播機(jī)制的,而是使用默認(rèn)的PROPAGATION_REQUIRED。默認(rèn)的這個(gè)傳播機(jī)制就是有事務(wù)那我就使用你的事務(wù),如果沒(méi)有事務(wù)我就新建一個(gè)事務(wù)。假如有以下的場(chǎng)景存在,為了方便看就是用a、b命名方法了:
@Transactional public void a(){ //a方法被事務(wù)管理了,同時(shí)又調(diào)用了b這個(gè)事務(wù)方法 b(); } @Transactional public void b(){ ??????? }
在此時(shí)我們不為a、b兩個(gè)方法聲明傳播機(jī)制時(shí),那a、b兩個(gè)方法其實(shí)是共用一個(gè)事務(wù)的,因?yàn)樗麄兊氖聞?wù)傳播機(jī)制是PROPAGATION_REQUIRED。這個(gè)機(jī)制就是有事務(wù)就用已經(jīng)存在的,沒(méi)有則新建,很顯然a方法時(shí)開(kāi)啟了一個(gè)事務(wù),執(zhí)行b方法時(shí)既然事務(wù)以及存在,就使用了a的事務(wù)。所以a、b方法其實(shí)是共用事務(wù)的?;乜吹谝徊糠諷pring中事務(wù)的傳播機(jī)制其實(shí)有7種,其實(shí)這其中主要就是為了事務(wù)嵌套場(chǎng)景下使用的,也就是我們事務(wù)中又調(diào)用了事務(wù)的場(chǎng)景。此時(shí)我們就需要關(guān)注內(nèi)層事務(wù)到底需要做什么,需不需要和上層事務(wù)保持一致的動(dòng)作,如果不需要我們就可以選擇PROPAGATION_REQUIRED_NEW,這樣內(nèi)層事務(wù)就是一個(gè)全新的事務(wù)。此時(shí)Spring是通過(guò)數(shù)據(jù)源和數(shù)據(jù)庫(kù)新建立了一個(gè)連接,從而實(shí)現(xiàn)了新的事務(wù)開(kāi)啟。
此外在其中傳播機(jī)制中最后一種需要單獨(dú)說(shuō)下:PROPAGATION_NESTED,他是嵌套事務(wù)。這個(gè)才是真正為嵌套事務(wù)使用的傳播機(jī)制。上面的例子中有內(nèi)層事務(wù)和外層事務(wù)其實(shí)他的原理還是不同的事務(wù)。而PROPAGATION_NESTED嵌套事務(wù)的底層卻是使用的一個(gè)事務(wù)實(shí)現(xiàn)的嵌套事務(wù)。此時(shí)上面的代碼可以改造如下:
@Transactional(propagation = Propagation.REQUIRED) public void a(){ //a方法被事務(wù)管理了,同時(shí)又調(diào)用了b這個(gè)事務(wù)方法 b(); } @Transactional(propagation = Propagation.NESTED) public void b(){ }
此時(shí)b方法就是一個(gè)嵌套事務(wù)了,Spring的嵌套事務(wù)同樣底層是依賴于數(shù)據(jù)庫(kù)的嵌套事務(wù),在Mysql里支持了一種偽嵌套事務(wù),就是通過(guò)在一個(gè)事務(wù)中保存回滾點(diǎn)savepoint的方式來(lái)進(jìn)行事務(wù)嵌套。當(dāng)事務(wù)正常提交時(shí)都會(huì)提交,當(dāng)事務(wù)異常時(shí)我們可以指定事務(wù)回滾到指定的回滾點(diǎn),下面列舉一個(gè)Mysql的回滾例子:假設(shè)有一個(gè)員工表employees表,對(duì)他進(jìn)行了如下的操作:
START TRANSACTION; SAVEPOINT sp1; INSERT INTO employees (id, name, age) VALUES (1, 'Alice', 30); SAVEPOINT sp2; INSERT INTO employees (id, name, age) VALUES (3, 'Bob', 25); SAVEPOINT sp3; INSERT INTO employees (id, name, age) VALUES (4, 'Charlie', 27); SAVEPOINT sp4; INSERT INTO employees (id, name, age) VALUES (5, 'Dave', 29); ROLLBACK TO sp3; COMMIT;
上面的例子我們創(chuàng)建了4個(gè)回滾點(diǎn),且我們最后是回滾到了sp3這個(gè)savepoint,那就意味著sp3之后的所有操作不會(huì)被寫(xiě)入數(shù)據(jù)庫(kù),而sp3之前的所有操作還是會(huì)正常入庫(kù),這樣就實(shí)現(xiàn)了事務(wù)嵌套場(chǎng)景下的部分回滾機(jī)制。Spring事務(wù)傳播機(jī)制中的PROPAGATION_NESTED底層正是利用了Mysql的這一功能進(jìn)行了事務(wù)嵌套場(chǎng)景下的部分回滾。
五、總結(jié)
這篇先介紹了事務(wù)的基礎(chǔ)知識(shí),然后總結(jié)了Spring事務(wù)的支持方式,分析了他們的原理,最后總結(jié)下來(lái)就會(huì)發(fā)現(xiàn)Spring的事務(wù)其實(shí)全部都是依賴于數(shù)據(jù)源對(duì)數(shù)據(jù)庫(kù)的事務(wù)操作,若是數(shù)據(jù)庫(kù)事務(wù)不支持的動(dòng)作,Spring也是不支持的。Spring事務(wù)的支持動(dòng)作都是依賴于底層數(shù)據(jù)庫(kù)事務(wù)的封裝,包括了嵌套事務(wù)的場(chǎng)景,希望這一篇的總結(jié)可以幫助到路過(guò)的朋友。
到此這篇關(guān)于一文詳解Spring事務(wù)的實(shí)現(xiàn)與本質(zhì)的文章就介紹到這了,更多相關(guān)Spring事務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解Intellij IDEA的Facets和Artifacts
這篇文章主要介紹了Intellij IDEA的Facets和Artifacts的相關(guān)知識(shí),本文通過(guò)實(shí)例給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2020-09-09java 簡(jiǎn)單的計(jì)算器程序?qū)嵗a
這篇文章主要介紹了java 簡(jiǎn)單的計(jì)算器程序?qū)嵗a的相關(guān)資料,需要的朋友可以參考下2017-06-06從繁瑣到簡(jiǎn)潔的Jenkins?Pipeline腳本優(yōu)化實(shí)踐
這篇文章主要為大家介紹了從繁瑣到簡(jiǎn)潔的Jenkins?Pipeline腳本優(yōu)化實(shí)踐示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12詳解如何在spring boot中使用spring security防止CSRF攻擊
這篇文章主要介紹了詳解如何在spring boot中使用spring security防止CSRF攻擊,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-05-05使用CI/CD工具Github Action發(fā)布jar到Maven中央倉(cāng)庫(kù)的詳細(xì)介紹
今天通過(guò)對(duì)Github Action的簡(jiǎn)單使用來(lái)介紹了CI/CD的作用,這個(gè)技術(shù)體系是項(xiàng)目集成交付的趨勢(shì),也是面試中的一個(gè)亮點(diǎn)技能。 而且這種方式可以實(shí)現(xiàn)“一次配置,隨時(shí)隨地集成部署”,感興趣的朋友一起看看吧2021-07-07