再不掌握Spring事务怎么行?
一、Spring事务的基本原理
- Spring事务介绍
Spring的事务依赖于数据库的事务,没有数据库事务的支持,Spring则无法提供事务功能。通常,如果使用纯JDBC的方式来实现事务操作,写入如下:
纯JDBC操作数据库的事务使用方法:
1. 获取数据库连接:Connection con = DriverManager.getConnection();
2. 开启事务:con.setAutoCommit(true/false);
3. 执行CRUD操作
4. 提交事务/回滚事务:con.commit() / con.rollback();
5. 关闭连接:conn.close();
Spring的事务管理机制帮我们省略了步骤2和步骤4,我们只需要编写好CRUD代码,Spring会自动在CRUD代码的前后开启和关闭事务。
-
注解方式事务的简单使用
- 在需要开启事务的类或方法上加上 @Transactional 注解
- Spring启动时解析加载相关的Bean,会为 @Transactional 注解标注的类或方法生成代理类,
并根据注解中的相关参数进行配置项注入,在代理类中来做开启事务、关闭事务、回滚事务等操作 - 数据库层面的事务提交和回滚则是通过Binlog或者Redo log来实现
注解方式的事务称为:声明式事务,通过注解来选择需要使用事务的方法,使用 @Transactional 注解在方法上表明该方法需要事务支持。声明式事务通过Spring的AOP机制实现。
二、Spring事务的传播属性
Spring的事务传播属性定义再:TransactionDefinition 接口中。一共有七种,总结如下表所示。
事务传播属性名称 | 属性取值 | 含义解释 |
---|---|---|
PROPAGATION_REQUIRED | 0 | 支持当前事务,如果当前没有事务则新建一个 【默认】 |
PROPAGATION_SUPPORTS | 1 | 支持当前事务,如果当前没有事务,就以非事务方式运行 |
PROPAGATION_MANDATORY | 2 | 支持当前事务,如果当前没有事务,就抛出异常 |
PROPAGATION_REQUIRES_NEW | 3 | 新建事务,如果当前存在事务,则把当前事务挂起。新建的事务和被挂起的事务没有任何关系,是两个独立的事务。外层事务失败回滚后,不能回滚内层事务执行的结果,内层事务失败抛异常后,外层事务捕获,也可以不处理回滚操作 |
PROPAGATION_NOT_SUPPORTED | 4 | 以非事务的方式执行操作,如果当前存在事务,则挂起事务 |
PROPAGATION_NEVER | 5 | 以非事务的方式执行,如果当前存在事务,则抛出异常 |
PROPAGATION_NESTED | 6 | 如果一个活动的事务存在,则运行在一个嵌套的事务中。如果没有活动事务,则按REQUIRED属性执行 |
我们都知道,@Transactional注解可以加载不同的类或者方法上,以开启事务支持。假设存在类ClazzA和类ClazzB,它们的信息如下:
public class ClazzA {
@Resource
ClazzB clazzB;
@Transactional
String MethodA() {
// 1. 执行业务逻辑
... ...
// 2. 调用 ClazzB # methodB()
clazzB.methodB();
... ....
// 3. 执行业务逻辑
}
}
public class ClazzB {
@Transactional
String methodB() {
// 执行业务逻辑
... ...
}
}
methodA方法开启了事务支持,且methodB方法也开启了事务支持,这个时候在methodA中调用methodB方法就属于事务的嵌套场景,methodA的事务为外层事务,methodB的事务为内层事务。Spring的事务传播机制就是用在对嵌套事务的处理场景中。
-
PROPAGATION_REQUIRED [默认]
-
如果methodB定义为:@Transactional(propagation = Propagation.REQUIRED),由于methodA在执行时已经开启了事务(外层事务),在代码执行到调用methodB方法时,发现当前已经存在事务,则不会创建新事务,此时如果在methodA和methodB中任何地方出现异常,事务都会回滚。
-
假如执行到调用methodB时发现methodA没有开启事务,则methodB会创建一个新事务,此时如果methodB中出现异常会回滚事务。
-
-
PROPAGATION_SUPPORTS [常用]
-
如果methodB定义为:@Transactional(propagation = Propagation.SUPPORTS),假如methodA在执行时开启了事务,则methodB不会去创建一个新事务,而是加入到methodA的事务当中
-
假如methodA没有开启事务,则methodB也不会开启事务
-
内部方法是否开启事务完全取决于最外层的方法是否开启事务
-
-
PROPAGATI****ON_MANDATORY [基本不用]
-
如果methodB定义为:@Transactional(propagation = Propagation.MANDATORY),假如methodA在执行时开启了事务,则methodB不会去创建一个新事务,而是加入到methodA的事务当中
-
假如methodA没有开启事务,则methodB会抛异常
-
-
PROPAGATION_REQUIRES_NEW [常用]
-
如果methodB定义为:@Transactional(propagation = Propagation.REQUIRES_NEW),假如methodA在执行时开启了事务,当代码执行到调用methodB时,会将methodA的事务挂起,然后创建一个新的事务,等到methodB的事务完成后,methodA的事务才继续执行
-
methodA的事务和methodB的事务是两个不同的事务,如果methodB的事务已经提交,而methodA因执行出了异常而回滚事务,此时methodB的事务不会被回滚
-
如果methodB执行出现异常而回滚事务,如果抛出的异常能被methodA捕获,则methodA的事务仍然可能正常提交
-
-
PROPAGATION_NOT_SUPPORTED [基本不用]
-
如果methodB定义为:@Transactional(propagation = Propagation.NOT_SUPPORTED),假如methodA在执行时开启了事务,当代码执行到调用methodB时,会将methodA的事务挂起,然后以非事务的方式执行methodB的代码。执行完成后才会继续执行methodA的事务
-
思考:如果methodB出现了异常,methodA的事务会不会回滚?
-
-
PROPAGATION_NEVER [基本不用]
-
如果methodB定义为:@Transactional(propagation = Propagation.NEVER),假如methodA在执行时开启了事务,当代码执行到调用methodB时,会抛出异常
-
假设methodA没有开启事务,则执行到methodB时,会以非事务的方式执行
-
-
PROPAGATION_NESTED [常用]
-
如果methodB定义为:@Transactional(propagation = Propagation.NESTED),假如methodA在执行时开启了事务,当执行到methodB时,不会开启新事务,而是在methodA事务中继续运行,如果methodB执行出现了异常,则methodA的事务有两种处理方式:
-
捕获methodB的异常,在catch语块中处理异常,执行其他操作
-
methodA的事务可以根据配置自行决定是继续提交事务还是回滚事务
-
-
如果methodA没有开启事务,则methodB会像PROPAGATION_REQUIRED一样,开启一个新事务
-
如果外层开启事务,只要methodB出现异常都会回滚methodB,但是不一定回滚methodA
-
七种事务传播机制的代码效果测试详见:附录1、Spring事务的传播属性 - 代码测试
三、Spring事务的隔离级别
Spring的事务隔离级别定义在:TransactionDefinition 接口中。一共有五种,总结如下表所示。
事务隔离级别名称 | 属性取值 | 含义解释 |
---|---|---|
ISOLATION_DEFAULT | -1 | 使用数据库默认的隔离级别。下面的四个与JDBC的隔离级别对应 【默认】 |
ISOLATION_READ_UNCOMMITTED | 1 | 允许另一个事务可以看到这个事务未提交的数据 产生问题:脏读、不可重复读、幻读 |
ISOLATION_READ_COMMITTED | 2 | 保证一个事务修改的数据只有提交后才能被另一个事务读取 产生问题:不可重复读、幻读 |
ISOLATION_REPEATABLE_READ | 4 | 可以防止脏读、不可重复读,但是可能会产生幻读 |
ISOLATION_SERIALIZABLE | 8 | 事务被处理为顺序执行,代价最高但最可靠 |
四、分布式事务
说起分布式,自然就很容易想到分布式领域中的CAP(Consistency、Availability、Partition Tolerance Theorem)定理,CAP定理描述了在一个分布式系统中,当涉及到共享数据问题时,以下的三条特性最多只能同时满足两个:
-
一致性(Consistency):数据在多个副本之间能够保持一致。代表数据在任何时刻、任何分布式节点中所看到的都是符合预期的
-
可用性(Availability):系统提供的服务一直处于可用的状态,每次请求都能够获得正确的响应
-
分区容错性(Partition Tolerance Theorem):分布式系统在遇到任何网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务
接下来在说说分布式系统CAP定理的延伸:BASE理论。BASE理论是指:Basically Available(基本可用) 、Soft-state(软状态) 和 Eventually Consistent(最终一致性),是对CAP理论中的一致性(Consistency)和可用性(Availability)的权衡的结果。BASE理论来源于对大规模互联网分布式系统的实践总结,是基于CAP定理逐步演化而来的,大大降低了我们对系统的要求。
即使无法做到强一致性(Strong consistency),但每个应用都可以根据自身的业务特点,采用适当的方式来使系统达到最终一致性(Eventual consistency)
-
基本可用(Basically Available):假如系统出现了不可预知故障,允许损失部分可用性,当然也不能完全不可用。
-
响应时间上的损失:正常情况下的搜索引擎0.5秒即返回给用户结果,而基本可用的搜索引擎可以在2秒作用返回结果。
-
功能上的损失:在一个电商网站上,正常情况下,用户可以顺利完成每一笔订单。但是到了大促期间,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。
-
-
软状态(Soft-state):软状态指允许系统中的数据存在中间状态(CAP 理论中的数据不一致),并认为该中间状态的存在不会影响系统的整体可用性,即允许系统在不同节点的数据副本之间进行数据同步的过程存在延时。
-
最终一致性(Eventually Consistent):最终一致性强调的是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性。
分布式一致性的 3 种级别:
-
强一致性 :系统写入了什么,读出来的就是什么。
-
弱一致性 :不一定可以读取到最新写入的值,也不保证多少时间之后读取到的数据是最新的,只是会尽量保证某时刻达到数据一致的状态。
-
最终一致性 :弱一致性的升级版,系统会保证在一定时间内达到数据一致的状态。
业界比较推崇是最终一致性级别,但是某些对数据一致要求十分严格的场景比如银行转账还是要保证强一致性。
最终一致性的保证:
-
读时修复 : 在读取数据时,检测数据的不一致,进行修复。比如 Cassandra 的 Read Repair 实现,具体来说,在向 Cassandra 系统查询数据的时候,如果检测到不同节点 的副本数据不一致,系统就自动修复数据。
-
写时修复 : 在写入数据,检测数据的不一致时,进行修复。比如 Cassandra 的 Hinted Handoff 实现。具体来说,Cassandra 集群的节点之间远程写数据的时候,如果写失败 就将数据缓存下来,然后定时重传,修复数据的不一致性。
-
异步修复 : 这个是最常用的方式,通过定时对账检测副本数据的一致性,并修复。
BASE理论面向的是大型高可用、可扩展的分布式系统。与传统ACID特性相反,不是强一致性模型,BASE提出通过牺牲强一致性来获得可用性,并允许数据一段时间内的不一致,但是最终需要达到一致状态。
附录1、Spring事务的传播属性 - 代码测试
测试代码准备:给定两个service类,分别为UserService和TeamService,具体类下的方法如下所示。
TeamService.java && TeamServiceImpl.java
public interface TeamService {
/**
* 根据ID更新团队的主管
*
* @param teamId 团队ID
* @param masterId 人员ID
* @return 更新后的团队信息
*/
TeamDO updateTeamMasterById(Long teamId, String masterId);
}
@Service
public class TeamServiceImpl implements TeamService{
@Resource
private UserDao userDao;
@Resource
private TeamDao teamDao;
@Override
@Transactional(propagation = Propagation.REQUIRED)
public TeamDO updateTeamMasterById(Long teamId, String masterId) {
// 1. 根据ID查询用户信息
UserDO existMaster = userDao.findUserById(Long.valueOf(masterId));
Long userId = Long.valueOf(masterId);
// 2. 如果查询不到则插入默认用户
if (Objects.isNull(existMaster)) {
UserDO newMaster = new UserDO();
newMaster.setId(Long.valueOf(masterId));
newMaster.setName("admin");
newMaster.setCity("beijing");
newMaster.setCompany("baidu");
newMaster.setJob("boss");
newMaster.setEmail("admin@baidu.com");
existMaster = userDao.addUserInfo(newMaster);
userId = existMaster.getId();
}
// 3. 更新插入的用户信息
userDao.updateUserCityAndCompanyById(userId, "New York", "Amazon");
// 4. 更新团队主管信息
return teamDao.updateTeamMasterById(teamId, String.valueOf(existMaster.getId()));
}
}
UserService.java && UserServiceImpl.java
public interface UserDao {
/**
* 根据ID查询用户信息
*
* @param id 用户ID
* @return 用户信息
*/
UserDO findUserById(Long id);
/**
* 根据ID更新用户信息
*
* @param id 用户ID
* @param city 所属城市
* @param company 所属公司
* @return 更新的用户信息
*/
UserDO updateUserCityAndCompanyById(Long id, String city, String company);
/**
* 添加一个新用户
*
* @param user 新用户
* @return 插入的用户信息
*/
UserDO addUserInfo(UserDO user);
}
@Repository
public class UserDaoImpl implements UserDao {
@Resource
private UserMapper usersMapper;
@Override
public UserDO findUserById(Long id) {
UserParam param = new UserParam();
UserParam.Criteria criteria = param.createCriteria();
criteria.andIdEqualTo(id);
return Optional.ofNullable(usersMapper.selectByParam(param))
.orElse(new ArrayList<>()).get(0);
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public UserDO updateUserCityAndCompanyById(Long id, String city, String company) {
UserDO existUser = findUserById(id);
if (Objects.isNull(existUser)) {
return null;
}
existUser.setCity(city);
existUser.setCompany(company);
UserParam param = new UserParam();
UserParam.Criteria criteria = param.createCriteria();
criteria.andIdEqualTo(id);
usersMapper.updateByParamSelective(existUser, param);
// 模拟代码执行出现异常
int i = 1 / 0;
return findUserById(id);
}
@Override
@Transactional(propagation = Propagation.REQUIRED)
public UserDO addUserInfo(UserDO user) {
usersMapper.insert(user);
UserParam param = new UserParam();
UserParam.Criteria criteria = param.createCriteria();
criteria.andNameEqualTo(user.getName());
return Optional.ofNullable(usersMapper.selectByParam(param))
.orElse(new ArrayList<>()).get(0);
}
}
测试的数据库准备:准备两张表,tb_users表和tb_teams表,分别插入一些数据,具体内容如下。
模拟业务场景:根据给定的master_work_no来更新指定id的team信息。首先根据给定的master_work_no查询tb_users表,判断是否存在该用户,如果不存在则插入一个id为当前master_work_no值的默认用户。然后更新插入用户的city和company的值,最后更新指定id的team的master_work_no值。
模拟两种异常情况 (两个异常不同时出现):
-
异常1:UserServiceImpl#updateUserCityAndCompanyById( )方法中抛异常
public UserDO updateUserCityAndCompanyById(Long id, String city, String company) { UserDO existUser = findUserById(id); if (Objects.isNull(existUser)) { return null; } existUser.setCity(city); existUser.setCompany(company); UserParam param = new UserParam(); UserParam.Criteria criteria = param.createCriteria(); criteria.andIdEqualTo(id); usersMapper.updateByParamSelective(existUser, param); // 模拟代码执行出现异常 int i = 1 / 0; return findUserById(id); }
-
异常2:TeamServiceImpl#updateTeamMasterById( )方法中抛异常
public TeamDO updateTeamMasterById(Long teamId, String masterId) { // 1. 根据ID查询用户信息 Long masterUserId = Long.valueOf(masterId); UserDO existMaster = userDao.findUserById(masterUserId); Long userId = Long.valueOf(masterId); // 2. 如果查询不到则插入默认用户 if (Objects.isNull(existMaster)) { // "admin" "beijing" "baidu" "boss" "admin@baidu.com" UserDO newMaster = UserDO.newInstance(); existMaster = userDao.addUserInfo(newMaster); userId = existMaster.getId(); } // 3. 更新插入的用户信息 userDao.updateUserCityAndCompanyById(userId, "New York", "Amazon"); // 4. 更新团队主管信息 TeamDO newTeam = teamDao.updateTeamMasterById(teamId, String.valueOf(existMaster.getId())); int i = 1/0; return newTeam; }
-
具体测试场景:
-
没有开启事务支持:
-
出现异常1的场景:新增的用户信息会被写入数据库tb_users表,且会被更新city和company。tb_teams表不会被更新
-
出现异常2的场景:tb_teams表的主管id字段被更新了,tb_users表有了新增的数据,且city和company字段被修改
-
结论:无论是异常1还是异常2都会造成数据库的修改
-
-
-
PROPAGATION_REQUIRED
-
UserServiceImpl#updateUserCityAndCompanyById( )和TeamServiceImpl#updateTeamMasterById( )都增加@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)注解
-
出现异常1的场景:数据库的两张表都不会发生更新
-
出现异常2的场景:数据库的两张表都不会发生更新
-
-
仅UserServiceImpl#updateUserCityAndCompanyById( )增加@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)注解
-
出现异常1的场景:tb_users表会有新记录写入,但是没有发生更新,tb_teams表也没有更新
-
出现异常2的场景:tb_users表数据有新增,同时字段被修改,tb_teams表字段被修改
-
-
结论:PROPAGATION_REQUIRED在外层方法开启事务时,会使用外层方法的事务,在外层方法和内层方法的任意地方出现异常都会进行回滚,不更新数据库。当外层方法没有开启事务,则内层方法会开启一个事务,当内层方法抛异常时仅回滚内层方法。
-
-
PROPAGATION_SUPPORTS
-
UserServiceImpl#updateUserCityAndCompanyById( )添加@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)注解,TeamServiceImpl#updateTeamMasterById( )添加@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)注解
-
出现异常1的场景:数据库两张表都不会更新
-
出现异常2的场景:数据库两张表都不会更新
-
-
仅UserServiceImpl#updateUserCityAndCompanyById( )添加@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)注解
-
出现异常1的场景:tb_users表数据有新增,同时字段被修改,tb_teams表字段被修改
-
出现异常2的场景:tb_users表数据有新增,字段也被修改了,tb_teams表没有修改
-
-
结论:PROPAGATION_SUPPORTS下如果外层方法开启了事务,则内层方法会使用外层方法的事务,在任何一个地方抛异常都会回滚。如果外层方法没有开启事务,则内层方法也不会开启事务
-
-
PROPAGATI****ON_MANDATORY
-
对于外层方法开启事务的场景不在进行模拟,同上
-
仅UserServiceImpl#updateUserCityAndCompanyById( )添加@Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class)注解
-
出现异常1的场景:tb_users表数据被写入,但是字段没有更新,同时控制台打印出异常提示
-
出现异常2的场景:tb_users表数据被写入,但是字段没有更新,同时控制台打印出异常提示
-
-
结论:PROPAGATION_MANDATORY在当外层方法没有开启事务时,进入内层方法会直接抛出异常,压根不会执行内层方法。当外层方法开启事务时,会使用外层方法的事务
-
-
PROPAGATION_REQUIRES_NEW
-
UserServiceImpl#updateUserCityAndCompanyById( )添加@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)注解,TeamServiceImpl#updateTeamMasterById( )添加@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)注解
-
出现异常1的场景:tb_users表有新增记录,但是字段没有更新,tb_teams表的字段发生了更新。因为内层方法出现异常,且内层方法开启了独立的事务,所以内层方法执行了回滚。内层方法的异常会被外层方法吞掉,所以外层方法正常执行了,造成tb_teams表字段的更新
-
出现异常2的场景:给updateTeamMasterById的参数masterId传入"2",可以发现tb_users表的id=2的记录的字段发生了修改,但是tb_teams的记录没有发生修改。因为内层方法开启了独立事务,正常执行完成了。外层方法抛异常后发生了回滚,但是只回滚了外层方法,内层方法没有被回滚
-
-
仅UserServiceImpl#updateUserCityAndCompanyById( )添加@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)注解
-
出现异常1的场景:tb_users表有新记录插入,但是字段没有修改,tb_teams表字段也没有修改。因为内层方法抛异常后,内层方法会被回滚,造成新增的记录的修改被回滚掉。由于内层方法抛出了异常,导致外层方法无法执行,所有tb_teams表字段没有被更新
-
出现异常2的场景:tb_users表出现新增记录且字段被修改,tb_teams表字段被修改。因为内层方法没有出现异常,且开启了独立的事务,修改生效。外层方法没有事务,所有抛异常后数据没有回滚
-
-
结论:PROPAGATION_REQUIRES_NEW下内层方法会开启独立的事务,如果外层方法也开启了事务,则当内层方法抛异常时,内层方法会回滚,但是外层方法会吞掉异常,继续执行。如果内层方法正常执行,但是外出方法抛出异常,则外层方法会发生回滚,但是不会回滚内层方法。如果外层方法不开启事务,则当内层方法抛异常后,外层方法就停止执行了
-
-
PROPAGATION_NOT_SUPPORTED
-
对于外层方法不开启事务时,内层方法和外层方法都没有事务,该场景不再进行测试
-
UserServiceImpl#updateUserCityAndCompanyById( )添加@Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class)注解,TeamServiceImpl#updateTeamMasterById( )添加@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)注解
-
出现异常1的场景:tb_users表有新增记录且字段发生了更新,tb_teams表字段也更新了。因为内层方法将外层方法事务挂起,以非事务的方式执行,所以tb_users表字段被更新。外层方法会吞掉内层方法的异常,继续执行,造成tb_teams表字段更新
-
出现异常2的场景:数据库两张表都没有被更新。因为内层事务以非事务方式执行没有出现异常,外层事务执行出现异常会回滚事务
-
-
总结:PROPAGATION_NOT_SUPPORTED当外层方法开启事务后,内层方法会将外层事务挂起,以非事务的方式执行,如果出现异常则外层方法会吞掉异常,继续执行。如果内层方法正常执行但是外层方法抛异常,则外层方法会回滚,同时也会回滚内层方法
-
-
PROPAGATION_NEVER
-
对于外层不开启事务的场景,内外层方法都以无事务的方式运行,不在测试
-
UserServiceImpl#updateUserCityAndCompanyById( )添加@Transactional(propagation = Propagation.NEVER, rollbackFor = Exception.class)注解,TeamServiceImpl#updateTeamMasterById( )添加@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)注解
-
出现异常1的场景:数据库的两张表都没有发生修改,但是在执行内层方法时直接抛出异常,外层方法由于内层方法抛出异常而进行了回滚,导致数据库没有更新
-
出现异常2的场景:数据库两张表都没有更新,控制台同样打印出异常
-
-
总结:PROPAGATION_NEVER下如果外层方法开启了事务,那么执行到内层方法时会直接抛出异常,不会执行内层方法,同时外层方法因为内层方法抛出了异常而进行事务回滚,造成数据库没有更新。
-
-
PROPAGATION_NESTED
- 在进行该传播方式的测试前,需要修改一下TeamServiceImpl#updateUserCityAndCompanyById( )方法,给内层方法使用try/catch块包装
public TeamDO updateTeamMasterById(Long teamId, String masterId) {
// 1. 根据ID查询用户信息
Long masterUserId = Long.valueOf(masterId);
UserDO existMaster = userDao.findUserById(masterUserId);
Long userId = Long.valueOf(masterId);
// 2. 如果查询不到则插入默认用户
if (Objects.isNull(existMaster)) {
// "admin" "beijing" "baidu" "boss" "admin@baidu.com"
UserDO newMaster = UserDO.newInstance();
existMaster = userDao.addUserInfo(newMaster);
userId = existMaster.getId();
}
// 3. 更新插入的用户信息
try {
userDao.updateUserCityAndCompanyById(userId, "New York", "Amazon");
} catch (Exception e) {
System.out.println("异常被外层方法处理了。。。");
}
// 4. 更新团队主管信息
TeamDO newTeam = teamDao.updateTeamMasterById(teamId, String.valueOf(existMaster.getId()));
int i = 1/0;
return newTeam;
}
-
对于外层方法不开启事务,内层方法按照REQUIRE的方式执行,不在测试
-
UserServiceImpl#updateUserCityAndCompanyById( )添加@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)注解,TeamServiceImpl#updateTeamMasterById( )添加@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)注解
-
出现场景1的情况:tb_users表有新增数据,但是字段没有被修改(内层发生了回滚),tb_teams表字段被修改,且控制台打印出 “异常被外层方法处理了。。。”
-
出现场景2的情况:数据库两张表都没有更新
-
-
对比一下Propagation.REQUIRED的方式:
-
出现异常1的情况:数据库两张表数据都没有更新,控制台打出"异常被外层方法处理了。。。",因为REQUIRE的方式在内外层代码任何一个地方出现异常都会发生回滚,即使在外层通过try/catch语句捕获了内层的异常
-
出现异常2的情况:数据库两张表都没有更新
-
-
总结:PROPAGATION_NESTED在外层方法开启事务后,内层方法会使用外层方法的事务,但是在外层方法中可以捕获内层方法的异常,继续执行外层方法而不发生事务的回滚。如果以PROPAGATION_REQUIRED的方式,则不管外层方法是否使用try/catch语句都会造成事务的回滚
-