一、基本概念

事务的隔离级别,事务传播行为见《事务之二:spring事务(事务管理方式,事务5隔离级别,7个事务传播行为,spring事务回滚条件) 》

二、 嵌套事务示例

2.1、Propagation.REQUIRED+Propagation.REQUIRES_NEW

package dxz.demo1;

@Service
public class ServiceAImpl implements ServiceA { @Autowired
private ServiceB serviceB; @Autowired
private VcSettleMainMapper vcSettleMainMapper; @Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodA() {
String id = IdGenerator.generatePayId("A");
VcSettleMain vc = buildModel(id);
vcSettleMainMapper.insertVcSettleMain(vc);
System.out.println("ServiceAImpl VcSettleMain111:" + vc); serviceB.methodB(); VcSettleMain vc2 = buildModel(id);
vcSettleMainMapper.insertVcSettleMain(vc2);
System.out.println("ServiceAImpl VcSettleMain22222:" + vc2);
} private VcSettleMain buildModel(String id) {
VcSettleMain vc = new VcSettleMain();
vc.setBatchNo(id);
vc.setCreateBy("dxz");
vc.setCreateTime(LocalDateTime.now());
vc.setTotalCount(11L);
vc.setTotalMoney(BigDecimal.ZERO);
vc.setState("5");
return vc;
} }

ServiceB

package dxz.demo1;

@Service
public class ServiceBImpl implements ServiceB { @Autowired
private VcSettleMainMapper vcSettleMainMapper; @Override
@Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = false)
public void methodB() {
String id = IdGenerator.generatePayId("B");
VcSettleMain vc = buildModel(id);
vcSettleMainMapper.insertVcSettleMain(vc);
System.out.println("---ServiceBImpl VcSettleMain:" + vc);
}
}

controller

package dxz.demo1;

@RestController
@RequestMapping("/dxzdemo1")
@Api(value = "Demo1", description="Demo1")
public class Demo1 {
@Autowired
private ServiceA serviceA; /**
* 嵌套事务测试
*/
@PostMapping(value = "/test1")
public String methodA() throws Exception {
serviceA.methodA();
return "ok";
}
}

结果:

看数据库表记录:

这种情况下, 因为 ServiceB#methodB 的事务属性为 PROPAGATION_REQUIRES_NEW,ServiceB是一个独立的事务,与外层事务没有任何关系。如果ServiceB执行失败(上面示例中让ServiceB的id为已经存在的值),ServiceA的调用出会抛出异常,导致ServiceA的事务回滚。

并且, 在 ServiceB#methodB 执行时 ServiceA#methodA 的事务已经挂起了 (关于事务挂起的内容已经超出了本文的讨论范围)。

2.2、Propagation.REQUIRED+Propagation.REQUIRED

