文章目录
- [Spring事务管理嵌套事务详解 : 同一个类中,一个方法调用另外一个有事务的方法------这篇文章中唯一出错的地方:“在一个Service内部,事务方法之间的嵌套调用,不会启用事务”]()
- 1.事务的传播机制:
- 1.1propagation_required(xml文件中为required)
- 实验1:
- 1.2 propagation_requires_new(xml文件中为requires_new)
- 1.3 propagation_nested(xml文件中为nested)
- 1.4 propagation_supports(xml文件中为supports)
- 1.5 propagation_mandatory(xml文件中为mandatory)
- 1.6 propagation_not_supported(xml文件中为not_supported)
- 1.7 propagation_never(xml文件中为never)
- 总结:
- 2.解决Transactional注解不回滚(自己的理解)
- 2.1**常见的三种:**
- 1. 检查你方法是不是public的。
- 2.在同一类内部调用调用类内部@Transactional标注的方法。这种情况下也会导致事务不开启
- 2.在不同类:在一个类中调用其他类@Transactional标注的方法。这种情况下事务会开启的。
- 3. 事务方法内部捕捉了异常,没有抛出新的异常,导致事务操作不会进行回滚。示例代码如下。
- 2.2 你的异常类型是不是unchecked异常。
- 2.3 数据库引擎要支持事务
- 2.4不要在接口上使用
- 3.使用:
- 经过自己试验总结:
Spring事务管理嵌套事务详解 : 同一个类中,一个方法调用另外一个有事务的方法------这篇文章中唯一出错的地方:“在一个Service内部,事务方法之间的嵌套调用,不会启用事务”
上面这篇很重要的参考博文,一定要看。
Spring 在扫描bean的时候会扫描方法上是否包含@Transactional注解,如果包含,spring会为这个bean动态地生成一个子类(即代理类,proxy),代理类是继承原来那个bean的。此时,当这个有注解的方法被调用的时候,实际上是由代理类来调用的,代理类在调用之前就会启动transaction。然而,如果这个有注解的方法是被同一个类中的其他方法调用的,那么该方法的调用并没有通过代理类,而是直接通过原来的那个bean,所以就不会启动transaction,我们看到的现象就是@Transactional注解无效。
通过看target中的文件可以看出
1.事务的传播机制:
在TransactionDefinition接口中定义了七个事务传播行为。
说最常用的三个
1.1propagation_required(xml文件中为required)
- 含义:表示当前方法必须在一个具有事务的上下文中运行,如有客户端有事务在进行,那么被调用端将在该事务中运行,否则的话重新开启一个事务。(如果被调用端发生异常,那么调用端和被调用端事务都将回滚)
- 回滚:子事务回滚,父事务一定回滚,父事务回滚,子事务一定回滚;
实验1:
目的:子事务发生回滚,父事务也同样发生回滚。
表的数据为:
@GetMapping("test")
@ApiOperation("测试事务")
public void Test01(String id){
iUserService.updateUser(id);
}
* @author Mr wu
* @since 2021-04-22
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Transactional(propagation = Propagation.REQUIRED)
public void updateUser(String id){
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id",id).set("name","wu");
this.update(updateWrapper);
updateOtherUser();
}
@Transactional(propagation = Propagation.REQUIRED)
public void updateOtherUser(){
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id",2).set("name","zhang");
this.update(updateWrapper);
int a = 1/0;
}
}
结论:两个方法会进行回滚。
1.2 propagation_requires_new(xml文件中为requires_new)
- 含义: 总是开启一个新的事务。如果一个事务已经存在,则将这个存在的事务挂起。,直到新的事务提交或者回滚才恢复执行。
- 回滚:外层事务不会影响内部事务的提交/回滚
- 注意:如果不起作用,可能的原因如下:调用方与被调用方写在了同一个service类里面,需要用Spring的上下文获取对象。
例如:((ABCServiceImpl) SpringContextHolder.getBean("注册的标识符"))
实验2:
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
private ApplicationContext applicationContext;
@Transactional(propagation = Propagation.REQUIRED)
public void updateUser(String id){
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id",id).set("name","wu");
this.update(updateWrapper);
UserServiceImpl bean = applicationContext.getBean(UserServiceImpl.class);
bean.updateOtherUser();
int a = 1/0;
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateOtherUser(){
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id",2).set("name","zhang");
this.update(updateWrapper);
}
}
1.3 propagation_nested(xml文件中为nested)
- 含义: 如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按
TransactionDefinition.PROPAGATION_REQUIRED
属性执行。 - 回滚:子事务回滚到savepoint,父事务选择性回滚或者不回滚;父事务回滚,子事务一定回滚;
其他四个
1.4 propagation_supports(xml文件中为supports)
- 含义:如果存在一个事务,支持当前事务。如果没有事务,则非事务的执行。但是对于事务同步的事务管理器,PROPAGATION_SUPPORTS与不使用事务有少许不同。
如果上下文存在事务,则支持事务加入事务,如果没有事务,则使用非事务的方式执行
1.5 propagation_mandatory(xml文件中为mandatory)
- 含义:如果已经存在一个事务,支持当前事务。如果没有一个活动的事务,则抛出异常。
1.6 propagation_not_supported(xml文件中为not_supported)
- 含义:表示该方法不应该在一个事务中运行。如果有一个事务正在运行,他将在运行期被挂起,直到这个事务提交或者回滚才恢复执行
1.7 propagation_never(xml文件中为never)
- 含义:表示当方法务不应该在一个事务中运行,如果存在一个事务,则抛出异常
总结:
通常我们只会用到@Transactional(propagation = Propagation.REQUIRED)。
在特殊需求的时候需要在一个方法内部提前提交一部分事务或者是让内部的一段代码处于单独的一个事务管理的时候需要用到REQUIRES_NEW
。
2.解决Transactional注解不回滚(自己的理解)
2.1**常见的三种:**
1. 检查你方法是不是public的。
如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。
2.在同一类内部调用调用类内部@Transactional标注的方法。这种情况下也会导致事务不开启
示例代码如下。
设置一个内部调用
* @author Mr wu
* @since 2021-04-22
*/
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private UserServiceImpl2 userServiceImpl2;
public void updateUser(String id){
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id",id).set("name","wu");
this.update(updateWrapper);
updateOtherUser();
}
@Transactional(propagation = Propagation.REQUIRED)
public void updateOtherUser(){
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id",2).set("name","zhang");
this.update(updateWrapper);
int a = 1/0;
}
}
原因分析:
2.在不同类:在一个类中调用其他类@Transactional标注的方法。这种情况下事务会开启的。
示例代码如下。
第一个类:UserServiceImpl
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {
@Autowired
private ApplicationContext applicationContext;
@Autowired
private UserServiceImpl2 userServiceImpl2;
public void updateUser(String id){
UpdateWrapper<User> updateWrapper = new UpdateWrapper<>();
updateWrapper.eq("id",id).set("name","wu");
this.update(updateWrapper);
userServiceImpl2.updateOther2User();
}
第二个类:
/**
* @auther Mr.Wu
* @date 2021/4/22 14:35
*/
@Service
public class UserServiceImpl2 {
@Autowired
private UserMapper userMapper;
@Transactional(propagation = Propagation.REQUIRED)
public void updateOther2User(){
userMapper.updateOther2User();
int a = 1/0;
}
}
***
对应的xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.wu.jee.system.mapper.UserMapper">
<update id="updateOther2User">
update test_user set name='xin' where id = '2'
</update>
</mapper>
分析:
3. 事务方法内部捕捉了异常,没有抛出新的异常,导致事务操作不会进行回滚。示例代码如下。
/**
* @author zhoujy
* @date 2018年12月06日
**/
@Component
public class TestServiceImpl implements TestService {
@Resource
TestMapper testMapper;
@Transactional
public void insertTestCatchException() {
try {
int re = testMapper.insert(new Test(10,20,30));
if (re > 0) {
//运行期间抛异常
throw new NeedToInterceptException("need intercept");
}
testMapper.insert(new Test(210,20,30));
}catch (Exception e){
System.out.println("i catch exception");
}
}
}
2.2 你的异常类型是不是unchecked异常。
Spring的AOP即声明式事务管理默认是针对unchecked exception回滚。也就是默认对RuntimeException()
异常或是其子类进行事务回滚;checked异常,即Exception可try{}捕获的不会回滚,如果使用try-catch捕获抛出的unchecked异常后没有在catch块中采用页面硬编码的方式使用spring api对事务做显式的回滚,则事务不会回滚, “将异常捕获,并且在catch块中不对事务做显式提交=生吞掉异常”
如果我想check异常也想回滚怎么办,注解上面写明异常类型即可,
@Transactional(rollbackFor=Exception.class)
类似的还有norollbackFor,自定义不回滚的异常。
如果使用try-catch捕获抛出的unchecked异常后没有在catch块中采用页面硬编码的方式使用spring api对事务做显式的回滚,则事务不会回滚,
2.3 数据库引擎要支持事务
如果是mysql,注意表要使用支持事务的引擎,比如innodb,如果是myisam,事务是不起作用的。
2.4不要在接口上使用
Spring团队建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。
3.使用:
属性名 | 说明 |
name | 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。 |
propagation | 事务的传播行为,默认值为 REQUIRED。 |
isolation | 事务的隔离度,默认值采用 DEFAULT。 |
timeout | 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。 |
read-only | 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。 |
rollback-for | 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。 |
no-rollback- for | 抛出 no-rollback-for 指定的异常类型,不回滚事务。 |
在应用系统调用声明@Transactional 的目标方法时,Spring Framework 默认使用 AOP 代理,在代码运行时生成一个代理对象,根据@Transactional 的属性配置信息,这个代理对象决定该声明@Transactional 的目标方法是否由拦截器 TransactionInterceptor 来使用拦截,在 TransactionInterceptor 拦截时,会在在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器(图 2 有相关介绍)AbstractPlatformTransactionManager 操作数据源 DataSource 提交或回滚事务, 如图 1 所示。 | |
Spring AOP 代理有 CglibAopProxy 和 JdkDynamicAopProxy 两种,图 1 是以 CglibAopProxy 为例,对于 CglibAopProxy,需要调用其内部类的 DynamicAdvisedInterceptor 的 intercept 方法。对于 JdkDynamicAopProxy,需要调用其 invoke 方法。 |
正如上文提到的,事务管理的框架是由抽象事务管理器 AbstractPlatformTransactionManager 来提供的,而具体的底层事务处理实现,由 PlatformTransactionManager 的具体实现类来实现,如事务管理器 DataSourceTransactionManager。不同的事务管理器管理不同的数据资源 DataSource,比如 DataSourceTransactionManager 管理 JDBC 的 Connection。
PlatformTransactionManager,AbstractPlatformTransactionManager 及具体实现类关系如图 2 所示。
图 2. TransactionManager 类结构
经过自己试验总结:
结论1:在同一个类中:,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解是不会生效的(原理也是上面所说的)
结论2:在不同类中:,一个方法调用另外一个类中的有注解(比如@Async,@Transational)的方法,注解是会生效,但是只是含有事务注解的方法会回滚,父亲方法不会发生变化。