若是沒有事務管理器的話,咱們的程序多是這樣:html
Connection connection = acquireConnection(); try{ int updated = connection.prepareStatement().executeUpdate(); connection.commit(); }catch (Exception e){ rollback(connection); }finally { releaseConnection(connection); }
也有多是這樣"優雅的事務":java
execute(new TxCallback() { @Override public Object doInTx(Connection var1) { //do something... return null; } }); public void execute(TxCallback txCallback){ Connection connection = acquireConnection(); try{ txCallback.doInTx(connection); connection.commit(); }catch (Exception e){ rollback(connection); }finally { releaseConnection(connection); } } # lambda版 execute(connection -> { //do something... return null; });
可是以上兩種方式,針對一些複雜的場景是很不方便的。在實際的業務場景中,每每有比較複雜的業務邏輯,代碼冗長,邏輯關聯複雜,若是一個大操做中有全是這種代碼的話我想開發人員可能會瘋把。更不用提定製化的隔離級別,以及嵌套/獨立事務的處理了。spring
Spring做爲Java最強框架,事務管理也是其核心功能之一。Spring爲事務管理提供了統一的抽象,有如下優勢:數據庫
事務,天然是控制業務的,在一個業務流程內,每每但願保證原子性,要麼全成功要麼全失敗。編程
因此事務通常是加載@Service
層,一個Service內調用了多個操做數據庫的操做(好比Dao),在Service結束後事務自動提交,若有異常拋出則事務回滾。session
這也是Spring事務管理的基本使用原則。app
下面貼出具體的使用代碼:框架
在被Spring管理的類頭上增長@Transactional註解,便可對該類下的全部方法開啓事務管理。事務開啓後,方法內的操做無需手動開啓/提交/回滾事務,一切交給Spring管理便可。異步
@Service @Transactional public class TxTestService{ @Autowired private OrderRepo orderRepo; public void submit(Order order){ orderRepo.save(order); } }
也能夠只在方法上配置,方法配置的優先級是大於類的ide
@Service public class TxTestService{ @Autowired private OrderRepo orderRepo; @Transactional public void submit(Order order){ orderRepo.save(order); } }
XML的配置方式較爲古老,此處就不貼代碼了,若有須要自行搜索
事務隔離級別是數據庫最重要的特性之一,他保證了髒讀/幻讀等問題不會發生。做爲一個事務管理框架天然也是支持此配置的,在@Transactional註解中有一個isolation配置,能夠很方便的配置各個事務的隔離級別,等同於connection.setTransactionIsolation()
Isolation { DEFAULT(-1), READ_UNCOMMITTED(1), READ_COMMITTED(2), REPEATABLE_READ(4), SERIALIZABLE(8); }
可能沒有接觸過Spring的人聽到傳播行爲會奇怪,這是個什麼東西。
其實這個傳播行爲和數據庫功能無關,只是事務管理器爲了處理複雜業務而設計的一個機制。
好比如今有這樣一個調用場景,A Service -> B Service -> C Service
,可是但願A/B在一個事務內,C是一個獨立的事務,同時C若是出錯,不影響AB所在的事務。
此時,就能夠經過傳播行爲來處理;將C Service的事務配置爲@Transactional(propagation = Propagation.REQUIRES_NEW)
便可
Spring支持如下幾種傳播行爲:
默認策略,優先使用當前事務(及當前線程綁定的事務資源),若是不存在事務,則開啓新事務
優先使用當前的事務(及當前線程綁定的事務資源),若是不存在事務,則以無事務方式運行
優先使用當前的事務,若是不存在事務,則拋出異常
建立一個新事務,若是存在當前事務,則掛起(Suspend)
以非事務方式執行,若是當前事務存在,則掛起當前事務。
以非事務方式執行,若是當前事務存在,則拋出異常
@Transactional中有4個配置回滾策略的屬性,分爲Rollback策略,和NoRollback策略
默認狀況下,RuntimeException和Error這兩種異常會致使事務回滾,普通的Exception(須要Catch的)異常不會回滾。
配置須要回滾的異常類
# 異常類Class Class<? extends Throwable>[] rollbackFor() default {}; # 異常類ClassName,能夠是FullName/SimpleName String[] rollbackForClassName() default {};
針對一些要特殊處理的業務邏輯,好比插一些日誌表,或者不重要的業務流程,但願就算出錯也不影響事務的提交。
能夠經過配置NoRollbackFor來實現,讓某些異常不影響事務的狀態。
# 異常類Class Class<? extends Throwable>[] noRollbackFor() default {}; # 異常類ClassName,能夠是FullName/SimpleName String[] noRollbackForClassName() default {};
設置當時事務的只讀標示,等同於connection.setReadOnly()
有下列代碼,入口爲test方法,在testTx方法中配置了@Transactional註解,同時在插入數據後拋出RuntimeException異常,可是方法執行後插入的數據並無回滾,居然插入成功了
public void test(){ testTx(); } @Transactional public void testTx(){ UrlMappingEntity urlMappingEntity = new UrlMappingEntity(); urlMappingEntity.setUrl("http://www.baidu.com"); urlMappingEntity.setExpireIn(777l); urlMappingEntity.setCreateTime(new Date()); urlMappingRepository.save(urlMappingEntity); if(true){ throw new RuntimeException(); } }
這裏不生效的緣由是由於入口的方法/類沒有增長@Transaction註解,因爲Spring的事務管理器也是基於AOP實現的,不論是Cglib(ASM)仍是Jdk的動態代理,本質上也都是子類機制;在同類之間的方法調用會直接調用本類代碼,不會執行動態代理曾的代碼;因此在這個例子中,因爲入口方法test
沒有增長代理註解,因此textTx
方法上增長的事務註解並不會生效
好比在一個事務方法中,開啓了子線程操做庫,那麼此時子線程的事務和主線程事務是不一樣的。
由於在Spring的事務管理器中,事務相關的資源(鏈接,session,事務狀態之類)都是存放在TransactionSynchronizationManager中的,經過ThreadLocal存放,若是跨線程的話就沒法保證一個事務了
# TransactionSynchronizationManager.java private static final ThreadLocal<Map<Object, Object>> resources = new NamedThreadLocal<>("Transactional resources"); private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations = new NamedThreadLocal<>("Transaction synchronizations"); private static final ThreadLocal<String> currentTransactionName = new NamedThreadLocal<>("Current transaction name"); private static final ThreadLocal<Boolean> currentTransactionReadOnly = new NamedThreadLocal<>("Current transaction read-only status"); private static final ThreadLocal<Integer> currentTransactionIsolationLevel = new NamedThreadLocal<>("Current transaction isolation level"); private static final ThreadLocal<Boolean> actualTransactionActive = new NamedThreadLocal<>("Actual transaction active");
org.springframework.transaction.UnexpectedRollbackException: Transaction silently rolled back because it has been marked as rollback-only
這個異常是因爲在同一個事務內,多個事務方法之間調用,子方法拋出異常,但又被父方法忽略了致使的。
由於子方法拋出了異常,Spring事務管理器會將當前事務標爲失敗狀態,準備進行回滾,但是當子方法執行完畢出棧後,父方法又忽略了此異常,待方法執行完畢後正常提交時,事務管理器會檢查回滾狀態,如有回滾標示則拋出此異常。具體能夠參考org.springframework.transaction.support.AbstractPlatformTransactionManager#processCommit
示例代碼:
A -> B # A Service(@Transactional): public void testTx(){ urlMappingRepo.deleteById(98l); try{ txSubService.testSubTx(); }catch (Exception e){ e.printStackTrace(); } } # B Service(@Transactional) public void testSubTx(){ if(true){ throw new RuntimeException(); } }