Java 事务的传播性(Transactional)

article/2025/10/3 3:28:07

前言

事务的传播性是Spring特有的概念,是基于Spring AOP技术实现的,原本的方法不具备事务的功能,运用Spring AOP的方式动态的增加了事务的功能,来确保数据库的数据的一致性。

只要开启事务的方法发生调用关系就一定存在事务的传播,重点在于调用才有传播,调用就存在调用者和被调用者,事务传播就是研究调用者和被调用者之间的关系。

7种传播机制的约束条件

约束条件说明
REQUIRED如果当前没有事务,则新建事务,如果当前存在事务,则加入当前事务,合并成一个事务
REQUIRES_NEW新建事务,如果当前存在事务,则把当前事务挂起,新建事务执行完后再恢复当前事务
NESTED如果当前没有事务,则新建事务,如果当前存在事务,则创建一个当前事务的子事务(嵌套事务),子事务不能单独提交,只能和父事务一起提交
SUPPORTS支持当前事务,如果当前没有事务,以非事务的方式执行
NOT_SUPPORTED以非事务方式执行,如果存在当前事务就把当前事务挂起
NEVER以非事务方式执行,如果当前存在事务就抛异常
MANDATORY使用当前事务,如果当前没有事务,就抛异常

上述的约束条件只有 REQUIRED 最常用,也是默认的约束条件, REQUIRES_NEW 可能会被用到,剩下的 SUPPORTS,NOT_SUPPORTED,NEVER,NESTED,MANDATORY 都是不常用的,我以 REQUIRED 做研究,其它不考虑。

首先先搞懂传播机制的时机,其实就是被调用者被调用者调用的时候

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void transfer(String inName, String outName, float money) {yangMapper.outMoney(outName, money);yangMapper.inMoney(inName, money);
}问:从上代码可以看出采用REQUIRED传播,不加propagation参数默认也是REQUIRED(@Transactional),我们知道这种传播模式有新建和加入事务两种,请问能否判断出transfer方法是新建事务还是加入事务?
答:不能,因为该方法还没有发生调用,只有发生调用的时候注解才生效,所以无法判断。

再看下面的代码

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public int transfer(String inName, String outName, float money) {yangMapper.outMoney(outName, money);yangMapper.inMoney(inName, money);return 1;
}//伪代码
void main(){// 开启事务transfer("yang","c",100f);// 提交事务
}问:请问能否判断出transfer方法是新建事务还是加入事务?
答:能,因为该方法还发生调用,调用者是main方法,被调用者是transfer,并且是新建事务,因为main方法没有事务,被调用者只能新建事务,相反如果main方法有事务,那么被调用者只需要加入事务。// 被调用者加入事务样例
// @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
// void main(){
//	  开启事务
//    transfer("yang","c",100f);
//	  提交事务
// }

现在展示下事务的使用场景,我要演示的场景分两种,一个是A类的A方法调用B类的B方法,另一个是A类的A方法调用A类的B方法

这个是Controller层的方法的入口,调用A类的A方法

@RequestMapping("/yang")
public Integer addMoney() {serviceImplA.addMoney("yang", 100);return 1;
}

A类的A方法调用B类的B方法

A类的A方法

// A类的A方法
public void addMoney(String yang, int i) {Map<String, Object> map = new HashMap<>();map.put("name", yang);map.put("money", i);yangMapper.addMoney(map); // 调用加钱方法 100serviceImplB.addMoney(yang, i);// 调用B类的B方法
}

B类的B方法

// B类的B方法
public void addMoney(String yang, int i) {Map<String, Object> map = new HashMap<>();	map.put("name", yang);map.put("money", i);yangMapper.addMoney(map);// 调用加钱方法 200
}

mapper和provider中的方法,方法意思就是给yang加100元

// mapper
@UpdateProvider(type = YangProvider.class, method = "addMoney")
void addMoney(Map<String, Object> map);
// provider
public String addMoney(Map<String, Object> map) {StringBuffer sb = new StringBuffer();sb.append("UPDATE YANGMONEY SET MONEY = MONEY + " + map.get("money") + " WHERE NAME = '" + map.get("name") + "'");return sb.toString();
}

经过上述的操作调用结果为给yang增加200元,这个是毫无争议的对吧

