事务传播方式

本文详细解析了Spring中事务的传播行为(Propagation)及隔离级别(Isolation Level),并通过实例说明了不同传播行为如何影响事务的执行流程。此外,还讨论了事务的只读属性与超时设置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

文章一:
我们都知道事务的概念,那么事务的传播特性是什么呢?(此处着重介绍传播特性的概念,关于传播特性的相关配置就不介绍了,可以查看spring的官方文档)
在我们用SSH开发项目的时候,我们一般都是将事务设置在Service层 那么当我们调用Service层的一个方法的时候它能够保证我们的这个方法中执行的所有的对数据库的更新操作保持在一个事务中,在事务层里面调用的这些方法要么全部成功,要么全部失败。那么事务的传播特性也是从这里说起的。
如果你在你的Service层的这个方法中,除了调用了Dao层的方法之外,还调用了本类的其他的Service方法,那么在调用其他的Service方法的时候,这个事务是怎么规定的呢,我必须保证我在我方法里掉用的这个方法与我本身的方法处在同一个事务中,否则如果保证事物的一致性。事务的传播特性就是解决这个问题的,“事务是会传播的”在Spring中有针对传播特性的多种配置我们大多数情况下只用其中的一种:PROPGATION_REQUIRED:这个配置项的意思是说当我调用service层的方法的时候开启一个事务(具体调用那一层的方法开始创建事务,要看你的aop的配置),那么在调用这个service层里面的其他的方法的时候,如果当前方法产生了事务就用当前方法产生的事务,否则就创建一个新的事务。这个工作使由Spring来帮助我们完成的。
以前没有Spring帮助我们完成事务的时候我们必须自己手动的控制事务,例如当我们项目中仅仅使用hibernate,而没有集成进spring的时候,我们在一个service层中调用其他的业务逻辑方法,为了保证事物必须也要把当前的hibernate session传递到下一个方法中,或者采用ThreadLocal的方法,将session传递给下一个方法,其实都是一个目的。现在这个工作由spring来帮助我们完成,就可以让我们更加的专注于我们的业务逻辑。而不用去关心事务的问题。

默认情况下当发生RuntimeException的情况下,事务才会回滚,所以要注意一下 如果你在程序发生错误的情况下,有自己的异常处理机制定义自己的Exception,必须从RuntimeException类继承 这样事务才会回滚!


文章二:

事务的传播行为和隔离级别[transaction behavior and isolated level]
Spring中事务的定义:
一、Propagation :
  key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。有以下选项可供使用:
PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。

很多人看到事务的传播行为属性都不甚了解,我昨晚看了j2ee without ejb的时候,看到这里也不了解,甚至重新翻起数据库系统的教材书,但是也没有找到对这个的分析。今天搜索,找到一篇极好的分析文章,虽然这篇文章是重点分析PROPAGATION_REQUIRED 和 PROPAGATION_REQUIRED_NESTED的
解惑 spring 嵌套事务
********TransactionDefinition 接口定义*******************

int PROPAGATION_REQUIRED = 0;



int PROPAGATION_SUPPORTS = 1;

int PROPAGATION_MANDATORY = 2;



int PROPAGATION_REQUIRES_NEW = 3;


int PROPAGATION_NOT_SUPPORTED = 4;


int PROPAGATION_NEVER = 5;


int PROPAGATION_NESTED = 6;
*************************************************************************
在这篇文章里,他用两个嵌套的例子辅助分析,我这里直接引用了。
********************sample***********************
ServiceA {


void methodA() {
ServiceB.methodB();
}

}

