前文:Spring_基於Spring的JDBC
事務應該在哪一層?
前文中,我們知道Spring的JDBC會幫我們管理事務。
在這種情況下,會出現什麼問題呢?
一些僞代碼:
public class AccountDaoImpl implements IAccountDao{ /* 轉入 */ public void transin(Long inId, BigDecimal value){ // do work } /* 轉出 */ public void transout(Long outId, BigDecimal value){ // do work } }
public class AccountServiceImpl implements IAccountService{ /* 轉賬方法 */ public void transfer(Long inId, Long outId, BigDecimal value){ accountDao.transout(outId, value); // 轉出 accountDao.transin(inId, value); // 轉入 } }
可以看到,在Service層中,轉賬方法是通過調用DAO層的轉出、轉入方法實現的。
但是,由於事務是在DAO層中進行管理的,若Service層在轉出後,系統出現異常,轉入代碼將無法繼續正常執行。
正確的JDBC轉賬流程
1、進入Service層的轉賬方法時:獲取DataSoure對象,得到Connection對象。
2、關閉JDBC的事務自動提交:Connection對象.setAutoCommit(false);
3、將Connection對象綁定到當前線程中。
4、調用DAO層的方法,其Connection對象要從當前線程中獲取。
5、正常執行則提交事務;出現異常則回滾。
Spring的事務管理
· 相關接口
TransactionDefinition
封裝事務的隔離級別,超時時間。是否爲只讀事務和事務的隔離級別,轉播規則等事務屬性。
可通過XML配置其具體信息。
PlatformTransactionManager
根據TeansactionDefinition提供的事務屬性,創建事務。
TransactionStatus
封裝了事務的具體運行狀態。
如是否新開事務,是否已提交事務,可設置當前事務爲rollback-only等。
· 事務管理的方式
Spring支持編程式事務管理和聲明式事務管理。
編程式事務管理
如使用Hibernate,我們需要在代碼中顯式調用beginTransaction()、commit()、rollback()等事務管理相關的方法,這就是編程式事務管理。
通過 Spring 提供的事務管理 API,我們可以在代碼中靈活控制事務的執行。在底層,Spring 仍然將事務操作委託給底層的持久化框架來執行。
這種方式事務和業務代碼耦合度太高
聲明式事務管理
Spring 的聲明式事務管理在底層是建立在 AOP 的基礎之上的。
其本質是對方法前後進行攔截,然後在目標方法開始之前創建或者加入一個事務,在執行完目標方法之後根據執行情況提交或者回滾事務。
這種方式侵入性小,把事務從業務代碼中抽離出來到配置文件中,提供維護性。
· 事務管理器
Spring並不直接管理事務,而是提供了多種事務管理器,將事務管理叫給Hibernate等持久化機制所提供的相關平臺框架的事務來實現。
Spring事務管理器的接口是org.springframework.transaction.PlatformTransactionManager。
通過這個接口,Spring爲各個平臺如JDBC、Hibernate等都提供了對應的事務管理器,但是具體的實現就是各個平臺自己的事情了。
spring提供的事務管理器
在使用Spring的事務時,需告訴Spring使用哪一個管理器。
其中DataSourceTransactionManager爲JDBC、MyBatis使用的事務管理器。
· PlatformTransactionManager中的方法
TransactionStatus getTransaction(TransactionDefinition definition)
根據TransactionDefinition事務定義信息,從事務環境返回一個已存在的事務,或創建一個新的事務。
並用TransactionStatus描述事務狀態。
void commit(TransactionStatus status)
根據事務狀態提交事務。
如事務狀態標識爲rollback-only,該方法執行回滾事務的操作。
void rollback(TransactionStatus status)
將事務回滾,當commit方法拋出異常時,rollback會被隱式調用
聲明式事務管理
基於AOP,使用事務管理器,對業務代碼進行事務增強。
· what :做什麼增強?
做事務管理器的增強。
告訴Spring使用的是哪一個具體的事務管理器。(如JDBC使用的是DataSourceTransactionManager。)
並對事務管理器做相應的配置。(如DataSourceTransactionManager需注入DataSource對象。)
如下例:
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" > <property name="dataSource" ref="dataSource" /> </bean>
· When:在什麼時機增強?
事務應該是環繞增強。如下例:
<tx:advice transaction-manager="txManager" id="txAdvice">
<tx:attributes>
<tx:method name="transfer"/>
</tx:attributes>
</tx:advice>
<tx:advice>中配置transaction-manager屬性,該管理器做環繞增強。
<tx:attributes>元素中<tx:method>的屬性配置,可理解爲對TransactionDefinition對象做屬性配置以及一些額外的配置,以便事務管理器獲取事務,做回滾相關操作。
其name屬性值爲方法名,表示在對該方法做事務增強,可使用通配符*。
更多具體配置可參考文末。
· Where:在哪裏做增強?
通過AOP做相關配置,如下例:
<aop:config>
<aop:pointcut id="txPoint" expression="execution(* com.hanaii.spring_tx.service.*Service.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPoint" />
</aop:config>
對切入點進行配置後,我們還需要通過<aop:advisor>將advice與pointcut關聯起來。
這樣Spring才知道從哪個地方where,什麼時候when,做什麼樣what的切入。
<tx:method>中的屬性
· 事務傳播行爲
Spring在TransactionDefinition接口中規定了7種類型的事務傳播行爲,
它們規定了事務方法和事務方法發生嵌套調用時事務如何進行傳播。
PROPAGATION_REQUIRED
如果當前沒有事務,就新建一個事務,
如果已經存在一個事務中,加入到這個事務中。這是最常見的選擇。
PROPAGATION_SUPPORTS
支持當前事務,如果當前沒有事務,就以非事務方式執行。
PROPAGATION_MANDATORY
使用當前的事務,如果當前沒有事務,就拋出異常。
PROPAGATION_REQUIRES_NEW
新建事務,如果當前存在事務,把當前事務掛起。
PROPAGATION_NOT_SUPPORTED
以非事務方式執行操作,如果當前存在事務,就把當前事務掛起。
PROPAGATION_NEVER
以非事務方式執行,如果當前存在事務,則拋出異常。
PROPAGATION_NESTED
如果當前存在事務,則在嵌套事務內執行。如果當前沒有事務,則執行與PROPAGATION_REQUIRED類似的操作。
· CRUD通用的環繞增強
<tx:advice transaction-manager="txManager" id="txAdvice">
<tx:attributes>
<tx:method name="get*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
· 使用註解配置事務
1、在Spring配置文件中配置註解解析器和事務管理器
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager" >
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="txManager"/>
2、使用@Transactional標籤進行標註
該註解源代碼:
public @interface Transactional {
String value() default "";
Propagation propagation() default Propagation.REQUIRED;
Isolation isolation() default Isolation.DEFAULT;
int timeout() default -1;
boolean readOnly() default false;
Class<? extends Throwable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<? extends Throwable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}
該註解標註在類上時,其配置對該類所有方法生效。
同時,標註在方法時,可對某一方法進行局部的配置(如查詢方法配置read-only)。