Spring事务传播行为(Propagation behavior)
来源:互联网 发布:总裁高级研修班知乎 编辑:程序博客网 时间:2024/06/05 09:23
Spring事务传播行为(Propagation behavior)
有的时候,一个业务类的方法需要调用另一个业务类的方法,如下面的情形所示。
public class OuterService { private InnerService innerService; @Transactional public void outerMethod() { /*do something*/ try { innerService.innerMethod(); } catch(Exception e) { /*catch the exception*/ } }}public class InnerService { @Transactional public void innerMethod() { /* do something */ }}
OuterService类的outerMethod()需要调用InnerService类的innerMethod()方法。如果innerMethod()发生某些异常并且回滚,那么,outerMethod()是否回滚?(即事务怎么由内传播到外)
1 七个传播行为
Spring针对这种情况,定义了一些行为,叫做事务传播行为(transaction propagation behavior)。Spring事务传播行为可以分为三类:
- REQUIRED,SUPPORTS,MANDATORY (REQUIRES_NEW)
要求方法在事务中运行。如果outer method在事务中运行,就使用当前事务。(但REQUIRES_NEW
比较特殊,始终会新建一个事务,始终在事务中运行。)如果outer method不存在事务,则分别是新建一个事务、以非事务方式运行和抛出异常。
- NOT_SUPPORTED, NEVER
要求inner method以非事务的方式运行。如果当前存在事务,则分别是将当前事务挂起和抛出异常。
- NESTED
innerMethod()的事务与outerMethod()的事务共享同一个physical transaction,但是,这个physical transaction中有很多savepoint。每个innerMethod()可以单独还原到savepoint,outerMethod()仍然可以正常提交。但是,如果outerMethod()回滚,则innerMethod()也要回滚。
2 重点说明
REQUIRED
如下图所示,method1 (outer method)处于事务之中,当method2(inner method) 传播行为设置为REQUIRED
时,它们两者处于不同的logical transaction,但是,处于相同的physical transaction。当两者有一个回滚时,他们都会回滚,因为他们处于相同的physical transaction之中。
(来源:http://docs.spring.io/spring-framework/docs/4.2.x/spring-framework-reference/html/transaction.html#transaction-declarative)
如果outer method不处于事务之中,则inner method会新建一个事务。
见下面的例子,以银行转账业务为例。假设,每次转账之后,都将一条转账的记录插入数据库之中。
- 设置outer method抛出异常并回滚,那么inner method也会回滚。
转账相关的类:
AccountService
package com.chris.service;import java.text.SimpleDateFormat;import java.util.Date;import java.util.List;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.transaction.annotation.Isolation;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import com.chris.dao.AccountDAO;import com.chris.domain.Account;import com.chris.domain.Record;public class AccountService { private AccountDAO accountDAO; @Autowired private RecordService recordService; public AccountDAO getAccountDAO() { return accountDAO; } public void setAccountDAO(AccountDAO accountDAO) { this.accountDAO = accountDAO; } @Transactional(propagation=Propagation.REQUIRED) public void transfer(String from, String to, double money) { accountDAO.outMoney(from, money); accountDAO.inMoney(to, money); Date date = new Date(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); //here the type is String, but in db, the type is datetime. However, it works. String currentTime = sdf.format(date); Record record = new Record(from, to, money, currentTime); try { recordService.insertRecord(record); } catch(RuntimeException e) { } throw new RuntimeException("rollback outer transaction"); }}
插入转账记录的类RecordService:
package com.chris.service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import com.chris.dao.RecordDAO;import com.chris.domain.Record;public class RecordService { private RecordDAO recordDAO; public RecordDAO getRecordDAO() { return recordDAO; } public void setRecordDAO(RecordDAO recordDAO) { this.recordDAO = recordDAO; } @Transactional(propagation=Propagation.REQUIRED) public void insertRecord(Record record) { recordDAO.insertRecord(record); //throw new RuntimeException("rollback the inner transaction"); }}
测试方法
@RunWith(SpringJUnit4ClassRunner.class)@ContextConfiguration("classpath:applicationContext.xml")public class TransferTest { @Autowired private AccountService accountService; @Test public void testTransfer() { accountService.transfer("Jane", "Michael", 100d); }}
数据库初始记录
mysql> select * from account;+----+---------+-----------+| id | name | money |+----+---------+-----------+| 1 | Michael | 1100.0000 || 2 | Jane | 900.0000 || 3 | Kate | 1000.0000 |+----+---------+-----------+3 rows in set (0.00 sec)mysql> select * from record;+----+-----------+---------+----------+---------------------+| id | from_user | to_user | money | time |+----+-----------+---------+----------+---------------------+| 29 | Jane | Michael | 100.0000 | 2017-01-11 12:45:54 |+----+-----------+---------+----------+---------------------+1 row in set (0.00 sec)
运行之后,数据库的记录没有变。
REQUIRES_NEW
REQUIRES_NEW
始终会新建一个事务,这个事务与原来的事务处于不同的logical transaction和physical transaction。如下图所示。因此两个事务是彼此独立的,不会相互影响。当然,如果inner method出现了RuntimeException并且抛出,没有被outer method 捕获并且处理,导致outer method异常,则两个都会回滚。
- outer method为
REQUIRED
,inner method为REQUIRES_NEW
@Transactional(propagation=Propagation.REQUIRED) public void transfer(String from, String to, double money) { accountDAO.outMoney(from, money); accountDAO.inMoney(to, money); /*省略一部分*/ try { recordService.insertRecord(record); } catch(RuntimeException e) { } } @Transactional(propagation=Propagation.REQUIRES_NEW) public void insertRecord(Record record) { recordDAO.insertRecord(record); throw new RuntimeException("rollback the inner transaction"); }
运行之前,
mysql> select * from account;+----+---------+-----------+| id | name | money |+----+---------+-----------+| 1 | Michael | 1100.0000 || 2 | Jane | 900.0000 || 3 | Kate | 1000.0000 |+----+---------+-----------+3 rows in set (0.00 sec)mysql> select * from record;+----+-----------+---------+----------+---------------------+| id | from_user | to_user | money | time |+----+-----------+---------+----------+---------------------+| 29 | Jane | Michael | 100.0000 | 2017-01-11 12:45:54 |+----+-----------+---------+----------+---------------------+1 row in set (0.00 sec)
运行之后,
mysql> select * from record;+----+-----------+---------+----------+---------------------+| id | from_user | to_user | money | time |+----+-----------+---------+----------+---------------------+| 29 | Jane | Michael | 100.0000 | 2017-01-11 12:45:54 |+----+-----------+---------+----------+---------------------+1 row in set (0.00 sec)mysql> select * from account;+----+---------+-----------+| id | name | money |+----+---------+-----------+| 1 | Michael | 1200.0000 || 2 | Jane | 800.0000 || 3 | Kate | 1000.0000 |+----+---------+-----------+3 rows in set (0.00 sec)
可见,outer transaction运行成功,inner transaction运行失败。因为,inner method抛出RuntimeException,故回滚。由于是两个独立的physical transaction,并且,outer method捕获了inner method抛出的异常并处理,故outer method不会回滚。
NESTED
innerMethod()的事务与outerMethod()的事务共享同一个physical transaction,但是,这个physical transaction中有很多savepoint。每个innerMethod()可以单独还原到savepoint,outerMethod()仍然可以正常提交。但是,如果outerMethod()回滚,则innerMethod()也要回滚。
下面的例子中,outer method为REQUIRED
,inner method为NESTED
。inner method抛出RuntimeException。
@Transactional(propagation=Propagation.REQUIRED) public void transfer(String from, String to, double money) { accountDAO.outMoney(from, money); accountDAO.inMoney(to, money); /* 省略一部分*/ Record record = new Record(from, to, money, currentTime); try { recordService.insertRecord(record); } catch(RuntimeException e) { } } @Transactional(propagation=Propagation.NESTED) public void insertRecord(Record record) { recordDAO.insertRecord(record); throw new RuntimeException("rollback the inner transaction"); }
运行之前,
mysql> select * from account;+----+---------+-----------+| id | name | money |+----+---------+-----------+| 1 | Michael | 1200.0000 || 2 | Jane | 800.0000 || 3 | Kate | 1000.0000 |+----+---------+-----------+3 rows in set (0.00 sec)mysql> select * from record;+----+-----------+---------+----------+---------------------+| id | from_user | to_user | money | time |+----+-----------+---------+----------+---------------------+| 29 | Jane | Michael | 100.0000 | 2017-01-11 12:45:54 |+----+-----------+---------+----------+---------------------+1 row in set (0.00 sec)
运行之后,
mysql> select * from record;+----+-----------+---------+----------+---------------------+| id | from_user | to_user | money | time |+----+-----------+---------+----------+---------------------+| 29 | Jane | Michael | 100.0000 | 2017-01-11 12:45:54 |+----+-----------+---------+----------+---------------------+1 row in set (0.00 sec)mysql> select * from account;+----+---------+-----------+| id | name | money |+----+---------+-----------+| 1 | Michael | 1400.0000 || 2 | Jane | 600.0000 || 3 | Kate | 1000.0000 |+----+---------+-----------+3 rows in set (0.00 sec)
可见,虽然inner method异常,且处于同一个physical transaction, 但是,outer method仍然运行成功。
注意 如果inner method和outer method处于同一个service类中,则以上的叙述不保证正确。
3 总结
REQUIRED
、REQUIRES_NEW
和NESTED
为比较常用的三个属性。
如有疑问,还请提出,共同进步。
参考
- propagation
- Transaction Management
- 浅析Spring事务传播行为和隔离级别
- Spring事务传播行为(Propagation behavior)
- 事务属性之传播行为Propagation Behavior
- 事务属性之传播行为Propagation Behavior
- Spring 事务传播Propagation类型
- Spring事务传播行为
- Spring 事务传播行为
- spring 事务传播行为
- Spring事务传播行为
- spring 事务 传播行为
- Spring事务传播行为
- Spring 事务传播行为
- Spring事务传播行为
- Spring传播行为 种类 PROPAGATION 选项配置
- Spring的传播行为(propagation)
- Spring事务的传播行为和隔离级别[transaction behavior and isolated level](转)
- Spring事务的传播行为
- Spring事务传播行为种类
- spring事务传播行为类型
- java抽象类和接口的区别总结
- Leetcode 463. Island Perimeter
- c++ 一个经典的makefile实例 g++ (8.1是目标文件)
- 虚析构函数
- I/O寄存器的边际效应
- Spring事务传播行为(Propagation behavior)
- gdb简单的调试命令
- 解决Three.js中使用OrbitControls导致不能选中DOM的问题
- 高通dsps部分的打印
- Error:Error converting bytecode to dex: Cause: com.android.dex.DexException: Multiple dex files defi
- Oracle Goldengate在HP平台裸设备文件系统OGG-01028处理
- Linux下CGroup进行CPU内存等资源控制
- Oracle中 (+)与left join 的用法区别
- linux笔记two