总结
NESTED 似乎与REQUIRED 是一样的,但是他们是不同的。
若a 调用b 方法。a方法为REQUIRED,且在a中捕获b方法异常。注意 a ,b 方法不要在一个service中,不然事务不生效。
若b方法为REQUIRED@Transactional(propagation=Propagation.REQUIRED)
a(){
a插入数据库;
try {
b();
} catch (Exception e) {
e.printStackTrace();
}
}
@Transactional(propagation=Propagation.REQUIRED)
b(){
b插入数据库;
int xx=1/0;//抛出异常
}
则a,b的插入都会回滚,即使a中加了捕获b()异常的代码。同时a方法会抛出异常:org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only,异常原因可参考:Spring事务管理报错:Transaction rolled back because it has been marked as rollback-only_科西嘉狮子的博客-CSDN博客
若b方法为NESTED
@Transactional(propagation=Propagation.REQUIRED)
a(){
a插入数据库;
try {
b();
} catch (Exception e) {
e.printStackTrace();
}
}
@Transactional(propagation=Propagation.NESTED)
b(){
b插入数据库;
int xx=1/0;//抛出异常
}
上述代码 则只有b的插入回滚,a不回滚。原因是在进入NESTED方法时事务保存了当前b的savepoint,b异常只会回滚到b的savepoint。
同时若在a方法中抛出异常,则a,b插入都会回滚@Transactional(propagation=Propagation.REQUIRED)
a(){
a插入数据库;
try {
b();
} catch (Exception e) {
e.printStackTrace();
}
int xx=1/0;//抛出异常
}
@Transactional(propagation=Propagation.NESTED)
b(){
b插入数据库;
}
亲测代码示例
@Service
public class Test3Service {
@Autowired
private TestMapper testMapper;
@Autowired
private Test4Service test4Service;
@Transactional(propagation = Propagation.REQUIRED)
public void test1() {
testMapper.add3();
try {
//add3,add4 都回滚,同时当test1()方法抛出异常Transaction rolled back because it has been marked as rollback-only"
test4Service.add4();
//add3不会滚,add4_1回滚
// test4Service.add4_1();
//add3不会滚,add4_2也不回滚
// test4Service.add4_2();
} catch (Exception e) {
e.printStackTrace();
}
}
@Transactional(propagation = Propagation.REQUIRED)
public void test2() {
testMapper.add3();
test4Service.add4_0();
//add3 add4_0都回滚
int i=1/0;
}
}
@Service
public class Test4Service {
@Autowired
private TestMapper testMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void add4() {
testMapper.add4();//插入mysql
int a=1/0;
}
@Transactional(propagation = Propagation.NESTED)
public void add4_0() {
testMapper.add4();//插入mysql
}
@Transactional(propagation = Propagation.NESTED)
public void add4_1() {
testMapper.add4();//插入mysql
int a=1/0;
}
//不要事务
public void add4_2() {
testMapper.add4();//插入mysql
int a=1/0;
}
}
@Mapper
@Repository
public interface TestMapper {
@Insert("insert into test3 (name) values('2')")
int add3();
@Insert("insert into test4 (name) values('2')")
int add4();
}
关于spring事务传播机制的解释
播行为 | 意义 |
PROPAGATION_REQUIRES | 表示当前方法必须在一个事务中运行。如果一个现有事务正在进行中,该方法将在那个事务中运行,否则就要开始一个新事务。 |
PROPAGATION_SUPPORTS | 表示当前方法不需要事务性上下文,但是如果有一个事务已经在运行的话,它也可以在这个事务里运行。 |
PROPAGATION_MANDATORY | 表示该方法必须运行在一个事务中。如果当前没有事务正在发生,将抛出一个异常。 |
PROPAGATION_REQUIRES_NEW | 表示当前方法必须在它自己的事务里运行。一个新的事务将被启动,而且如果有一个现有事务在运行的话,则将在这个方法运行期间被挂起。 |
PROPAGATION_NOT_SUPPORTED | 表示当前方法不需要事务性上下文,但是如果有一个事务已经在运行的话,它也可以在这个事务里运行。 |
PROPAGATION_NEVER | 表示当前的方法不应该在一个事务中运行。如果一个事务正在进行,则会抛出一个异常。 |
PROPAGATION_NESTED | 表示如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于封装事务进行提交或回滚。如果封装事务不存在,行为就像PROPAGATION_REQUIRES一样。 |
这个例子是基于 spring 事务管理之事务传播行为之实践REQUIRED(一) - 简书 这个文章的代码
首先,InsertUsers和InsertCuser方法上都申明了REQUIRED,让他们属于同一个事务。将引发异常的语句
int i = 1/0;
放到 InsertCuser方法里
@Transactional(propagation = Propagation.REQUIRED)
@Override
public void InsertUsers(Users users) {
jdbcTemplate.update("INSERT INTO users(id,name, age, email) VALUES (?, ?, ?, ?);", users.getId(), users.getName(), users.getAge(), users.getEmail());
//调用service中另一个方法
Cuser cuser = new Cuser(users.getId(), users.getName(), users.getAge(), users.getEmail());
//打印事务名
List
@Transactional(propagation=Propagation.REQUIRED)
@Override
public void InsertCuser(Cuser cuser) {
jdbcTemplate.update("INSERT INTO cuser(id,name, age, email) VALUES (?, ?, ?, ?);", cuser.getId(), cuser.getName(), cuser.getAge(), cuser.getEmail());
//打印事务名
List
注意
为什么要加try/catch包裹cuserService.InsertCuser(cuser);语句?
为了杜绝InsertCuser中抛出的异常影响InsertUsers方法的实验结果
try {
cuserService.InsertCuser(cuser);
} catch (Exception e) {
e.printStackTrace();
}
程序运行,结果是InsertCuser中出现异常,导致事务回滚、users表和cuser表均无数据插入。由于两个方法被纳入同一个事务,因此两者都会回滚。即使在cuserService.InsertCuser(cuser);上使用try/catch捕获并不抛出异常也没用(此方法能保证调用者方法中的独立事务不受被调用者抛出的异常影响而回滚)
我们再来看,将上面环境的InsertCuser方法传播行为改成NESTED
@Transactional(propagation=Propagation.NESTED)
@Override
public void InsertCuser(Cuser cuser) {
jdbcTemplate.update("INSERT INTO cuser(id,name, age, email) VALUES (?, ?, ?, ?);", cuser.getId(), cuser.getName(), cuser.getAge(), cuser.getEmail());
//打印事务名
List
再次运行,可以看到日志的打印情况。两方法的事务的id一致,说明的确是相同事务
users表中插入了数据说明InsertUsers方法提交成功,cuser表中没有数据说明InsertCuser方法回滚
那么现在就有一个问题了,既然两个方法使用同一个事务,为什么没有一起回滚?
这就是NESTED嵌套事务的奥秘之处-----它能让事务部分回滚
我在网上找到了一句话:
NESTED申明在被调用方法上,若调用者方法有开启事务。此时NESTED会开始一个 "嵌套的" 事务, 它是已经存在事务的一个真正的子事务。 潜套事务开始执行时, 它将取得一个
savepoint
。 如果这个嵌套事务失败, 我们将回滚到此savepoint
。 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交。
这段话中提到的 savepoint
其实是mysql的innodb引擎的特性,为了去了解它我在mysql客户端对它进行了简单使用,可以看看这篇文章mysql 事务之使用savepoint部分回滚 - 简书 。 总之它就是一个保存点,生成一个保存点就是生成一个数据镜像。然后无论经过了什么sql *** 作,只要使用回滚至此保存点的命令即可恢复至创建保存点的数据状态。
那么上面代码的演示结果也就说的通了。即使InsertUsers和InsertCuser方法属于同一个事务,被NESTED嵌套事务申明的InsertCuser方法出现异常也没导致REQUIRED申明的InsertUsers的全部回滚
,只是部分回滚
到了调用InsertCuser方法之前。因为在调用InsertCuser方法时会自动生成一个savepoint
InsertUsers方法里出现异常会导致InsertCuser方法嵌套事务回滚吗?
将出现异常的代码行放到这里,结果都回滚了,毕竟是同一个事务
总结下NESTED的回滚特性
- 主事务和嵌套事务属于同一个事务
- 嵌套事务出错回滚不会影响到主事务
- 主事务回滚会将嵌套事务一起回滚了
进一步证明NESTED嵌套事务的savepoint机制
可以通过阅读spring源码的方式来验证NESTED是不是使用savepoint机制来实现的,我现在的水平还不足以去阅读源码。但是我会很快就有这个能力的,我相信! 不过有简友已经分析过了,可以去看看。写的很好~
Spring 采用保存点(Savepoint)实现嵌套事务原理 - 简书
业务同步单子的逻辑很简单,把原单重复发起,我们这边做下幂等返回库中的数据就好,其实这里已经差不多能看出原因了,处理逻辑中套了两层事务,我们知道@Transaction这个注解是基于切面实现的,类似于下面的代码
public void testTransactional(){
DefaultTransactionDefinition defaultTransactionDefinition = new DefaultTransactionDefinition();
defaultTransactionDefinition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus transaction = transactionManager.getTransaction(defaultTransactionDefinition);
try{
//do something
} catch (Exception e) {
transactionManager.rollback(transaction);
throw e;
}
transactionManager.commit(transaction);
}
最外面一层采用的默认REQUIRED传播方式,内层插入也采用这种插入方式,并且内外层是一个事务,当内层事务抛出异常的时候会回滚整个事务,所以下面查询库中信息就有问题了,事务已经回滚了,因此抛出上述异常。
NESTED事务跟REQUIRED事务区别就在这里,NESTED事务是回滚到回滚点,而回滚点生成是在进入内嵌事务的时候,外面事务是不会回滚的
具体创建回滚点代码是在这里
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
if (!isNestedTransactionAllowed()) {
throw new NestedTransactionNotSupportedException(
"Transaction manager does not allow nested transactions by default - " +
"specify 'nestedTransactionAllowed' property with value 'true'");
}
if (debugEnabled) {
logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
}
if (useSavepointForNestedTransaction()) {
// Create savepoint within existing Spring-managed transaction,
// through the SavepointManager API implemented by TransactionStatus.
// Usually uses JDBC 3.0 savepoints. Never activates Spring synchronization.
DefaultTransactionStatus status =
prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
status.createAndHoldSavepoint();
return status;
}
else {
// Nested transaction through nested begin and commit/rollback calls.
// Usually only for JTA: Spring synchronization might get activated here
// in case of a pre-existing JTA transaction.
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, null);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
}
处理回滚点是在processRollback中
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
status.rollbackToHeldSavepoint();
}
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
doRollback(status);
}
@Override
public void rollbackToSavepoint(Object savepoint) throws TransactionException {
ConnectionHolder conHolder = getConnectionHolderForSavepoint();
try {
conHolder.getConnection().rollback((Savepoint) savepoint);
}
catch (Throwable ex) {
throw new TransactionSystemException("Could not roll back to JDBC savepoint", ex);
}
}
@Override
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
}
try {
con.rollback();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
}
}
spring事务传播机制NESTED和REQUIRED的区别_wit_cx的博客-CSDN博客_nested和required的区别
Spring事务传播方式 REQUIRED 与 NESTED 踩坑_Carisy的博客-CSDN博客
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)