摘自: https://www.cnblogs.com/xiohao/p/4808088.html
Spring下面的@Transactional注解標志的講解
最近在開發中對Spring中的事務標記@Transactional用的比較多,今天上網收集了一些內容,做一個簡單的總結~~~
在service類前加上@Transactional,聲明這個service所有方法需要事務管理。每一個業務方法開始時都會打開一個事務。
Spring默認情況下會對運行期例外(RunTimeException)進行事務回滾。這個例外是unchecked
如果遇到checked意外就不回滾。
如何改變默認規則:
1 讓checked例外也回滾:在整個方法前加上 @Transactional(rollbackFor=Exception.class)
2 讓unchecked例外不回滾: @Transactional(notRollbackFor=RunTimeException.class)
3 不需要事務管理的(只查詢的)方法:@Transactional(propagation=Propagation.NOT_SUPPORTED)
如果在整個方法運行前就不會開啟事務
還可以加上:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true),這樣就做成一個只讀事務,可以提高效率。
此外需要注意: 如果異常被try{}catch{}了,事務就不回滾了,如果想讓事務回滾必須再往外拋try{}catch{throw Exception}。
各種屬性的意義:
REQUIRED:業務方法需要在一個容器里運行。如果方法運行時,已經處在一個事務中,那么加入到這個事務,否則自己新建一個新的事務。 NOT_SUPPORTED:聲明方法不需要事務。如果方法沒有關聯到一個事務,容器不會為他開啟事務,如果方法在一個事務中被調用,該事務會被掛起,調用結束后,原先的事務會恢復執行。 REQUIRESNEW:不管是否存在事務,該方法總會自己發起一個新的事務。如果方法已經運行在一個事務中,則原有事務掛起,新的事務被創建。 MANDATORY:該方法只能在一個已經存在的事務中執行,業務方法不能發起自己的事務。如果在沒有事務的環境下被調用,容器拋出例外。 SUPPORTS:該方法在某個事務范圍內被調用,則方法成為該事務的一部分。如果方法在該事務范圍外被調用,該方法就在沒有事務的環境下執行。 NEVER:該方法絕對不能在事務范圍內執行。如果在就拋例外。只有該方法沒有關聯到任何事務,才正常執行。 NESTED:如果一個活動的事務存在,則運行在一個嵌套的事務中。如果沒有活動事務,則按REQUIRED屬性執行。它使用了一個單獨的事務,這個事務擁有多個可以回滾的保存點。內部事務的回滾不會對外部事務造成影響。它只對 DataSourceTransactionManager事務管理器起效。
// 如果有事務,那么加入事務,沒有的話新建一個(不寫的情況下) @Transactional(propagation=Propagation.REQUIRED) // 容器不為這個方法開啟事務 @Transactional(propagation=Propagation.NOT_SUPPORTED) // 不管是否存在事務,都創建一個新的事務,原來的掛起,新的執行完畢,繼續執行老的事務 @Transactional(propagation=Propagation.REQUIRES_NEW) // 必須在一個已有的事務中執行,否則拋出異常 @Transactional(propagation=Propagation.MANDATORY) // 必須在一個沒有的事務中執行,否則拋出異常(與Propagation.MANDATORY相反) @Transactional(propagation=Propagation.NEVER) // 如果其他bean調用這個方法,在其他bean中聲明事務,那就用事務.如果其他bean沒有聲明事務,那就不用事務. @Transactional(propagation=Propagation.SUPPORTS) @Transactional(propagation=Propagation.NESTED) // readOnly=true只讀,不能更新,刪除 @Transactional (propagation = Propagation.REQUIRED,readOnly=true) // 設置超時時間 @Transactional (propagation = Propagation.REQUIRED,timeout=30) // 設置數據庫隔離級別 @Transactional (propagation = Propagation.REQUIRED,isolation=Isolation.DEFAULT)
@Transactional設置:
propagation:事務傳播性設置,Propagation枚舉類型。Spring支持的事務傳播屬性包括7種:
ROPAGATION_MANDATORY:方法必須在事務中執行,否則拋出異常。 PROPAGATION_NESTED:使方法運行在嵌套事務中,否則和PROPAGATION_REQUIRED一樣。 PROPAGATION_NEVER :當前方法永遠不在事務中運行,否則拋出異常。 PROPAGATION_NOT_SUPPORTED:定義為當前事務不支持的方法,在該方法執行期間正在運行的事務會被暫停 PROPAGATION_REQUIRED:當前的方法必須運行在事務中,如果沒有事務就新建一個事務。新事務和方法一起開始,隨着方法返回或者拋出異常時終止。 PROPAGATION_REQUIRED_NEW :當前方法必須新建一個事務,如果當前的事務正在運行則暫停。 PROPAGATION_SUPPORTS :規定當前方法支持當前事務,但是如果沒有事務在運行就使用非事務方法執行。
isolation:事務隔離性級別設置,Isolation枚舉類型
ISOLATION_DEFAULT :使用數據庫默認的隔離級別 ISOLATION_COMMITTED:允許其他事務已經提交的更新(防止臟讀取) ISOLATION_READ_UNCOMMITTED:允許讀取其他事務未提交的更新,會導致三個缺陷發生。執行速度最快 ISOLATION_REPEATABLE_READ :除非事務自身更改了數據,否則事務多次讀取的數據相同(防止臟數據,多次重復讀取) ISOLATION_SERIALIZABLE:隔離級別最高,可以防止三個缺陷,但是速度最慢,影響性能。
readOnly:讀寫性事務,只讀性事務,布爾型
對數據庫的操作中,查詢是使用最頻繁的操作,每次執行查詢時都要從數據庫中重新讀取數據,有時多次讀取的數據都是相同的,這樣的數據操作不僅浪費了系統資源,還影響了系統速度。對訪問量大的程序來說,節省這部分資源可以大大提 升系統速度。 將事務聲明為只讀的,那么數據庫可以根據事務的特性優化事務的讀取操作
timeout:超時時間,單位秒
事務可能因為某種原因很長時間沒有反應,這期間可能鎖定了數據庫表,影響性能。設置超時時間,如果超過該時間,事務自動回滾。
rollbackFor:一組異常類的實例,遇到時必須進行回滾
rollbackForClassname:一組異常類的名字,遇到時必須進行回滾
noRollbackFor:一組異常類的實例,遇到時必須不回滾
noRollbackForClassname:一組異常類的名字,遇到時必須不回滾
實例一:事務的回滾情況
1、默認情況對非運行時異常不進行回滾操作
/** * 運行時異常默認事務回滾 * @throws Exception */ @Transactional public void updateUser_1() throws Exception { Map<String,String> userMap=new HashMap<String,String>(); userMap.put("id","1"); userMap.put("userName","測試_01"); userMap.put("password","654321"); userDao.updateUserByID(userMap); throw new RuntimeException(); //拋出運行時異常 默認情況下回滾 事務---------------------->回滾 }
2、非運行時異常默認事務不回滾
/** * 非運行時異常默認事務不回滾 * @throws Exception */ @Transactional public void updateUser_3() throws Exception { Map<String,String> userMap=new HashMap<String,String>(); userMap.put("id","1"); userMap.put("userName","測試_01"); userMap.put("password","654321"); userDao.updateUserByID(userMap); throw new Exception(); //拋出非運行時異常 默認情況下不回滾 事務---------------------->不回滾 }
3、對所有異常事務都進行回滾操作
/** * 對所有異常事務都進行回滾操作 * @throws Exception */ @Transactional(rollbackFor=Exception.class) public void updateUser_6() throws Exception { Map<String,String> userMap=new HashMap<String,String>(); userMap.put("id","1"); userMap.put("userName","測試_01"); userMap.put("password","654321"); userDao.updateUserByID(userMap); throw new Exception(); //對所有異常事務都進行回滾操作 事務---------------------->回滾 }
4、對所有異常事務都不進行回滾操作
/** * 對所有異常事務都不進行回滾操作 * @throws Exception */ @Transactional(noRollbackFor=Exception.class) public void updateUser_7() throws Exception { Map<String,String> userMap=new HashMap<String,String>(); userMap.put("id","1"); userMap.put("userName","測試_01"); userMap.put("password","654321"); userDao.updateUserByID(userMap); throw new RuntimeException(); //對所有異常,事務都不進行回滾操作 事務---------------------->不回滾 }
實例二:對try{} catch{}的異常不回滾
/** * 對於try{} catch{}的異常不進行回滾 * @throws Exception */ @Transactional public void updateUser_5() throws Exception { Map<String,String> userMap=new HashMap<String,String>(); userMap.put("id","1"); userMap.put("userName","測試_01"); userMap.put("password","654321"); userDao.updateUserByID(userMap); try{ throw new RuntimeException(); //運行時異常被捕獲,事務不回滾 } catch(Exception e){ System.err.println("運行時異常被捕獲,事務不回滾"); } }
實例三:關於嵌套事務的回滾原則
說實話對於嵌套事務的測試,我現在仍然有一些疑問,比如說如果內部事務設置為
// 不管是否存在事務,都創建一個新的事務,原來的掛起,新的執行完畢,繼續執行老的事務
@Transactional(propagation=Propagation.REQUIRES_NEW)
那這樣的情況下,內部事務設置為Propagation.REQUIRES_NEW,外部事務設置為Propagation.REQUIRED,那么如果在內部事務執行完后之后,如果外部事務拋出了運行時異常,那么對於內部事務而言
由於是新開的一個單獨的事務,這樣的情況下內部事務應該不受外部事務的影響而回退,但是事實上經過測試,內部事務也是回滾的。感興趣的同學可以一起探討啊~
在網上找了一些博客,下面貼出一篇比較好嵌套事務的博客:
http://blog.chinaunix.net/uid-10289334-id-2964925.html
1、外部事務拋異常,內部事務回滾(一般來說,只要外部開啟事務,內部方法無論是否開啟事務,只要出現相應的運行時異常,事務都將進行回滾操作)
/** * 測試事務的相關信息 * @throws Exception */ @Override @Transactional(propagation=Propagation.REQUIRES_NEW) public void updateUser() throws Exception { Map<String,String> userMap=new HashMap<String,String>(); userMap.put("id","1"); userMap.put("userName","測試_01"); userMap.put("password","654321"); userDao.updateUserByID(userMap); insideMethod_01(); //1 、異常 不捕獲 事務---------------------->回滾 // insideMethod_02(); //2、異常 捕獲 事務---------------------->不回滾 throw new RuntimeException(); //3、測試外部方法拋異常 事務---------------------->回滾 // throw new Exception();//4、默認情況,非運行時異 常 事務---------------------->不回滾 } /** * 事務測試的內部方法 內部方法拋運行時異常不捕獲 </br> * * 事務會對內外方法進行回滾操作 */ @Override @Transactional(propagation=Propagation.REQUIRES_NEW) public void insideMethod_01(){ Map<String,String> userMap=new HashMap<String,String>(); userMap.put("id","2"); userMap.put("id","2"); userMap.put("userName","測試_02"); userMap.put("password","654321"); userDao.updateUserByID(userMap); // int value=1/0;//運行時異常 }
2、內部事務拋異常,外部事務回滾(一般來說,只要外部開啟事務,內部方法無論是否開啟事務如何配置,只要出現相應的運行時異常,內外事務都將進行回滾操作)
/** * 測試事務的相關信息 * @throws Exception */ @Override @Transactional(propagation=Propagation.REQUIRES_NEW) public void updateUser() throws Exception { Map<String,String> userMap=new HashMap<String,String>(); userMap.put("id","1"); userMap.put("userName","測試_01"); userMap.put("password","654321"); userDao.updateUserByID(userMap); insideMethod_01(); //1 、異常 不捕獲 事務---------------------->回滾 // insideMethod_02(); //2、異常 捕獲 事務---------------------->不回滾 // throw new RuntimeException(); //3、測試外部方法拋異常 事務---------------------->回滾 // throw new Exception();//4、默認情況,非運行時異 常 事務---------------------->不回滾 } /** * 事務測試的內部方法 內部方法拋運行時異常不捕獲 </br> * * 事務會對內外方法進行回滾操作 */ @Override @Transactional(propagation=Propagation.REQUIRES_NEW) public void insideMethod_01(){ Map<String,String> userMap=new HashMap<String,String>(); userMap.put("id","2"); userMap.put("id","2"); userMap.put("userName","測試_02"); userMap.put("password","654321"); userDao.updateUserByID(userMap); int value=1/0;//運行時異常 }
注:這里還有一點在實際開發中需要注意,如果外部方法調用了遠程的接口,由於它們不在一個事務控制中,類似於分布式事務,這種情況遠程事務是無法進行回滾操作的~