ServiceB {


void methodB() {
}

}
*************************************************
我们这里一个个分析吧
1: PROPAGATION_REQUIRED
加入当前正要执行的事务不在另外一个事务里,那么就起一个新的事务
比如说,ServiceB.methodB的事务级别定义为PROPAGATION_REQUIRED, 那么由于执行ServiceA.methodA的时候,
ServiceA.methodA已经起了事务,这时调用ServiceB.methodB,ServiceB.methodB看到自己已经运行在ServiceA.methodA
的事务内部,就不再起新的事务。而假如ServiceA.methodA运行的时候发现自己没有在事务中,他就会为自己分配一个事务。
这样,在ServiceA.methodA或者在ServiceB.methodB内的任何地方出现异常,事务都会被回滚。即使ServiceB.methodB的事务已经被
提交,但是ServiceA.methodA在接下来fail要回滚,ServiceB.methodB也要回滚
2: PROPAGATION_SUPPORTS
如果当前在事务中,即以事务的形式运行,如果当前不再一个事务中,那么就以非事务的形式运行
这就跟平常用的普通非事务的代码只有一点点区别了。不理这个,因为我也没有觉得有什么区别
3: PROPAGATION_MANDATORY
必须在一个事务中运行。也就是说,他只能被一个父事务调用。否则,他就要抛出异常。
4: PROPAGATION_REQUIRES_NEW
这个就比较绕口了。 比如我们设计ServiceA.methodA的事务级别为PROPAGATION_REQUIRED,ServiceB.methodB的事务级别为PROPAGATION_REQUIRES_NEW,
那么当执行到ServiceB.methodB的时候,ServiceA.methodA所在的事务就会挂起,ServiceB.methodB会起一个新的事务,等待ServiceB.methodB的事务完成以后,
他才继续执行。他与PROPAGATION_REQUIRED 的事务区别在于事务的回滚程度了。因为ServiceB.methodB是新起一个事务,那么就是存在
两个不同的事务。如果ServiceB.methodB已经提交,那么ServiceA.methodA失败回滚,ServiceB.methodB是不会回滚的。如果ServiceB.methodB失败回滚,
如果他抛出的异常被ServiceA.methodA捕获,ServiceA.methodA事务仍然可能提交。
5: PROPAGATION_NOT_SUPPORTED
当前不支持事务。比如ServiceA.methodA的事务级别是PROPAGATION_REQUIRED ,而ServiceB.methodB的事务级别是PROPAGATION_NOT_SUPPORTED ,
那么当执行到ServiceB.methodB时,ServiceA.methodA的事务挂起,而他以非事务的状态运行完,再继续ServiceA.methodA的事务。
6: PROPAGATION_NEVER
不能在事务中运行。假设ServiceA.methodA的事务级别是PROPAGATION_REQUIRED, 而ServiceB.methodB的事务级别是PROPAGATION_NEVER ,
那么ServiceB.methodB就要抛出异常了。
7: PROPAGATION_NESTED
理解Nested的关键是savepoint。他与PROPAGATION_REQUIRES_NEW的区别是,PROPAGATION_REQUIRES_NEW另起一个事务,将会与他的父事务相互独立,
而Nested的事务和他的父事务是相依的,他的提交是要等和他的父事务一块提交的。也就是说,如果父事务最后回滚,他也要回滚的。
而Nested事务的好处是他有一个savepoint。
*****************************************
ServiceA {


void methodA() {
try {
//savepoint
ServiceB.methodB(); //PROPAGATION_NESTED 级别
} catch (SomeException) {
// 执行其他业务, 如 ServiceC.methodC();
}
}

}
********************************************
也就是说ServiceB.methodB失败回滚,那么ServiceA.methodA也会回滚到savepoint点上,ServiceA.methodA可以选择另外一个分支,比如
ServiceC.methodC,继续执行,来尝试完成自己的事务。
但是这个事务并没有在EJB标准中定义。

二、Isolation Level(事务隔离等级):
1、Serializable:最严格的级别,事务串行执行,资源消耗最大;

2、REPEATABLE READ:保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。

3、READ COMMITTED:大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。

4、Read Uncommitted:保证了读取过程中不会读取到非法数据。
隔离级别在于处理多事务的并发问题。
我们知道并行可以提高数据库的吞吐量和效率,但是并不是所有的并发事务都可以并发运行,这需要查看数据库教材的可串行化条件判断了。
这里就不阐述。
我们首先说并发中可能发生的3中不讨人喜欢的事情
1: Dirty reads--读脏数据。也就是说,比如事务A的未提交(还依然缓存)的数据被事务B读走,如果事务A失败回滚,会导致事务B所读取的的数据是错误的。

2: non-repeatable reads--数据不可重复读。比如事务A中两处读取数据-total-的值。在第一读的时候,total是100,然后事务B就把total的数据改成200,事务A再读一次,结果就发现,total竟然就变成200了,造成事务A数据混乱。