在这里插入图片描述
如果现在B类的B方法下加个运行异常语句,如下

// B类的B方法
public void addMoney(String yang, int i) {Map<String, Object> map = new HashMap<>();	map.put("name", yang);map.put("money", i);yangMapper.addMoney(map);// 调用加钱方法 200int e = 1 / 0;
}

那么现在如果重新执行,数据库的结果会是什么呢,是的,虽然会抛出异常但是数据库已经操作完了,结果是200,这种就是非常不合理的,明明程序有问题还给我修改了数据库数据,现在我们就给B类的B方法加上事务,再看结果会是多少,能猜到吗?

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void addMoney(String yang, int i) {Map<String, Object> map = new HashMap<>();map.put("name", yang);map.put("money", i);yangMapper.addMoney(map);// 调用加钱方法 200int e = 1 / 0;
}

最终的结果是100,因为B类的B方法加上了事务就相当于A类的A方法在调用B类B方法的时候就变成了

public void addMoney(String yang, int i) {Map<String, Object> map = new HashMap<>();map.put("name", yang);map.put("money", i);yangMapper.addMoney(map); // 调用加钱方法 100// 开启事务serviceImplB.addMoney(yang, i);// 调用B类的B方法// 提交事务
}

可以看到B类的B方法被调用的时候是有事务的,B类方法的B方法是有异常的,B方法是一个整体,遇到异常就回滚了,所以数据库最后的只入了A类A方法加钱,B类的回滚了,为100,如果不是在B类B方法开启的事务,而是在A类的A方法上开始的事务,那么结果又会是什么样子呢

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void addMoney(String yang, int i) {Map<String, Object> map = new HashMap<>();map.put("name", yang);map.put("money", i);yangMapper.addMoney(map); // 调用加钱方法serviceImplB.addMoney(yang, i);// 调用B类的B方法
}

这样最后的结果是数据库不会增加钱,结果为0,因为你在A类的A方法开启事务,A类的A方法和调用B类的B方法又是一个整体了,B有异常就一起回滚了,看在哪里开启的事务是要看调用者的,在A类A方法上加事务,开启事务的时机是

@RequestMapping("/yang")
public Integer addMoney() {// 开启事务serviceImplA.addMoney("yang", 100);// 提交事务return 1;
}

