Spring是目前Java開發中最流行的框架了,它的事務管理我們在開發中常常的用到,但是很多人不理解它事務的原理,導致開發中遇到事務方面的問題往往都要用很長的時間才能解決,下面就帶著大家去深入瞭解Spring的事務,然後文章的最後還會給出開發中常常遇到的問題以及解決方案。
如果單純的用Spring框架進行開發(PS使用註解開發,不是XML設定的方式)。那麼要使用Spring事物我們首先要加的就是Spring的這個【EnableTransactionManagement】註解(PS如果直接使用了Spingboot框架了,它已經使用自動設定相關的原理自動加了這個註解了)。
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(TransactionManagementConfigurationSelector.class) public @interface EnableTransactionManagement {
上面註解的主要作用就是幫我們Import了TransactionManagementConfigurationSelector這個類,它的主要功能如下:
這個類目前就是幫我們在Spring中注入了ProxyTransactionManagementConfiguration,大家一看名稱就知道這是一個設定類(設定類一般都是幫我們注入各種需要的Bean)如下它幫我們做的事情。
@Bean(name = TransactionManagementConfigUtils.TRANSACTION_ADVISOR_BEAN_NAME) @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public BeanFactoryTransactionAttributeSourceAdvisor transactionAdvisor() { BeanFactoryTransactionAttributeSourceAdvisor advisor = new BeanFactoryTransactionAttributeSourceAdvisor(); advisor.setTransactionAttributeSource(transactionAttributeSource()); advisor.setAdvice(transactionInterceptor()); return advisor; } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionAttributeSource transactionAttributeSource() { return new AnnotationTransactionAttributeSource(); } @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) public TransactionInterceptor transactionInterceptor() { TransactionInterceptor interceptor = new TransactionInterceptor(); interceptor.setTransactionAttributeSource(transactionAttributeSource()); return interceptor; }
上面的程式碼可以看出它幫我們做了三件事,1:匯入了一個增強器。2:匯入了一個事務屬性的資源。3:匯入一個事務的攔截器。這三個類後面講事務的時候都會用到。
如果方法上加了事物註解的類,Spring會建立它的一個代理類放到容器中,如果沒加註解(PS本文只考慮事物註解不考慮其他的)那麼Spring就會把它原始的物件放到容器中這個很重要,後面總結事物為什麼會失效。(下面第一張圖沒加Transaction註解的物件,第二張圖是加了的,然後Sping就把它轉換為代理物件,具體怎麼轉換的是AOP相關功能,AOP很多功能點本文不做具體講解)【上面說的一段話很是重要】。
重點來了,下面是我UserService中的主要的方法,很簡單如下:
@Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao ; @Autowired private ApplicationContext applicationContext ; @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) public void addUser(User user) { user.setPersonName("xiaozhang"); userDao.insert(user) ; user.setPersonName("xiaozhang02"); UserService userService = applicationContext.getBean(UserService.class); userService.addUser02(user); int i= 1/0 ; //讓程式丟擲異常 } @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRES_NEW) public void addUser02(User user02) { userDao.insert(user02) ; } }
然後就開始帶著大家看相關的原始碼,首先打斷點會進入到如下的方法(你如果想嘗試就在這個類【TransactionAspectSupport】的如下方法中打一個斷點)。
分析上面程式碼主要功能。
--1:這段程式碼是獲取事務的屬性,上面設定中設定了 final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null); --2:決定使用那個事務管理器 final PlatformTransactionManager tm = determineTransactionManager(txAttr); --3:找方法的切入點也就是加了事務註解的方法 final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
上面圖中的三個方法就是註釋中寫的作用,後面就是事務的重點了,然後會走下面的方法。
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, final String joinpointIdentification) { TransactionStatus status = null; if (txAttr != null) { if (tm != null) { status = tm.getTransaction(txAttr); //這個重點方法 }
tm.getTransaction(txAttr),這個方法會呼叫如下方法:
//AbstractPlatformTransactionManager這個類的方法 public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException { //獲取事務管理器,第一次獲取的為空 Object transaction = doGetTransaction(); //看看是否存在事務,第一次不存在。第二次會進入到這個方法,後面會重點講。 if (isExistingTransaction(transaction)) { // Existing transaction found -> check propagation behavior to find out how to behave. return handleExistingTransaction(definition, transaction, debugEnabled); } //我們addUser的方法事務傳播行為是PROPAGATION_REQUIRED,所以走這個分支 else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) { //掛起事務,後面PROPAGATION_REQUIRES_NEW這個方法很重要 SuspendedResourcesHolder suspendedResources = suspend(null); try { boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); //建立一個事務狀態 DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); //重點來了,開啟事務 doBegin(transaction, definition); prepareSynchronization(status, definition); return status; }
開啟事務方法(doBegin)(這個方法做了如下6件很重要的事情)
protected void doBegin(Object transaction, TransactionDefinition definition) { DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction; //1:獲取事務連線,我們寫JDBC的時候也是需要自己獲取事務連線的 Connection con = null; if (!txObject.hasConnectionHolder() || txObject.getConnectionHolder().isSynchronizedWithTransaction()) { Connection newCon = obtainDataSource().getConnection(); txObject.setConnectionHolder(new ConnectionHolder(newCon), true); } //2:設定資料庫的隔離級別 Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition); txObject.setPreviousIsolationLevel(previousIsolationLevel); //3:關閉資料庫的自動提交 // Switch to manual commit if necessary. This is very expensive in some JDBC drivers, // so we don't want to do it unnecessarily (for example if we've explicitly // configured the connection pool to set it already). if (con.getAutoCommit()) { txObject.setMustRestoreAutoCommit(true); con.setAutoCommit(false); } prepareTransactionalConnection(con, definition); // 4:啟用事務 txObject.getConnectionHolder().setTransactionActive(true); // 5:設定超時的時間 int timeout = determineTimeout(definition); if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) { txObject.getConnectionHolder().setTimeoutInSeconds(timeout); } //6:繫結資料來源和連線持有器。 // Bind the connection holder to the thread. if (txObject.isNewConnectionHolder()) { TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder()); } }
上面方法完成後,開始執行下面這個方法:
try { 這個方法的主要意思是去執行目標方法,也就是(addUser) // This is an around advice: //Invoke the next interceptor in the chain. // This will normally result in a target object being invoked. retVal = invocation.proceedWithInvocation(); }
去執行目標方法(addUser)後,就又回到前面那個第一次進入Spring原始碼中的方法(因為從上面的程式碼可知我們的addUser方法又呼叫了其他事務方法)如下圖:
注意此時的addUser()方法還沒執行完,又進入同樣的方法如下:
上面很多重複的就不講了,前面由於已經存在了事務所以會進入如下方法。
//前面說要重點說的 private TransactionStatus handleExistingTransaction( TransactionDefinition definition, Object transaction, boolean debugEnabled) throws TransactionException { //不允許有事務就丟擲異常 if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) { throw new IllegalTransactionStateException( "Existing transaction found for transaction marked with propagation 'never'"); } //重點也是我們開發中常用的事務傳播行為(新建一個事務) // PROPAGATION_REQUIRES_NEW if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) { //先把舊的事務掛起,並且快取起來, // 怎麼快取舊的事務的自己可走進去看一下,因為前面事務還沒提交,還需要用 SuspendedResourcesHolder suspendedResources = suspend(transaction); try { boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER); DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources); //又開啟新的事務了,和上面講過的同樣的套路了 doBegin(transaction, definition); prepareSynchronization(status, definition); return status; }
後面就和前面一樣了流程。最後資料庫結果如下(PS忽略我的資料庫表設計,只是為了簡單,用的Mysql資料庫也是為了自己電腦方便安裝)。
結果addUser插入失敗了,addUser02插入成功了。(這也就是事務傳播屬性,Propagation.REQUIRED和Propagation.REQUIRES_NEW的區別)。
好了我們進行總結在工作中事務方面的問題。
1:事務註解使用不當,造成事務不生效(PS只舉列開發中常用的)。
1.1 :沒有用代理物件呼叫,直接用原始物件(文章開始說了加了事務註解的會生成一個代理物件),如下的使用事務會失效(也是大多數開發者常犯的錯誤)。
@Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao ; @Autowired private ApplicationContext applicationContext ; @Override public void addUser(User user) { user.setPersonName("xiaozhang"); userDao.insert(user) ; user.setPersonName("xiaozhang03"); //用this沒有AOP代理物件,造成事務失效,2個user都會插入資料庫 this.addUser02(user) ; } @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) public void addUser02(User user02) { userDao.insert(user02) ; int i= 1/0 ; //讓程式丟擲異常 } }
1.2 rollbackFor = Exception.class如果不加,Spring只回滾執行時候的異常和程式中的Error(這是很細節的程式碼) 。
上面2個問題解決方案大家看完也就很明白怎樣解決了。
2:事務巢狀使用不當導致事務都回滾了,如下有時候會寫下面的業務。
@Service public class UserServiceImpl implements UserService { @Autowired private UserDao userDao ; @Autowired private ApplicationContext applicationContext ; @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) public void addUser(User user) { user.setPersonName("xiaozhang"); userDao.insert(user) ; user.setPersonName("xiaozhang02"); UserServiceImpl userService = (UserServiceImpl)applicationContext.getBean(UserService.class); try { userService.addUser02(user); }catch (Exception e){ System.out.println(e); } } @Override @Transactional(rollbackFor = Exception.class,propagation = Propagation.REQUIRED) public void addUser02(User user02) { userDao.insert(user02) ; int i= 1/0 ; //讓程式丟擲異常 } }
很多時候我們一看程式碼也沒什麼問題addUser()這個方法會新增資料的,因為把addUser02()方法丟擲的異常捕獲了。開發的時候(PS開發的時候呼叫的其他服務的業務方法,我為了演示簡單這樣寫了)我遇到這方面的問題找了很久才找到原因。會丟擲如下異常:
然後我們拿Spring原始碼分析,上面寫的兩個事務傳播屬性都是propagation = Propagation.REQUIRED (PS如果存在事務就使用原來的事務,不存在就新啟一個事務)。
簡單的說一下為什麼會出現上面的原因,addUser02()這個方法丟擲異常了它會把一個全域性的rollback-only變數標記為true(預設為false)。由於它和addUser()方法共用事務,所以後面這個方法事務提交的時候會檢查這個變數,如果看到這個變數為true。就把它自己也回滾了。如下:
下面addUser()方法要進行提交事務瞭如下:
然後會進入AbstractPlatformTransactionManager這個類的commit方法如下圖。
然後會進入processRollback這個方法,為什麼會進入這個方法還是因為rollback-only這個變數上一步設定為true了。(圖片中可以看到是那個類的這個方法,自己Debug打斷點的時候可參考),這個就是我們前面圖中丟擲的異常(Transaction rolled back because it has been marked as rollback-only)。
private void processRollback(DefaultTransactionStatus status, boolean unexpected){ try { //unexpected 為true 。 boolean unexpectedRollback = unexpected; // Unexpected rollback only matters here if we're asked to fail early //這個方法不會進入 if (!isFailEarlyOnGlobalRollbackOnly()) { unexpectedRollback = false; } } } triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK); // Raise UnexpectedRollbackException if we had a global rollback-only marker // unexpectedRollback 為true 就會丟擲開始說的那個異常。 if (unexpectedRollback) { throw new UnexpectedRollbackException( "Transaction rolled back because it has been marked as rollback-only"); } }
遇到上面的這個問題怎麼解決的?
1:addUser02()這個方法新建一個事物也就是使用事物傳播屬性propagation = Propagation.REQUIRES_NEW(PS如果你寫的業務邏輯可以這樣做)。
2:不用Transaction這個事物註解,事物的提交和回滾自己控制,Sping提供的有事物手動提交和回滾的方法,自己可以查詢一下。
文章中很多地方我都標註了那個類的那個方法,跟著上面一步步看完原始碼,事物方面開發中如果再遇到問題應該很快就能解決,事物那些傳播屬性也很快就能理解,開發中用巢狀事物也不必太擔心有Bug了。