Spring管理事務的方式有兩種:
編程式事務:利用手動代碼編寫事務相關的業務邏輯,這種方式比較復雜、啰嗦,但是更加靈活可控制
public void testTransactionTemplate() {
TransactionTemplate transactionTemplate = new TransactionTemplate(txManager); transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); //設置事務隔離級別 transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//設置為required傳播級別 .... transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { //事務塊 jdbcTemplate.update(INSERT_SQL, "test"); }}); }
聲明式事務:為了避免我們每次都手動寫代碼,利用Spring AOP的方式對每個方法代理環繞,利用xml配置避免了寫代碼。
<tx:advice id="txAdvice" transaction-manager="txManager"> <tx:attributes> <!--設置所有匹配的方法,然后設置傳播級別和事務隔離--> <tx:method name="save*" propagation="REQUIRED" /> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="create*" propagation="REQUIRED" /> <tx:method name="insert*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="merge*" propagation="REQUIRED" /> <tx:method name="del*" propagation="REQUIRED" /> <tx:method name="remove*" propagation="REQUIRED" /> <tx:method name="put*" propagation="REQUIRED" /> <tx:method name="get*" propagation="SUPPORTS" read-only="true" /> <tx:method name="count*" propagation="SUPPORTS" read-only="true" /> <tx:method name="find*" propagation="SUPPORTS" read-only="true" /> <tx:method name="list*" propagation="SUPPORTS" read-only="true" /> <tx:method name="*" propagation="SUPPORTS" read-only="true" /> </tx:attributes> </tx:advice> <aop:config> <aop:pointcut id="txPointcut" expression="execution(* org.transaction..service.*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut" /> </aop:config>
同時也可以用注解的方式
<tx:annotation-driven transaction-manager="transactioManager" /><!--開啟注解的方式-->
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional { @AliasFor("transactionManager") String value() default ""; @AliasFor("value") String transactionManager() default ""; Propagation propagation() default Propagation.REQUIRED;//傳播級別 Isolation isolation() default Isolation.DEFAULT;//事務隔離級別 int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;//事務超時時間 boolean readOnly() default false;//只讀事務 Class<? extends Throwable>[] rollbackFor() default {};//拋出哪些異常 會執行回滾 String[] rollbackForClassName() default {}; Class<? extends Throwable>[] noRollbackFor() default {}; String[] noRollbackForClassName() default {};//不回滾的異常名稱 } //Transaction注解可以放在方法上或者類上
注意事項:
@Transactional注解可以作用在接口、類、類方法。
作用於類:當把@Transactional 注解放在類上時,表示所有該類的public方法都配置相同的事務屬性信息。
作用於方法:當類配置了@Transactional,方法也配置了@Transactional,方法的事務會覆蓋類的事務配置信息。
作用於接口:不推薦這種使用方法,因為一旦標注在Interface上並且配置了Spring AOP 使用CGLib動態代理,將會導致@Transactional注解失效
Transactional 注解可以被應用於接口定義和接口方法、類定義和類的 public 方法上。
請注意僅僅 @Transactional 注解的出現不足於開啟事務行為,並且只有配置了<tx:annotation-driven/>元素,@Transactional才會有效。
Spring建議是你在具體的類(或類的方法)上使用 @Transactional 注解,而不要使用在類所要實現的任何接口上。
@Transactional不生效的場景
1、用在public 修飾的方法上
如果在protected、private 修飾的方法上使用 @Transactional 注解,不會有任何報錯,但是事務無效
還是動態代理的原因,類內部方法的調用是通過this調用的,不會使用動態代理對象,事務不會回滾。
3、異常被處理了,比如使用了try..catch
Spring是根據拋出的異常來回滾的,如果異常被捕獲了沒有拋出的話,事務就不會回滾。
4、注解 rollbackFor屬性設置不對
Spring默認拋出unchecked異常或Error時才會回滾事務,要想其他類型異常也回滾則需要設置rollbackFor屬性的值。
5、數據庫引擎不支持事務
常用的MySQL數據庫默認使用支持事務的innodb引擎。一旦數據庫引擎切換成不支持事務的myisam,那事務就從根本上失效了
Spring管理的事務是邏輯事務,而且物理事務和邏輯事務最大差別就在於事務傳播行為,事務傳播行為用於指定在多個事務方法間調用時,事務是如何在這些方法間傳播的,
Spring共支持7種傳播行為
@Transactional(propagation=Propagation.REQUIRED)
如果有事務, 那么加入事務, 沒有的話新建一個(默認情況下)
@Transactional(propagation=Propagation.NOT_SUPPORTED)
容器不為這個方法開啟事務
@Transactional(propagation=Propagation.REQUIRES_NEW)
不管是否存在事務,都創建一個新的事務,原來的掛起,新的執行完畢,繼續執行老的事務
@Transactional(propagation=Propagation.MANDATORY)
必須在一個已有的事務中執行,否則拋出異常
@Transactional(propagation=Propagation.NEVER)
必須在一個沒有的事務中執行,否則拋出異常(與Propagation.MANDATORY相反)
@Transactional(propagation=Propagation.SUPPORTS)
如果其他bean調用這個方法,在其他bean中聲明事務,那就用事務.如果其他bean沒有聲明事務,那就不用事務.
為了演示事務傳播行為,我們新建一張用戶表
CEATE TABLE user ( `id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(255) NOT NULL, `pwd` varchar(255) NOT NULL, PRIMARY KEY (`id`) ) ENGINE=`InnoDB` AUTO_INCREMENT=10 DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci ROW_FORMAT=COMPACT CHECKSUM=0 DELAY_KEY_WRITE=0;
Required:
必須有邏輯事務,否則新建一個事務,使用PROPAGATION_REQUIRED指定,表示如果當前存在一個邏輯事務,則加入該邏輯事務,否則將新建一個邏輯事務,如下圖所示;
測試的代碼如下,在account插入的地方主動回滾
public int insertAccount(final String customer, final int money) {
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//設置為required傳播級別
int re= transactionTemplate.execute(new TransactionCallback<Integer>() { public Integer doInTransaction( TransactionStatus status) { int i = accountDao.insertAccount(customer, money); status.setRollbackOnly();//主動回滾 return i; } }); return re; } public int inertUser(final String username, final String password) { transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);//設置為required傳播級別 transactionTemplate.execute(new TransactionCallbackWithoutResult() { @Override protected void doInTransactionWithoutResult(TransactionStatus status) { int i = userDao.inertUser(username, password); int hahha = accountService.insertAccount("hahha", 2222); // status.setRollbackOnly(); System.out.println("user==="+i); System.out.println("account===="+hahha); } }); return 0; }
按照required的邏輯,代碼執行的邏輯如下:
- 在調用userService對象的insert方法時,此方法用的是Required傳播行為且此時Spring事務管理器發現還沒開啟邏輯事務,因此Spring管理器覺得開啟邏輯事務
- 在此邏輯事務中調用了accountService對象的insert方法,而在insert方法中發現同樣用的是Required傳播行為,因此直接使用該已經存在的邏輯事務;
- 返回userService,執行完並關閉事務
所以在這種情況下,兩個事務屬於同一個事務,一個回滾則兩個任務都回滾。
RequiresNew:
創建新的邏輯事務,使用PROPAGATION_REQUIRES_NEW指定,表示每次都創建新的邏輯事務(物理事務也是不同的)如下圖所示:
Supports:
支持當前事務,使用PROPAGATION_SUPPORTS指定,指如果當前存在邏輯事務,就加入到該邏輯事務,如果當前沒有邏輯事務,就以非事務方式執行,如下圖所示:
NotSupported:
不支持事務,如果當前存在事務則暫停該事務,使用PROPAGATION_NOT_SUPPORTED指定,即以非事務方式執行,如果當前存在邏輯事務,就把當前事務暫停,以非事務方式執行。
Mandatory:
必須有事務,否則拋出異常,使用PROPAGATION_MANDATORY指定,使用當前事務執行,如果當前沒有事務,則拋出異常(IllegalTransactionStateException)。當運行在存在邏輯事務中則以當前事務運行,如果沒有運行在事務中,則拋出異常
Never
不支持事務,如果當前存在是事務則拋出異常,使用PROPAGATION_NEVER指定,即以非事務方式執行,如果當前存在事務,則拋出異常(IllegalTransactionStateException)
Nested:
嵌套事務支持,使用PROPAGATION_NESTED指定,如果當前存在事務,則在嵌套事務內執行,如果當前不存在事務,則創建一個新的事務,嵌套事務使用數據庫中的保存點來實現,即嵌套事務回滾不影響外部事務,但外部事務回滾將導致嵌套事務回滾。
Nested和RequiresNew的區別:
- RequiresNew每次都創建新的獨立的物理事務,而Nested只有一個物理事務;
- Nested嵌套事務回滾或提交不會導致外部事務回滾或提交,但外部事務回滾將導致嵌套事務回滾,而 RequiresNew由於都是全新的事務,所以之間是無關聯的;
-
Nested使用JDBC 3的保存點(save point)實現,即如果使用低版本驅動將導致不支持嵌套事務。
使用嵌套事務,必須確保具體事務管理器實現的nestedTransactionAllowed屬性為true,否則不支持嵌套事務,如DataSourceTransactionManager默認支持,而HibernateTransactionManager默認不支持,需要設置來開啟。
關於spring的事務隔離級別與數據庫的一樣(詳見mysql事務),也是那四個,多了一個default
MYSQL: 默認為REPEATABLE_READ級別
SQLSERVER: 默認為READ_COMMITTED
比如下面的spring的配置方法:
<property name="transactionAttributes">
<props>
<prop key="save*">PROPAGATION_REQUIRED</prop>
<prop key="update*">PROPAGATION_REQUIRED</prop>
<prop key="delete*">PROPAGATION_REQUIRED</prop>
<prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="find*">PROPAGATION_REQUIRED,ISOLATION_READ_UNCOMMITTED</prop>
</props>
就以find為例,可以這么配置,前面是控制傳播行為,后面是控制事務隔離級別的。那么這時哪怕數據庫層面上是重復讀(Repeatable read),但是還是以這里為准,你會發現在同一個事務中兩次查詢的結果是不一樣的。
最后問題,readonly這個屬性,是放在傳播行為中的, readonly並不能影響數據庫隔離級別,只是配置之后,不允許在事務中對數據庫進行修改操作,僅此而已。