就算把那个异常语句移动到A类的A方法中,效果都是一样的,因为都在事务包裹中,是一个整体。如下

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)public void addMoney(String yang, int i) {Map<String, Object> map = new HashMap<>();map.put("name", yang);map.put("money", i);yangMapper.addMoney(map); // 调用加钱方法serviceImplB.addMoney(yang, i);// 调用B类的B方法int e = 1 / 0;}

A类的A方法调用A类的B方法

A类的A方法调用A类的B方法,A类的B方法和A类的A方法一个方法,方便看结果,如下

public void addMoney(String yang, int i) {Map<String, Object> map = new HashMap<>();map.put("name", yang);map.put("money", i);yangMapper.addMoney(map); // 调用加钱方法addMoney1(yang, i);}public void addMoney1(String yang, int i) {Map<String, Object> map = new HashMap<>();map.put("name", yang);map.put("money", i);yangMapper.addMoney(map);// 调用加钱方法}

这样数据库的结果会是200这个是肯定的对吧,现在我们就和上面一样,在addMoney1里面抛异常,如下

public void addMoney1(String yang, int i) {Map<String, Object> map = new HashMap<>();map.put("name", yang);map.put("money", i);yangMapper.addMoney(map);// 调用加钱方法int e = 1 /0;}

结果是一样的,数据库的结果肯定还是200,现在给addMoney1加事务,如下

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void addMoney1(String yang, int i) {Map<String, Object> map = new HashMap<>();map.put("name", yang);map.put("money", i);yangMapper.addMoney(map);// 调用加钱方法int e = 1 / 0;
}

你们觉得数据库的数据会是多少,我想肯定会有人觉得数据库的数据是100,但是错误了,数据是200
很明显出现这种结果就是事务失效了,这样加事务是加不上的,事务的底层是Spring AOP来实现的,这种自调用的方式是不满足AOP的动态代理的,如果你想要让这个事务生效,你在A类A方法中调用的时候不能采用 addMoney1(yang, i) 的方式,应该用A类的对象调用才行,如下

public void addMoney(String yang, int i) {Map<String, Object> map = new HashMap<>();map.put("name", yang);map.put("money", i);yangMapper.addMoney(map); // 调用加钱方法// 开启事务serviceImplA.addMoney1(yang, i);// 修改自调用// 提交事务
}

或者也可以在A类A方法上加事务,如下,这样是没问题的addMoney1是一个整体不是开启事务的时机

@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
public void addMoney(String yang, int i) {Map<String, Object> map = new HashMap<>();map.put("name", yang);map.put("money", i);yangMapper.addMoney(map); // 调用加钱方法addMoney1(yang, i);
}

@Transactional的几种失效场景

  • 访问权限问题(只有 public 方法会生效)。
  • 方法用 final 修饰,不会生效。

spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能。但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,而添加事务功能。如果某个方法是static的,同样无法通过动态代理,变成事务方法。

  • 同一个类中的方法直接内部调用,会导致事务失效。
  • 类本身未被spring管理。

在我们平时开发过程中,有个细节很容易被忽略。即使用spring事务的前提是:对象要被spring管理,需要创建bean实例。

  • 多线程调用。
@Slf4j
@Service
public class UserService {@Autowiredprivate UserMapper userMapper;@Autowiredprivate RoleService roleService;@Transactionalpublic void add(UserModel userModel) throws Exception {userMapper.insertUser(userModel);new Thread(() -> {roleService.doOtherThing();}).start();}
}@Service
public class RoleService {@Transactionalpublic void doOtherThing() {System.out.println("保存role表数据");}
}

从上面的例子中,我们可以看到事务方法add中,调用了事务方法doOtherThing,但是事务方法doOtherThing是在另外一个线程中调用的。这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想doOtherThing方法中抛了异常,add方法也回滚是不可能的。

  • 存储引擎不支持事务。
  • 自己吞了异常。

spring的事务是在调用业务方法之前开始的,业务方法执行完毕之后才执行commit or rollback,事务是否执行取决于是否抛出runtime异常。如果抛出runtime exception 并在你的业务方法中没有catch到的话,事务会回滚。尽量不要写 try-catch 如果要写的同时还要保证事务回滚可以尝试在catch最后一行throw一个runtimeException或者手动回滚。

声明式事务与编程式事务

① 声明式事务

通常情况下,我们会在方法上@Transactional注解,填加事务功能,比如:

@Service
public class UserService {@Autowired private RoleService roleService;@Transactionalpublic void add(UserModel userModel) throws Exception {query1();query2();query3();roleService.save(userModel);update(userModel);}
}@Service
public class RoleService {@Autowired private RoleService roleService;@Transactionalpublic void save(UserModel userModel) throws Exception {query4();query5();query6();saveData(userModel);}
}

但@Transactional注解,如果被加到方法上,有个缺点就是整个方法都包含在事务当中了。

上面的这个例子中,在UserService类中,其实只有这两行才需要事务:

roleService.save(userModel);
update(userModel);

在RoleService类中,只有这一行需要事务:

saveData(userModel);

现在的这种写法,会导致所有的query方法也被包含在同一个事务当中。如果query方法非常多,调用层级很深,而且有部分查询方法比较耗时的话,会造成整个事务非常耗时,而从造成大事务问题。

② 编程式事务

  1. @Transactional注解是通过Spring的AOP起作用的,但是如果使用不当,事务功能可能会失效。
  2. @Transactional注解一般加在某个业务方法上,会导致整个业务方法都在这个事务中,粒度太大,不好控制事务范围。

上面的这些内容都是基于@Transactional注解的,主要讲的是它的事务问题,我们把这种事务叫做:声明式事务。

其实,spring还提供了另外一种创建事务的方式,即通过手动编写代码实现的事务,我们把这种事务叫做:编程式事务。例如:

   @Autowiredprivate TransactionTemplate transactionTemplate;public void save(final User user) {queryData1();queryData2();transactionTemplate.execute(transactionStatus -> {addData1();updateData2();return Boolean.TRUE;});}

在spring中为了支持编程式事务,专门提供了一个类:TransactionTemplate,在它的execute方法中,就实现了事务的功能。

相较于@Transactional注解声明式事务,更建议大家使用,基于TransactionTemplate的编程式事务。主要原因如下:

  1. 避免由于spring aop问题,导致事务失效的问题。
  2. 能够更小粒度的控制事务的范围,更直观。

建议在项目中少使用@Transactional注解开启事务。但并不是说一定不能用它,如果项目中有些业务逻辑比较简单,而且不经常变动,使用@Transactional注解开启事务开启事务也无妨,因为它更简单,开发效率更高,但是千万要小心事务失效的问题。

事务中避免远程调用

我们在接口中调用其他系统的接口是不能避免的,由于网络不稳定,这种远程调的响应时间可能比较长,如果远程调用的代码放在某个事物中,这个事物就可能是大事务。当然,远程调用不仅仅是指调用接口,还有包括:发MQ消息,或者连接redis、mongodb保存数据等。


http://chatgpt.dhexx.cn/article/dFvejXDe.shtml

相关文章

Java 事务应用

一&#xff0c; 事务的一些基础知识简单回顾一下&#xff0c;讲的不是很深入&#xff0c;网上博客很多。 1&#xff0c;关于事务的四大特性&#xff1a;原子性、隔离性、一致性、持久性 本文不再赘述&#xff1b; 2&#xff0c;事务的隔离级别&#xff1a;读未提交&#xff0c;…

Java事务管理

事务的ACID属性&#xff1a;原子性(Atomicity )、一致性( Consistency )、隔离性或独立性( Isolation)和持久性(Durabilily) ACID 特性 A&#xff08;原子性&#xff09;事务的原子操作单元&#xff0c;对数据的修改&#xff0c;要么全部执行&#xff0c;要么全部不执行&#x…

java 事务级别_java事务隔离级别

事务隔离级别是由数据库系统实现的。 Java事务 1) 说到事务&#xff0c;不得不提的就是ACID特性&#xff0c;再次回顾&#xff1a; 原子性(atomicity)&#xff1a;组成事务处理的语句形成了一个逻辑单元&#xff0c;不能只执行其中的一部分。 一致性(consistency)&#xff1a…

Java中使用事务(注解实现)

Java中使用事务&#xff08;注解实现&#xff09; 事务的介绍 描述&#xff1a; 对于一个功能实现或者业务流程&#xff0c;要么全做&#xff0c;要么全不做&#xff01; 特性&#xff1a; ACID A - 原子性&#xff1a;执行的最小单位&#xff0c;要么全做&#xff0c;要么全…

JAVA的事务

要先知道什么是java中的事务? 事务: 一般是指要做的或所做的事情.专业术语是这样说的: 就是代码逻辑上的一组操作,这些操作要么全部成功,要么全部失败!举一个现实生活当中的例子: 1张三账上有2000元&#xff0c;李四账号也有2000元。张三要向李四转账1000元&#xff0c;正常来…

Java中的事务

一、事务概述 1. 什么是事务 事务是指对数据库的一系列的操作序列&#xff0c;数据库应用系统通过事务集来完成对数据的存取操作。 2. 事务的特性&#xff08;ACID原则&#xff09; 原子性&#xff08;Atomicity&#xff09;&#xff1a;一个事务的操作不可分割&#xff0c…

java事务总述

文章目录 一、java事务概述1.1、java事务简述1.2、Java事务的类型1.3、java事务的特性1.4、java事务的隔离级别1.5、spring事务的传播特性1.6、spring支持的事务管理类型 二、java事物使用2.1、XML配置2.2、事务使用方式 一、java事务概述 1.1、java事务简述 1、简介 事务(TR…

Java中的事务及使用

什么是事务&#xff1f; 事务&#xff08;Transaction&#xff09;&#xff0c;一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元(unit)。事务通常由高级数据库操纵语言或编程语言&#xff08;如SQL&#xff0c;C或Java&am…

测试用例设计方法---等价类划分法

1 等价类划分法 1.1 定义 是把所有可能输入的数据&#xff0c;即程序的输入域划分策划国内若干部分&#xff08;子集&#xff09;&#xff0c;然后从每一个子集中选取少数具有代表性的数据作为测试用例。方法是一种重要的、常用的黑盒测试用例设计方法。 1.1划分等价类 1&a…

02测试用例设计方法-等价类划分

等价类划分法 1&#xff09;定义 是把所有可能的输入数据,即程序的输入域划分成若干部分&#xff08;子集&#xff09;,然后从每一个子集中选取少数具有代表性的数据作为测试用例。该方法是一种重要的,常用的黑盒测试用例设计方法。使用这一方法时&#xff0c;完全不考虑程序的…

等价类划分用例案例设计

一、加法案例 测试要求&#xff1a;计算1到100的两个整数之和&#xff08;包括1和100&#xff09; 提示&#xff1a;一般是一个框输入正确的值&#xff0c;一个框输入错误的值&#xff0c;没有两个框都输入错误的值&#xff0c;因为更容易确定到底是哪个框出现错误的值&#x…

常见测试用例设计方法1---等价类划分

目录 一&#xff0c;等价类划分概念&#xff1a; 二&#xff0c;有效等价类和无效等价类&#xff1a; 三&#xff0c;划分等价类的标准 四&#xff0c;设计测试用例 五&#xff0c;以QQ密码设定规则为例使用等价类划分编写测试用例 1&#xff0c;分析需求&#xff0c;确定输…

黑盒测试方法|测试用例的设计方法--等价类划分方法

黑盒测试方法–测试用例的设计方法–等价类划分方法 目录 等价类划分方法边界值分析方法错误推测方法因果图方法判定表驱动分析方法正交实验设计方法功能图分析方法场景设计方法 等价类划分方法 一、方法简介 &#xff08;1&#xff09;定义 把所有可能的输入数据,即程序的…

软件测试:等价类划分举例

等价类的设计思路&#xff1a; 根据输入条件&#xff0c;确定等价类&#xff0c;包括有效等价类和无效等价类&#xff0c;建立等价类列表为每个等价类规定一个唯一的编号设计一个测试用例&#xff0c;使其尽可能多地覆盖尚未被覆盖的有效等价类&#xff0c;重复这一步&#xff…

等价类划分法-案例剖析-设计测试用例

目录 等价类划分法概念 有效等价类和无效等价类 等价类设计测试用例步骤 案例1 案例2 案例3 等价类划分法概念 等价类划分法是把所有可能的输入数据&#xff0c;即程序的输入数据集合划分成若干个子集即等价类&#xff0c;然后从每个等价类中选取少量具有代表性的数据作为…

15.3-等价类划分

目录 一、等价类划分的概念 二、使用等价类划分的原因 三、等价类划分的价值 四、相关概念 1、等价类 2、有效等价类 3、无效等价类 五、等价类划分法使用步骤 六、等价类的划分原则 1、原则1 2、原则2 3、原则3 4、原则4 5、原则5 6、原则6 七、测试用例的设计步骤 八、等价类…

软件测试用例设计 (一)等价类划分法

软件测试对于软件的重要性不言而喻&#xff0c;是计算机类学生毕业后的一个重要从业方向之一。 如果要从事软件测试&#xff0c;那么有些必备的技能还是要有的。比如&#xff0c;测试理论、测试工具、测试文档的编制。 今天我们就来看看最最最重要的测试理论&#xff1a;黑盒…

测试用例设计——等价类划分法

一、分析问题 如果我们需要对下面的这个两位数加法器设计测试用例&#xff0c;在测试了1&#xff0b;1&#xff0c;1&#xff0b;2&#xff0c;&#xff08;-1&#xff09;1和&#xff08;-1&#xff09;&#xff0b;2之后&#xff0c;是否有必要测试1&#xff0b;3&#xff0c…

等价类划分法设计用例(超详细)

等价类划分法 等价类&#xff1a; 1、解决了不能穷举测试的问题、控制成本、控制测试用例数量 2、数据值要明确&#xff0c;对文字敏感 3、依据需求将输入划分为若干个等价类&#xff0c;划分等价类&#xff08;需求、数据特征&#xff09; 等价类设计用例的难点&#xff1a;…

测试用例设计方法 之【等价类划分法】

前言&#xff1a; 在没有测试用例之前&#xff0c;团队里的成员进行测试时&#xff0c;完全“以人为本”&#xff0c;根据个人思路、需求理解度、发散性思想来完成测试系统&#xff0c;人无完人&#xff0c;弊端显而易见。衍生出【测试用例】后&#xff0c;测试工作的目标得到…