@Transation 注解是我們在開發(fā)中常用的注解,但是我們大多數(shù)只知道配置上就可以開啟事務(wù),很多人比如我,都不知道spring的事務(wù)原理,比如事務(wù)的嵌套生效情況 ,事務(wù)什么時候會失效,spring的事務(wù)又和mysql數(shù)據(jù)庫的事務(wù)有何聯(lián)系,其實這些都是面試中的高頻考點。
下面進行逐步分析:
Spring中七種事務(wù)傳播行為
事務(wù)傳播行為類型說明
PROPAGATION_REQUIRED如果當(dāng)前沒有事務(wù),就新建一個事務(wù),如果已經(jīng)存在一個事務(wù)中,加入到這個事務(wù)中。這是最常見的選擇。是默認的傳播機制;
PROPAGATION_SUPPORTS支持當(dāng)前事務(wù),如果當(dāng)前沒有事務(wù),就以非事務(wù)方式執(zhí)行。
PROPAGATION_MANDATORY使用當(dāng)前的事務(wù),如果當(dāng)前沒有事務(wù),就拋出異常。
PROPAGATION_REQUIRES_NEW新建事務(wù),如果當(dāng)前存在事務(wù),把當(dāng)前事務(wù)掛起。
PROPAGATION_NOT_SUPPORTED以非事務(wù)方式執(zhí)行操作,如果當(dāng)前存在事務(wù),就把當(dāng)前事務(wù)掛起。
PROPAGATION_NEVER以非事務(wù)方式執(zhí)行,如果當(dāng)前存在事務(wù),則拋出異常。
PROPAGATION_NESTED如果當(dāng)前存在事務(wù),則在嵌套事務(wù)內(nèi)執(zhí)行。如果當(dāng)前沒有事務(wù),則執(zhí)行與PROPAGATION_REQUIRED類似的操作。
Spring事務(wù)嵌套詳解:
1.PROPAGATION_REQUIRED
User1Service方法:
@Servicepublic class User1ServiceImpl implements User1Service {
? ? //省略其他...? ? @Override? ? @Transactional(propagation = Propagation.REQUIRED)
? ? public void addRequired(User1 user){
? ? ? ? user1Mapper.insert(user);
? ? }
}
User2Service方法:
@Servicepublic class User2ServiceImpl implements User2Service {
? ? //省略其他...? ? @Override? ? @Transactional(propagation = Propagation.REQUIRED)
? ? public void addRequired(User2 user){
? ? ? ? user2Mapper.insert(user);
? ? }
}
驗證方法1:
? ? @Override? ? public void notransaction_exception_required_required(){
? ? ? ? User1 user1=new User1();
? ? ? ? user1.setName("張三");
? ? ? ? user1Service.addRequired(user1);
? ? ? ? User2 user2=new User2();
? ? ? ? user2.setName("李四");
? ? ? ? user2Service.addRequired(user2);
? ? ? ? throw new RuntimeException();
? ? }
結(jié)果:插入了張三 李四 數(shù)據(jù) 外圍方法未開啟事務(wù),插入“張三”、“李四”方法在自己的事務(wù)中獨立運行,外圍方法異常不影響內(nèi)部插入“張三”、“李四”方法獨立的事務(wù)。
驗證方法2:
? @Override? ? @Transactional(propagation = Propagation.REQUIRED)
? ? public void transaction_exception_required_required(){
? ? ? ? User1 user1=new User1();
? ? ? ? user1.setName("張三");
? ? ? ? user1Service.addRequired(user1);
? ? ? ? User2 user2=new User2();
? ? ? ? user2.setName("李四");
? ? ? ? user2Service.addRequired(user2);
? ? ? ? throw new RuntimeException();
? ? }
結(jié)果:都沒有插入 張三 李四 數(shù)據(jù)? 外圍方法開啟事務(wù),內(nèi)部方法加入外圍方法事務(wù),外圍方法回滾,內(nèi)部方法也要回滾。
1. 在外圍方法未開啟事務(wù)的情況下Propagation.REQUIRED修飾的內(nèi)部方法會新開啟自己的事務(wù),且開啟的事務(wù)相互獨立,互不干擾。
2.外圍方法開啟事務(wù)的情況下Propagation.REQUIRED修飾的內(nèi)部方法會加入到外圍方法的事務(wù)中,所有Propagation.REQUIRED修飾的內(nèi)部方法和外圍方法均屬于同一事務(wù),只要一個方法回滾,整個事務(wù)均回滾。
2.PROPAGATION_REQUIRES_NEW
我們?yōu)閁ser1Service和User2Service相應(yīng)方法加上Propagation.REQUIRES_NEW屬性。
User1Service方法:
@Servicepublic class User1ServiceImpl implements User1Service {
? ? //省略其他...? ? @Override? ? @Transactional(propagation = Propagation.REQUIRES_NEW)
? ? public void addRequiresNew(User1 user){
? ? ? ? user1Mapper.insert(user);
? ? }
? ? @Override? ? @Transactional(propagation = Propagation.REQUIRED)
? ? public void addRequired(User1 user){
? ? ? ? user1Mapper.insert(user);
? ? }
}
User2Service方法:
@Servicepublic class User2ServiceImpl implements User2Service {
? ? //省略其他...? ? @Override? ? @Transactional(propagation = Propagation.REQUIRES_NEW)
? ? public void addRequiresNew(User2 user){
? ? ? ? user2Mapper.insert(user);
? ? }
? ? @Override? ? @Transactional(propagation = Propagation.REQUIRES_NEW)
? ? public void addRequiresNewException(User2 user){
? ? ? ? user2Mapper.insert(user);
? ? ? ? throw new RuntimeException();
? ? }
}
外圍方法沒有開啟事務(wù)。
驗證方法1:
? ? @Override? ? public void notransaction_exception_requiresNew_requiresNew(){
? ? ? ? User1 user1=new User1();
? ? ? ? user1.setName("張三");
? ? ? ? user1Service.addRequiresNew(user1);
? ? ? ? User2 user2=new User2();
? ? ? ? user2.setName("李四");
? ? ? ? user2Service.addRequiresNew(user2);
? ? ? ? throw new RuntimeException();
? ? }
結(jié)果:張三 李四都插入 。外圍方法沒有事務(wù),插入“張三”、“李四”方法都在自己的事務(wù)中獨立運行,外圍方法拋出異?;貪L不會影響內(nèi)部方法。
驗證方法2:
? ? @Override? ? @Transactional(propagation = Propagation.REQUIRED)
? ? public void transaction_exception_required_requiresNew_requiresNew(){
? ? ? ? User1 user1=new User1();
? ? ? ? user1.setName("張三");
? ? ? ? user1Service.addRequired(user1);
? ? ? ? User2 user2=new User2();
? ? ? ? user2.setName("李四");
? ? ? ? user2Service.addRequiresNew(user2);
? ? ? ? User2 user3=new User2();
? ? ? ? user3.setName("王五");
? ? ? ? user2Service.addRequiresNew(user3);
? ? ? ? throw new RuntimeException();
? ? }
結(jié)果:張三 未插入 李四 王五都插入 。外圍方法沒有事務(wù),插入“張三”、“李四”方法都在自己的事務(wù)中獨立運行,外圍方法拋出異?;貪L不會影響內(nèi)部方法。
1.在外圍方法未開啟事務(wù)的情況下Propagation.REQUIRES_NEW修飾的內(nèi)部方法會新開啟自己的事務(wù),且開啟的事務(wù)相互獨立,互不干擾。
2.在外圍方法開啟事務(wù)的情況下Propagation.REQUIRES_NEW修飾的內(nèi)部方法依然會單獨開啟獨立事務(wù),且與外部方法事務(wù)也獨立,內(nèi)部方法之間、內(nèi)部方法和外部方法事務(wù)均相互獨立,互不干擾
3.PROPAGATION_NESTED
我們?yōu)閁ser1Service和User2Service相應(yīng)方法加上Propagation.NESTED屬性。
User1Service方法:
@Servicepublic class User1ServiceImpl implements User1Service {
? ? //省略其他...? ? @Override? ? @Transactional(propagation = Propagation.NESTED)
? ? public void addNested(User1 user){
? ? ? ? user1Mapper.insert(user);
? ? }
}
User2Service方法:
@Servicepublic class User2ServiceImpl implements User2Service {
? ? //省略其他...? ? @Override? ? @Transactional(propagation = Propagation.NESTED)
? ? public void addNested(User2 user){
? ? ? ? user2Mapper.insert(user);
? ? }
? ? @Override? ? @Transactional(propagation = Propagation.NESTED)
? ? public void addNestedException(User2 user){
? ? ? ? user2Mapper.insert(user);
? ? ? ? throw new RuntimeException();
? ? }
}
此場景外圍方法沒有開啟事務(wù)。
驗證方法1:
? ? @Override? ? public void notransaction_exception_nested_nested(){
? ? ? ? User1 user1=new User1();
? ? ? ? user1.setName("張三");
? ? ? ? user1Service.addNested(user1);
? ? ? ? User2 user2=new User2();
? ? ? ? user2.setName("李四");
? ? ? ? user2Service.addNested(user2);
? ? ? ? throw new RuntimeException();
? ? }
結(jié)果:張三 李四都插入。外圍方法未開啟事務(wù),插入“張三”、“李四”方法在自己的事務(wù)中獨立運行,外圍方法異常不影響內(nèi)部插入“張三”、“李四”方法獨立的事務(wù)。
驗證方法2:
? ? @Transactional? ? @Override? ? public void transaction_exception_nested_nested(){
? ? ? ? User1 user1=new User1();
? ? ? ? user1.setName("張三");
? ? ? ? user1Service.addNested(user1);
? ? ? ? User2 user2=new User2();
? ? ? ? user2.setName("李四");
? ? ? ? user2Service.addNested(user2);
? ? ? ? throw new RuntimeException();
? ? }
結(jié)果:張三 李四都沒有插入。外圍方法開啟事務(wù),內(nèi)部事務(wù)為外圍事務(wù)的子事務(wù),外圍方法回滾,內(nèi)部方法也要回滾。
驗證方法3:
? ? @Transactional? ? @Override? ? public void transaction_nested_nested_exception_try(){
? ? ? ? User1 user1=new User1();
? ? ? ? user1.setName("張三");
? ? ? ? user1Service.addNested(user1);
? ? ? ? User2 user2=new User2();
? ? ? ? user2.setName("李四");
? ? ? ? try {
? ? ? ? ? ? user2Service.addNestedException(user2);
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? System.out.println("方法回滾");
? ? ? ? }
? ? }
結(jié)果:張三 插入 ,李四沒有插入? 這里李四拋出的異常被捕獲,不影響張三事務(wù)提交。外圍方法開啟事務(wù),內(nèi)部事務(wù)為外圍事務(wù)的子事務(wù),插入“李四”內(nèi)部方法拋出異常,可以單獨對子事務(wù)回滾。
1.在外圍方法未開啟事務(wù)的情況下Propagation.NESTED和Propagation.REQUIRED作用相同,修飾的內(nèi)部方法都會新開啟自己的事務(wù),且開啟的事務(wù)相互獨立,互不干擾。
2.在外圍方法開啟事務(wù)的情況下Propagation.NESTED修飾的內(nèi)部方法屬于外部事務(wù)的子事務(wù),外圍主事務(wù)回滾,子事務(wù)一定回滾,而內(nèi)部子事務(wù)可以單獨回滾而不影響外圍主事務(wù)和其他子事務(wù)
我們在實際工作中如何正確應(yīng)用spring事務(wù)呢?我來舉一個示例:
假設(shè)我們有一個注冊的方法,方法中調(diào)用添加積分的方法,如果我們希望添加積分不會影響注冊流程(即添加積分執(zhí)行失敗回滾不能使注冊方法也回滾),
而且在addPoint()中還要用一個addRecord()方法,這個方法用來記錄日志。
做以下設(shè)計:
會員注冊實現(xiàn):
@Service? public class UserServiceImpl implements UserService {
????@Transactional? ? ? ? public void register(User user){
? ? ? ? ? ? try {
?????????????????membershipPointService.addPoint(Point point);
? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? //省略...? ? ? ? ? ? }
? ? ? ? ? ? //省略...? ? ? ? }
? ? ? ? //省略...? }
注冊失敗要影響addPoint()方法(注冊方法回滾添加積分方法也需要回滾)
@Service? public class MembershipPointServiceImpl implements MembershipPointService{
????@Transactional(propagation = Propagation.NESTED)
? ? ? ? public void addPoint(Point point){
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? recordService.addRecord(Record record);
? ? ? ? ? ? } catch (Exception e) {
? ? ? ? ? ? ? //省略...? ? ? ? ? ? }
? ? ? ? ? ? //省略...? ? ? ? }
? ? ? ? //省略...? }
對于日志無所謂精確,可以多一條也可以少一條,所以addRecord()方法本身和外圍addPoint()方法拋出異常都不會使addRecord()方法回滾,并且addRecord()方法拋出異常也不會影響外圍addPoint()方法的執(zhí)行。
? @Service? public class RecordServiceImpl implements RecordService{
? ? ? ? @Transactional(propagation = Propagation.NOT_SUPPORTED)
? ? ? ? public void addRecord(Record record){
?????????????//省略...? ? ? ? }
? ? ? ? //省略...? }
Spring事務(wù)失效條件:
1.@Transactional 注解只能應(yīng)用到 public 可見度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不會報錯,事務(wù)也會失效。
2. Spring團隊建議在具體的類(或類的方法)上使用 @Transactional 注解,而不要使用在類所要實現(xiàn)的任何接口上。在接口上使用 @Transactional 注解,只能當(dāng)你設(shè)置了基于接口的代理時它才生效。因為注解是不能繼承 的,這就意味著如果正在使用基于類的代理時,那么事務(wù)的設(shè)置將不能被基于類的代理所識別,而且對象也將不會被事務(wù)代理所包裝。
3.數(shù)據(jù)庫不知道事務(wù)(一般不會有)
4.在業(yè)務(wù)代碼中,有如下兩種情況,比如:
throw new RuntimeException(“xxxxxxxxxxxx”); 事務(wù)回滾
throw new Exception(“xxxxxxxxxxxx”); 事務(wù)沒有回滾
一般不需要在業(yè)務(wù)方法中catch異常,如果非要catch,在做完你想做的工作后(比如關(guān)閉文件等)一定要拋出runtime exception,否則spring會將你的操作commit,這樣就會產(chǎn)生臟數(shù)據(jù)
5.異常類型是否配置正確
默認只支持 RuntimeException和Error ,不支持檢查異常
@Transactional(rollbackFor=Exception.class)? 顯示寫了遇到Exception回滾,拋出的是RuntimeException 會造成事務(wù)失效
Spring事務(wù)與數(shù)據(jù)庫事務(wù)的關(guān)系:
對數(shù)據(jù)庫來說隔離級別只有4種,
#讀未提交set tx_isolation='read-uncommitted';? #讀已提交set tx_isolation='read-committed';? #為可重復(fù)讀set tx_isolation='REPEATABLE-READ';? #為串行化set tx_isolation='SERIALIZABLE';
Spring有5種隔離級別,7種傳播行為,Spring多了一個DEFAULT 這是一個PlatfromTransactionManager默認的隔離級別。
spring的事務(wù)是對數(shù)據(jù)庫的事務(wù)的封裝,最后本質(zhì)的實現(xiàn)還是在數(shù)據(jù)庫,假如數(shù)據(jù)庫不支持事務(wù)的話,spring的事務(wù)是沒有作用的
數(shù)據(jù)庫的事務(wù)說簡單就只有開啟,回滾和關(guān)閉,spring對數(shù)據(jù)庫事務(wù)的包裝,原理就是拿一個數(shù)據(jù)連接,根據(jù)spring的事務(wù)配置,操作這個數(shù)據(jù)連接對數(shù)據(jù)庫進行事務(wù)開啟,回滾或關(guān)閉操作.但是spring除了實現(xiàn)這些,還配合spring的傳播行為對事務(wù)進行了更廣泛的管理.其實這里還有個重要的點,那就是事務(wù)中涉及的隔離級別,