spring多線程資料庫
1. spring的申明事務和多線程的關系
1、會話可以創建多個事務
比如:使用客端連接資料庫,這樣你就可以執行很多個事務了
2、一個事務只能由一個會話產生
在資料庫里的事務,如果在執行的sql都是由會話發起的,哪怕是自動執行的JOB也是由系統會話發起的
3、一個事務可能會產生一個或多個線程
比如RMAN備份,是可以創建多個線程可加快備份速度
4、一個線程在同一時間內只能執行一個事務
而一個線程,在沒結束當前事務是無法釋放資源來執行第二個事務敏宴碧
事務、會話與線程的關系和區別
我一直沒弄明白資料庫中的這三個概念之間的關系。
事務:簡單理解局勢一個業務需求的最小處理單位。
如:從A銀行卡轉賬500元到B銀行卡,事務就包括兩部分,1、從A卡減掉500元 2、從B卡加上500元
這兩個部分只要一個部分出錯,就要橋舉整體「回滾」,那這就是一個事務
會話:可以包含N個事務
如:你登陸網銀之後,可以重復轉賬祥銀步驟2次,第二次轉賬失敗,並不影響你第一次轉賬成功。
線程:一個事情,一個人乾和多個人乾的問題
如:比如植樹,任務是植樹500棵,一個人(線程)干5天,那五個人(線程)干1天。
至於會話和線程的關系,個人理解,植樹任務就是一個session
一個會話中可以由多個事務。
線程是操作系統概念。
2. 一篇讓你學會 11個Spring 失效場景
其實關於spring事務失效的場景,網路上文章介紹的不少,參差不齊。這里只分享下自己的見解,時長大概10分鍾左右,先上個圖介紹下。
事務方法需要定義public,非public方法事務會失效。事務攔截器TransactionalInterceptor會在執行方法前進行攔截,通過動態代理方式如果是cglib就是intercept方法或者jdk的invoke方法間接調用類的getTransactionAttribute方法獲取配置信息,附上源碼圖:
進一步的跟蹤getTransactionAttribute方法,我們就能看到,spring對於非public修飾的方式,返回的事務對象是null,其中allowPublicMethodsOnly返回的是一個布爾false。
事務底層使用了aop,那麼也就是說通過jdk或者是cglib生成代理類,在代理類中實現的事務的功能,如果說方法是final修飾的了,那麼就會導致代理類中無法重寫該方法,從而導致添加事務失敗。同樣的如果是static的修飾的話也是無法通過動態代理變成事務方法。
簡單來說就是一個方法內部調用另一個方法,但是另一個方式是有事務的,這樣也會導致事務失效,因為這個調用的是this對象的方法,而不是另一個方法持有的對象,可以這里理解。
如果想要在方法內部調用另一個方法也有事務的話,就需要新建一個service對象持有。
這樣,通過新建一個service方法,將事務添加到新建的service方法里就可以了。說到這里可能小夥伴覺得這樣有點麻煩,那麼是否有沒有其他的方式不新建一個方法呢,答案是可以的,就是注入自己,利用了spring ioc內部的三級緩存的機制,這里注入自己就很好的保證了也不會出現循環依賴:
其實到了這一步,還是發現有點不太雅觀,並不是說上面代碼有什麼問題只是覺得,可以讓上面代碼更加好看一點,那麼有沒有呢,答案是有的,是什麼呢?這就不得不佩服spring強大完善的支持,那就是AopContext.currentProxy(),這個就是創建代理類,在方法調·調用前後切入,這個代理類對象是保存在ThreadLocal中的,所以通過這個代理類對象調用事務方法就能生效了。
這樣看來,代碼是不是就優雅多了,哈哈!!!
這里需要明確一個前提,就是使用spring事務的前提,就是對象要被spring管理就需要創建bean實例,在開發中,我們都是通過@Controller,@Service,@Component,@Repository等註解自動的實現依賴注入實例化的功能,但假如說在相應的控制層,業務層,數據層忘記加相應的註解,那麼也是會失效的。因為沒有交給spring管理,例如:
回想起前幾年配置事務管理器時,都會有這樣的一段配置:
通過這一段配置也可以知道,其實spring事務就是通過資料庫連接事務多線程連接會導致持有的connetion不是同一個,從網上找了一張圖,通過這張圖進一步理解:
接著附上偽代碼結合上面的圖進一步理解:
事務add方法中調用了另一個事務doOtherThing,但是事務方法是在另一個線程中調用的,這樣就會導致兩個方法不在同一個線程中,獲取到的資料庫鏈接不一樣,是兩個不同的事務,一旦doOtherThing發生異常,add方法也是不可能發生回滾的.這里需要解釋以下什麼是同一個事務,也就是說只有擁有同一個資料庫連接才能同時提交和回滾。如果在不同的線程,拿到的資料庫連接肯定是不一樣的,所以是不同的事務。
這個就沒什麼好講的了,也就是innodb和myisam引擎的不同,5版本以前默認是myisam引擎,這個引擎是不支持事務的,5版本以後的innodb是支持事務的。
這個其實可能也是比較容易忽略的,因為我們印象里好像沒怎麼配置過怎麼開啟事務,也確實是這樣哈,為什麼?其實原因很簡單,springboot項目通過這個類已經默默的為我們開啟了事務。
這個類會載入spring.datasource這個配置文件從而啟動事務,如果是非springboot項目就需要自己手動在xml文件中配置事務管理器。
類似這樣的從而開啟事務。
在使用@Transactional註解時,是可以指定propagation參數的,該參數是用來指定事務的傳播特性,其中只有required,requires_new,nested這三種才會創建新事務:
像上面的Propagation.NEVER這種類型的傳播特性不支持事務,如果有事務則會拋異常。
像這種手動try...catch了異常,又沒有手動拋出,那麼sring就會認為程序是異常的就不會回滾了。
捕獲了異常又拋出了exception異常,事務同樣不會回滾,因為spring事務默認情況下只會回滾RuntimeException(運行時異常)和Error(錯誤),對於普通的Exception(非運行時異常),不會回滾,網上找了一張圖:
這里exception里除了分為運行時異常和非運行時異常(ioException)。
1) 讓checked例外也回滾:
在整個方法前加上 @Transactional(rollbackFor=Exception.class)
2) 讓unchecked例外不回滾:
@Transactional(notRollbackFor=RunTimeException.class)
3)不需要事務管理的(只查詢的)方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)
這里需要提及的一句是,如果是自定義了的異常,比如說我自定義了DALException異常,那麼就應該是@Transactional(notRollbackFor=DALException.class),一旦拋出的異常不屬於DALException異常,那麼事務也是不會生效的。
其實這個就有點像是js里的冒泡事件,可能我只是需要底部,結果外層窗口事件也觸發了,聯想到事務這里,那麼也是一樣的,嵌套多個可能只是想回滾對應的事務,就不用把其他事務也回滾了,這個可以通過try...catch來處理,將需要處理回滾的事務放這裡面就不會把外層的也會滾了。
3. 平時在spring框架中如何使用多線程
Spring aop 依賴注入的就是單例對象,我們在使用的時候如果有多個相同處理且不怎麼耗時的情況下一般會採用for循環直接執行,這樣的情況下即使有事務管理也不會影響業務執行下去;但是,大多數的應用比如:系統與系統之間的交互就比較耗時了,此時使用for循環執行業務就不可取了,於行悶是我檔扮彎們想用線程來解決這個問題。
另外,附帶說明一下:如果是在Spring提供的@Test下測試:如果有多線程處理,需要調用Thread.sleep(3000);實現線程等待,這也是測試多線程模式下所必須的缺清。如果不加線程休眠,不等待多線程執行完畢,很有可能測不出來多線程調用的處理。
4. spring事務失效的幾種場景以及原因
spring事務失效場景可能大家在很多文章都看過了,所以今天就水一篇,看大家能不能收獲一些不一樣的東西。直接進入主題
失效原因: spring事務生效的前提是,service必須是一個bean對象
解決方案: 將service注入spring
失效原因: spring默認只會回滾非檢查異常和error異常
解決方案: 配置rollbackFor
失效原因: spring事務只有捕捉到了業務拋出去的異常,才能進行後續的處理,如果業務自己捕獲了異常,則事務無法感知
解決方案:
1、將異常原樣拋出;
2、設置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
失效原因: spring事務切面的優先順序順序最低,但如果自定義的切面優先順序和他一樣,且自定義的切面沒有正確處理異常,則會同業務自己捕獲異常的那種場景一樣
解決鄭含方案:
1、在切面中將異常原樣拋出;
2、在切面中設置TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
失效原因: spring事務默認生效的方法許可權都必須為public
解決方案:
1、將方法改為public;
2、修改TansactionAttributeSource,將publicMethodsOnly改為false【這個從源碼跟蹤得出結論】
3、開啟 AspectJ 代理模式【從spring文檔得出結論】
具體步驟:
1、在pom引入aspectjrt坐標以及相應插件
2、在啟動類上加上如下配置
註: 如果是在idea上運行,則需做如下配置
4、直接用TransactionTemplate
示例:
失效原因: 子容器掃描范圍過大,將未加事務配置的serivce掃描進來
解決方案:
1、父子容器個掃個的范圍;
2、不用父子容器,所有bean都交給同一容器管理
註: 因為示例是使用springboot,而springboot啟動默認沒有父子容器,只有一個容器,因此就該場景就演示示例了
失效原因: 因為spring事務畝轎是用動態代理實現,因此如果方法使用了final修飾,則代理類無法對目標方法進行重寫,植入事務功能
解決方案:
1、方法不要用final修飾
失效原因: 原因和final一樣
解決方案:
1、方法不要用static修飾
失效原因: 本類方法不經過代理,無法進行增強
解決方案:
1、注入自己來調用;
2、使用@EnableAspectJAutoProxy(exposeProxy = true) + AopContext.currentProxy()
失效原因: 因為spring的事務是通過資料庫連接來實現,而資料庫連接spring是放在threadLocal裡面。同一個事務,只能用同一個資料庫連接。而多線程場景下,拿到的資料庫連接是不一樣的,即是屬於不同事務
失效原因: 使用的傳播特性不支持事迅叢肆務
失效原因: 使用了不支持事務的存儲引擎。比如mysql中的MyISAM
註: 因為springboot,他默認已經開啟事務管理器。org.springframework.boot.autoconfigure.jdbc.。因此示例略過
失效原因: 當代理類的實例化早於AbstractAutoProxyCreator後置處理器,就無法被AbstractAutoProxyCreator後置處理器增強
本文列舉了14種spring事務失效的場景,其實這14種裡面有很多都是歸根結底都是屬於同一類問題引起,比如因為動態代理原因、方法限定符原因、異常類型原因等
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-transaction-invalid-case
5. spring如何解決多線程的數據操作
看你的資料庫的隔離級別,還有orm組件的實現
6. spring如何保證並發的同時保證事務
ThreadLocal來處理。
ThreadLocal天生為解決相同變數的訪問沖突問題,所以這個對於spring的默認單例bean的多線程訪問是一個完美的解決方案。spring也確實是用了ThreadLocal來處理多線程下相同變數並發的線程安全問題。
要想實現jdbc事務,就必須是在同一個連接對象中此正嫌操作,多個連接下事務就會不可控,需要藉助分布式事務完成。那spring如何保證資料庫清扒事務在同一森手個連接下執行的呢。
7. 「Spring」事務失效的場景
1.資料庫引擎不支持事務
Spring 事務生效的前提是所連接的資料庫要支持事務,如果底層的資料庫引擎都不支持事務,則Spring的事務肯定會失效。
例如: MySQL 用的不是 InnoDB 引擎,而是用的 MyISAM 存儲引擎。
2.事務方法未被 Spring 容器管理
如果事務方法所在的類沒有載入到 Spring IOC 容器中,也就是說,事務方法所在的類沒有被 Spring 容器管理,則Spring事務會失效。
例如:你的方法所在類沒有加@Component或者@Service註解。
3.方法沒有被 public 修飾
如果事務所在的方法沒有被 public 修飾,此時 Spring 的事務也會失效。
4.同一類中方法之間直接的調用
例如:如果同一個類中有兩個歲畢方法分別為 A 和 B,方法 A 沒有添加事務註解,而方法 B 添加了 @Transactional 事務註解,此時方法 A 直接調用方法 B,則方法 B 的事務會失效。
5.未配置事務管理器
如果在項目中沒有配置 Spring 的事務管理器,即使使用了 Spring 的事務管理功能,Spring 的事務也不會生效。
例如:對於 SpringBoot 項目來說,導入了 mybatis 的 starter 依賴後,SpringBoot 會自動注入DataSourceTransactionManager 事務管理器,這樣我們就可以直接用 @Transactional 註解使用事務了。
6.事枯行務傳播類型不支持事務
如果方法的事務傳播類型為不支持事務沒雀嘩的傳播類型,則該方法的事務在 Spring 中會失效。
例如: A 方法的事務傳播類型為 NOT_SUPPORTED,不支持事務,此時用帶事務的方法 B 去調用 A 方法,則 A 方法的事務失效。
7.進行異常捕捉卻沒有拋出
比如對某一個新增數據代碼段進行 try catch 異常,而 catch 里沒有向外拋出異常,此時 spring 事務無法回滾。
8.錯誤的標注異常類型
如果在 @Transactional 註解中標注的異常類型不是我們拋出的異常類型,則Spring事務的回滾會失效。
例如: Spring 中默認回滾的異常類型為 RuntimeException,如果此時你拋出的異常是 Exception,那麼Spring 事務中無法捕獲到 Exception 異常,則事務回滾會失效。
9.開啟多線程
開啟一個線程去執行資料庫操作,多線程內的方法將不被 spring 事務控制。
例如:一個帶事務的方法 A 中開啟線程去執行同類中的一個 insert 方法,即使這個操作失敗了,也不會回滾 A 中的其他資料庫操作。
注意:如果把 insert 方法提出到一個新的類中,加入事務註解,就能成功的把 insert 方法加入到 spring 事務管理中。但是使用多線程事務的情況下,如果想進行回滾,比較麻煩,因為我們感知不到線程中方法執行的異常。
8. Spring 單例 多例 線程安全等問題,想請教大家
Spring作為一個IOC/DI容器,幫助我們管理了許許多多的「bean」。但其實,Spring並沒有確保這些對象的線程安全,需要由開發者自己編寫解決線程安全問題的代碼。
Spring對每個bean提供了一個scope屬性來表示該bean的作用域。它是bean的生命周期。
我們知道在一般情況下,只有無狀態的Bean才可以在多線程環境下共享,在Spring中,絕大部分Bean都可以聲明為singleton作用域。就是因為Spring對一些Bean(如RequestContextHolder、、LocaleContextHolder等)中非線程安全狀態採用ThreadLocal進行處沖李理,讓它們也成為線程安全的狀態,因為有狀態的Bean就可以在多線程中共享了。
一般的Web應用劃分為展現層、服務層和持久層三個層次,在不同的層中編寫對應的邏輯,下層通過介面向上層開放功能調用。在一般情況下,從接收請求到返回響應所經過的所有程序調用都同屬於一個線程。
ThreadLocal是解決線程安全問題一個很好的思路,ThreadLocal是一個為線程提供線程局部變數的工具類。它的思想也十分簡單,就是為線程提供一個線程私有的變數副本,這樣多個線程都可以隨意更改自己線程局部的變數,不會影響到其他線程。不過需要注意的是,ThreadLocal提供的只是一個淺拷貝,如果變數是一個引用類型,那麼就要考慮它內部的狀態是否會被改變,想要解決這個問題可以通過重寫ThreadLocal的initialValue()函數來自己實現深拷貝,建議在使用ThreadLocal時一開始就重寫該函數。
ThreadLocal通過為每個線程提供一個獨立的變數副本解決了變數並發訪問的沖突辯談問題。在很多情況下,ThreadLocal比直接使用synchronized同步機制解決線程安全問題更簡單,更方便,且結果程序擁有更高的並發性。
如果你的代碼所在的進程中有多個線程在同時運行,而這些線程可能會同時運行這段代碼。如果每次運行結果和單線程運行的結果是一樣的,而且其他的變數的值也和預期的是一樣的,就是線程安全的。
或者說:一個類或者攜判碰程序所提供的介面對於線程來說是原子操作或者多個線程之間的切換不會導致該介面的執行結果存在二義性,也就是說我們不用考慮同步的問題。
9. 如何實現springMVC的多線程並發
實現springMVC的多線程並發:
1、ThreadLocal為解決多線程程序的並發問題提供了一種新的思路
2、對於多線程資源共享的問題,同步機制採用了橘桐「以時間換空間」的方式,而ThreadLocal採用了「以空間換時間」的方式。前者僅提供一份變數,讓不同的線程排隊訪問,而後者為每一個線程都提供了一份變數,因此可以同時訪問而互不影響。