//ServiceA
//...
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodA() { //ServiceB
//...
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodB(String id) {
//...

--“1”可插入,“2”可插入,“3”不可插入:

结果是“1”,“2”,“3”都不能插入,“1”,“2”被回滚。

--“1”可插入,“2”不可插入,“3”可插入:

结果是“1”,“2”,“3”都不能插入,“1”,“2”被回滚。

2.3、Propagation.REQUIRED+无事务注解

//ServiceA
//...
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodA() { //ServiceB
//...
@Override
//没有加事务注解
public void methodB(String id) {
//...

--“1”可插入,“2”可插入,“3”不可插入:

结果是“1”,“2”,“3”都不能插入,“1”,“2”被回滚。

2.4、内层事务被try-catch:

2.4.1、trycatch+Propagation.REQUIRED+Propagation.REQUIRED

//ServiceA
//...
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodA() {
try {
serviceB.methodB(id);
} catch (Exception e) {
System.out.println("内层事务出错啦。");
}
} //ServiceB
//...
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodB(String id) {
//...

--“1”可插入,“2”不可插入,“3”可插入:

结果是“1”,“2”,“3”都不能插入,“1”被回滚。

事务设置为Propagation.REQUIRED时,如果内层方法抛出Exception,外层方法中捕获Exception但是并没有继续向外抛出,最后出现“Transaction rolled back because it has been marked as rollback-only”的错误。外层的方法也将会回滚。

其原因是:内层方法抛异常返回时,transacation被设置为rollback-only了,但是外层方法将异常消化掉,没有继续向外抛,那么外层方法正常结束时,transaction会执行commit操作,但是transaction已经被设置为rollback-only了。所以,出现“Transaction rolled back because it has been marked as rollback-only”错误。

2.4.2、trycatch+Propagation.REQUIRED+Propagation.NESTED

//ServiceA
//...
@Override
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public void methodA() {
try {
serviceB.methodB(id);
} catch (Exception e) {
System.out.println("内层事务出错啦。");
}
} //ServiceB
//...
@Override
@Transactional(propagation = Propagation.NESTED, readOnly = false)
public void methodB(String id) {
//...

--“1”可插入,“2”不可插入,“3”可插入:

结果是“1”,“3"记录插入成功,“2”记录插入失败。

说明:

当内层配置成 PROPAGATION_NESTED, 此时两者之间又将如何协作呢? 从 Juergen Hoeller 的原话中我们可以找到答案, ServiceB#methodB 如果 rollback, 那么内部事务(即 ServiceB#methodB) 将回滚到它执行前的 SavePoint(注意, 这是本文中第一次提到它, 潜套事务中最核心的概念), 而外部事务(即 ServiceA#methodA) 可以有以下两种处理方式:
1、内层失败,外层调用其它分支,代码如下

ServiceA {  

    /**
* 事务属性配置为 PROPAGATION_REQUIRED
*/
void methodA() {
try {
ServiceB.methodB();
} catch (SomeException) {
// 执行其他业务, 如 ServiceC.methodC();
}
} }

这种方式也是潜套事务最有价值的地方, 它起到了分支执行的效果, 如果 ServiceB.methodB 失败, 那么执行 ServiceC.methodC(), 而 ServiceB.methodB 已经回滚到它执行之前的 SavePoint, 所以不会产生脏数据(相当于此方法从未执行过), 这种特性可以用在某些特殊的业务中, 而 PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRES_NEW 都没有办法做到这一点。
2. 代码不做任何修改, 那么如果内部事务(即 ServiceB#methodB) rollback, 那么首先 ServiceB.methodB 回滚到它执行之前的 SavePoint(在任何情况下都会如此), 外部事务(即 ServiceA#methodA) 将根据具体的配置决定自己是 commit 还是 rollback。

三、嵌套事务总结

使用嵌套事务的场景有两点需求:

  • 需要事务BC与事务AD一起commit,即:作为事务AD的子事务,事务BC只有在事务AD成功commit时(阶段3成功)才commit。这个需求简单称之为“联合成功”。这一点PROPAGATION_NESTED和PROPAGATION_REQUIRED可以做到。
  • 需要事务BC的rollback不(无条件的)影响事务AD的commit。这个需求简单称之为“隔离失败”。这一点PROPAGATION_NESTED和PROPAGATION_REQUIRES_NEW可以做到。

分解下,可知PROPAGATION_NESTED的特殊性有:

1、使用PROPAGATION_REQUIRED满足需求1,但子事务BC的rollback会无条件地使父事务AD也rollback,不能满足需求2。即使对子事务进行了try-catch,父事务AD也不能commit。示例见2.4.1、trycatch+Propagation.REQUIRED+Propagation.REQUIRED

2、使用PROPAGATION_REQUIRES_NEW满足需求2,但子事务(这时不应该称之为子事务)BC是完全新的事务上下文,父事务(这时也不应该称之为父事务)AD的成功与否完全不影响BC的提交,不能满足需求1。

同时满足上述两条需求就要用到PROPAGATION_NESTED了。PROPAGATION_NESTED在事务AD执行到B点时,设置了savePoint(关键)。

当BC事务成功commit时,PROPAGATION_NESTED的行为与PROPAGATION_REQUIRED一样。只有当事务AD在D点成功commit时,事务BC才真正commit,如果阶段3执行异常,导致事务AD rollback,事务BC也将一起rollback ,从而满足了“联合成功”。

当阶段2执行异常,导致BC事务rollback时,因为设置了savePoint,AD事务可以选择与BC一起rollback或继续阶段3的执行并保留阶段1的执行结果,从而满足了“隔离失败”。

当然,要明确一点,事务传播策略的定义是在声明或事务管理范围内的(首先是在EJB CMT规范中定义,Spring事务框架补充了PROPAGATION_NESTED),编程式的事务管理不存在事务传播的问题。

四、PROPAGATION_NESTED的必要条件

上面大致讲述了潜套事务的使用场景, 下面我们来看如何在 spring 中使用 PROPAGATION_NESTED, 首先来看 AbstractPlatformTransactionManager

/**
* Create a TransactionStatus for an existing transaction.
*/
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException { ... 省略 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 =
newTransactionStatus(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.
doBegin(transaction, definition);
boolean newSynchronization = (this.transactionSynchronization != SYNCHRONIZATION_NEVER);
return newTransactionStatus(definition, transaction, true, newSynchronization, debugEnabled, null);
}
}
}

1. 我们要设置 transactionManager 的 nestedTransactionAllowed 属性为 true, 注意, 此属性默认为 false!!! 
再看 AbstractTransactionStatus#createAndHoldSavepoint() 方法

/**
* Create a savepoint and hold it for the transaction.
* @throws org.springframework.transaction.NestedTransactionNotSupportedException
* if the underlying transaction does not support savepoints
*/
public void createAndHoldSavepoint() throws TransactionException {
setSavepoint(getSavepointManager().createSavepoint());
}

可以看到 Savepoint 是 SavepointManager.createSavepoint 实现的, 再看 SavepointManager 的层次结构, 发现 
  其 Template 实现是 JdbcTransactionObjectSupport, 常用的 DatasourceTransactionManager, HibernateTransactionManager 
  中的 TransactonObject 都是它的子类 : 
  JdbcTransactionObjectSupport 告诉我们必须要满足两个条件才能 createSavepoint : 
 2. java.sql.Savepoint 必须存在, 即 jdk 版本要 1.4+ 
 3. Connection.getMetaData().supportsSavepoints() 必须为 true, 即 jdbc drive 必须支持 JDBC 3.0 
确保以上条件都满足后, 你就可以尝试使用 PROPAGATION_NESTED 了。

事务之六:spring 嵌套事务的更多相关文章

  1. MySQL事务及Spring事务管理

    事务,是在数据库中用于保证数据正确性的一种机制,涉及到很多概念以及不同的情况,这里做一个总结 相关概念 事务四特性(ACID) 原子性(Atomicity,或称不可分割性):要么全部完成或者全部不完成 ...

  2. 事务以及Spring的事务管理

    一.什么是事务? 事务是逻辑上的一组操作,要么都执行,要么都不执行 二.事务的特性(ACID) 原子性: 事务是最小的执行单位,不允许分割.事务的原子性确保动作要么全部完成,要么完全不起作用: 一致性 ...

  3. 【Spring】Spring的事务管理 - 1、Spring事务管理概述(数据库事务、Spring事务管理的核心接口)

    Spring事务管理概述 文章目录 Spring事务管理概述 数据库事务 什么是Spring的事务管理? Spring对事务管理的支持 Spring事务管理的核心接口 Platform Transac ...

  4. REQUIRES_NEW 如果不在一个事务那么自己创建一个事务 如果在一个事务中 自己在这个大事务里面在创建一个子事务 相当于嵌套事务 双层循环那种

    REQUIRES_NEW   如果不在一个事务那么自己创建一个事务 如果在一个事务中 自己在这个大事务里面在创建一个子事务  相当于嵌套事务 双层循环那种 不管是否存在事务,业务方法总会自己开启一个事 ...

  5. atitit.spring hibernate的事务机制 spring不能保存对象的解决

    atitit.spring hibernate的事务机制 spring不能保存对象的解决 sessionFactory.openSession() 不能..log黑头马sql语言.. sessionF ...

  6. 使用spring声明式事务,spring使用AOP来支持声明式事务,会根据事务属性,自动在方法调用之前决定是否开启一个事务,并在方法执行之后决定事务提交或回滚事务。

    使用spring声明式事务,spring使用AOP来支持声明式事务,会根据事务属性,自动在方法调用之前决定是否开启一个事务,并在方法执行之后决定事务提交或回滚事务.

  7. 数据库事务和spring事务的区别

    数据库事务和spring事务 本质上其实是同一个概念,spring的事务是对数据库的事务的封装,最后本质的实现还是在数据库,假如数据库不支持事务的话,spring的事务是没有作用的.数据库的事务说简单 ...

  8. 数据库程序接口——JDBC——功能第四篇——事务之Spring事务

    综述 事务的实现方式有三种,JTA,Spring事务,Web Container方式.本篇讲述Spring事务. Spring事务分为两个部分核心对象,Spring事务的实现方式. Spring事务实 ...

  9. 【spring源码学习】spring事务中的嵌套事务中的保存点相关知识

    JDBC事务保存点(setSavepoint, releaseSavepoint )实例 以下是使用事务教程中描述的setSavepoint和回滚的代码示例. 此示例代码是基于前面章节中完成的环境和数 ...

随机推荐

  1. Erlang节点重启导致的incarnation问题(转)

    转自霸爷的博客: 转载自系统技术非业余研究 本文链接地址: Erlang节点重启导致的incarnation问题 遇到个问题, =ERROR REPORT==== 10-Mar-2016::09:44 ...

  2. AOS应用基础平台-模块开发流程

    AOS平台简单介绍 AOS应用基础平台基于JavaEE技术体系,以"标准功能可复用.通用模块可配置.行业需求高速开发.异构系统无缝集成"为目标.为软件开发团队提供高效可控.随需应变 ...

  3. python 基础 7.4 os 模块

    #/usr/bin/python #coding=utf8 #@Time   :2017/11/11 3:15 #@Auther :liuzhenchuan #@File   :os 模块.py im ...

  4. EasyPusher进行Android UVC外接摄像头直播推送实现方法

    最近EasyPusher针对UVC摄像头做了适配.我们结合了UVCCamera与EasyPusher,支持将UVC摄像头的视频推送到RTSP服务器上.在此特别感谢UVCCamera这个牛逼的项目! 来 ...

  5. vue-cli与后台数据交互增删改查

    1. 安装vue-resource npm install vue-resource --save 2.访问后台地址,在vue中会出现跨域的问题,以下为解决方案 在config下的index.js 中 ...

  6. My97datepicker日期控件

    转自:http://www.my97.net/dp/demo/index.htm 非常不错的一篇文章,介绍的很详细.感兴趣的朋友可以好好研究一下. 网上资源很多的,可以下一个来使用. 先说一下整个文件 ...

  7. ubuntun下安装sublime text

    Sublime Text 3 是一款轻量级.跨平台的文本编辑器.可安装在ubuntu,Windows和MAC OS X上高级文本编辑软件,有一个专有的许可证,但该程序也可以免费使用,无需做逆向工程.如 ...

  8. quick-cocos2d-x教程11:实现http通信,并与站点php对接,可实现登录等常见功能

    手机游戏眼下是弱联网居多,http登录是经常使用功能.我们如今就来实现. 在启动时候.自己主动请求http. function MainScene:ctor()     local url = &qu ...

  9. PHP加Nginx实现动态裁剪图片方案

    许久以前写过一篇也是关于高性能PHP图片动态裁剪方案的文章,那文章使用的是nginx Cache和rewrite实现的,当然再加上CDN,那个方案存在一个问题就是图片并没有实际生成,而是以二进制的形式 ...

  10. 【shell】常用的正则表达式

    一.校验数字的表达式 1 数字:^[0-9]*$ 2 n位的数字:^\d{n}$ 3 至少n位的数字:^\d{n,}$ 4 m-n位的数字:^\d{m,n}$ 5 零和非零开头的数字:^(0|[1-9 ...