3: phantom reads--幻象读数据,这个和non-repeatable reads相似,也是同一个事务中多次读不一致的问题。但是non-repeatable reads的不一致是因为他所要取的数据集被改变了(比如total的数据),但是phantom reads所要读的数据的不一致却不是他所要读的数据集改变,而是他的条件数据集改变。比如Select account.id where account.name="ppgogo*",第一次读去了6个符合条件的id,第二次读取的时候,由于事务b把一个帐号的名字由"dd"改成"ppgogo1",结果取出来了7个数据。
Dirty reads non-repeatable reads phantom reads
Serializable 不会 不会 不会
REPEATABLE READ 不会 不会 会
READ COMMITTED 不会 会 会
Read Uncommitted 会 会 会

三、readOnly
事务属性中的readOnly标志表示对应的事务应该被最优化为只读事务。这是一个最优化提示。在一些情况下,一些事务策略能够起到显著的最优化效果,例如在使用Object/Relational映射工具(如:Hibernate或TopLink)时避免dirty checking(试图“刷新”)。
四、Timeout
在事务属性中还有定义“timeout”值的选项,指定事务超时为几秒。在JTA中,这将被简单地传递到J2EE服务器的事务协调程序,并据此得到相应的解释。
<think>好的,用户现在想了解Spring Boot中事务传播方式的使用方法以及解决saveBatch方法事务失效的问题。之前已经回答过类似的问题,但需要结合之前的对话来提供更详细的解答。 首先,回顾用户之前的提问,他提到调用其他服务的saveBatch方法时事务失效,之前的回答已经分析了原因,比如代理机制失效、跨服务调用方式错误、事务传播配置不当等,并给出了解决方案。现在用户进一步询问事务传播行为的详解以及saveBatch事务失效的具体解决方案,需要更详细地展开。 用户可能的需求是深入理解事务传播机制,特别是不同的传播行为如REQUIRED、REQUIRES_NEW等的应用场景,以及如何正确配置这些行为来避免事务失效。此外,用户可能在实践中遇到了saveBatch方法的事务不生效的问题,需要具体的解决步骤。 需要确认用户是否了解Spring事务的基本原理,比如AOP代理、事务管理器等。因为事务传播行为是建立在Spring的事务管理之上的,如果用户对这些基础概念不熟悉,可能需要先简要说明。但根据用户之前的提问,可能已经有一定了解,所以可以深入一些。 在解释事务传播行为时,要列举七种类型,但重点放在常用的几种,比如REQUIRED、REQUIRES_NEW、NESTED等,给出具体的应用场景和代码示例。例如,当方法A调用方法B时,不同传播行为下事务如何创建或加入现有事务。 对于saveBatch方法的事务失效,需要再次强调可能的原因,比如自调用导致代理失效、未正确注入服务、批量操作未启用事务等。同时,结合MyBatis Plus的saveBatch方法,说明需要配置事务管理器和批量操作的优化,比如设置合适的batchSize,启用事务支持。 还要注意用户可能遇到的特殊情况,比如跨服务调用时的事务传播,这时候可能需要分布式事务解决方案如Seata,但用户当前的问题可能集中在单服务内的事务处理,所以先解决单服务的问题,再提及分布式的情况作为扩展。 另外,用户可能需要验证事务是否生效的方法,比如抛出异常看是否回滚,或者查看事务日志,这些在之前的回答中已经提到过,可以再次强调。 最后,确保回答结构清晰,分点讲解事务传播行为,再针对saveBatch的问题给出解决方案,并附上相关问题,帮助用户进一步学习。</think>以下是Spring Boot事务传播行为的详解及`saveBatch`方法事务失效解决方案的技术解析: --- ### 一、Spring事务传播行为详解(7种模式) #### 1. 核心传播行为(标*为常用) | 传播类型 | 描述 | 代码示例 | |-------------------|---------------------------------------------------------------------|-------------------------------------------------------------------------| | **REQUIRED*** | 默认值,存在事务则加入,无事务则新建(原子操作场景) | `@Transactional(propagation = Propagation.REQUIRED)` | | **REQUIRES_NEW*** | 总是新建独立事务(日志记录/审计等独立操作) | `@Transactional(propagation = Propagation.REQUIRES_NEW)` | | **NESTED** | 嵌套事务,使用保存点实现部分回滚(电商订单多步骤处理) | `@Transactional(propagation = Propagation.NESTED)` | | SUPPORTS | 跟随当前事务,无事务则以非事务执行(查询优化) | `@Transactional(readOnly = true, propagation = Propagation.SUPPORTS)` | | NOT_SUPPORTED | 非事务执行,挂起当前事务(耗时统计等与事务无关操作) | `@Transactional(propagation = Propagation.NOT_SUPPORTED)` | | MANDATORY | 强制要求存在事务,否则抛异常(严格事务依赖场景) | `@Transactional(propagation = Propagation.MANDATORY)` | | NEVER | 强制非事务环境,存在事务则抛异常(非核心业务校验) | `@Transactional(propagation = Propagation.NEVER)` | #### 2. 典型应用场景对比 - **REQUIRED vs REQUIRES_NEW** ```java // 主事务 @Transactional public void mainMethod() { // 操作1(参与主事务) subMethod1(); // 操作2(独立事务) subMethod2(); } @Transactional(propagation = REQUIRED) public void subMethod1() { /*...*/ } @Transactional(propagation = REQUIRES_NEW) public void subMethod2() { /*...*/ } ``` 若`subMethod2`发生异常,仅回滚其独立事务,`subMethod1`的操作仍随主事务回滚[^3] #### 3. 嵌套事务实现原理 ```mermaid graph TD A[主事务BEGIN] --> B[设置保存点] B --> C[执行嵌套操作] C -->|异常| D[回滚到保存点] C -->|成功| E[释放保存点] ``` --- ### 二、`saveBatch`事务失效解决方案 #### 1. 失效原因深度分析 - **批量操作未启用事务** ```sql -- MyBatis Plus默认执行流程 INSERT INTO table VALUES (...); -- 逐条执行 INSERT INTO table VALUES (...); ``` - **自调用绕过代理** ```java public void process() { this.saveBatch(list); // 直接调用导致@Transactional失效 } ``` #### 2. 完整解决方案 **步骤1:启用批量事务模式** ```java @Configuration public class MybatisPlusConfig { @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); // 关键配置:批量操作拦截器 interceptor.addInnerInterceptor(new BatchInsertInnerInterceptor()); return interceptor; } } ``` **步骤2:正确的事务配置** ```java @Service public class DataServiceImpl implements DataService { @Autowired private DataMapper dataMapper; @Override @Transactional(rollbackFor = Exception.class) public boolean saveBatch(Collection<Data> list) { // 必须开启事务才能生效 return dataMapper.saveBatch(list, 1000); // 设置合理batchSize } } ``` **步骤3:调用方正确使用** ```java @Service public class BusinessService { @Autowired // 必须通过代理对象调用 private DataService dataService; @Transactional public void batchProcess(List<Data> data) { dataService.saveBatch(data); // 跨服务调用 // 其他业务操作... } } ``` #### 3. 性能优化技巧 1. **合理设置batchSize** ```java // 根据数据库配置调整 dataMapper.saveBatch(list, 500); ``` 2. **关闭自动提交** ```properties spring.datasource.hikari.auto-commit=false ``` 3. **启用JDBC批处理** ```properties spring.jpa.properties.hibernate.jdbc.batch_size=1000 spring.jpa.properties.hibernate.order_inserts=true ``` --- ### 三、事务验证方案 #### 1. 基础验证方法 ```java @Test @Transactional public void testSaveBatch() { // 准备测试数据 List<Data> testData = generateTestData(10000); try { dataService.saveBatch(testData); throw new RuntimeException("强制异常"); // 验证回滚 } catch (Exception e) { Assert.assertEquals(0, dataMapper.count()); // 断言数据已回滚 } } ``` #### 2. 高级调试技巧 ```properties # 开启事务日志 logging.level.org.springframework.orm.jpa=DEBUG logging.level.org.hibernate.SQL=DEBUG logging.level.org.hibernate.engine.transaction.internal.TransactionImpl=TRACE ``` --- ### 四、扩展应用场景 **分布式事务整合方案** ```java @GlobalTransactional // Seata分布式事务注解 public void distributedOperation() { // 本地事务 dataService.saveBatch(localData); // 远程服务调用 feignClient.remoteOperation(remoteData); } ``` ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值