目录
- 一、声明式事务@Transactional参数
- 二、事务的七种传播机制
- 三、事务隔离级别
- 四、Spring事务未生效场景
- 1、抛出事务不支持的异常
- 2、使用了try catch
- 3、添加事务的方法必须是public,并且不能带有static、final关键字
- 4、类未被Spring管理
- 5、数据表不支持事务
- 6、Spring事务传播级别设置为不支持事务
- 7、未开启事务
- 8、多线程调用
- 五、事务使用分析
- 1、多方法事务范围分析
- 2、方法1 直接调用 方法2,在方法2 中添加事务
- 3、方法1 调用 方法2,在方法1 中添加事务
- 4、方法1 中通过 bean 调用方法2
spring中事务分为声明式事务和编程式事务,本文主要介绍声明式事务
前提:
1、Spring事务基于AOP代理实现
2、不能解决不同服务之间调用的场景-分布式事务
一、声明式事务@Transactional参数
二、事务的七种传播机制
- REQUIRED(必需)
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常用的事务传播行为。
- SUPPORTS(支持)
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式执行。
- MANDATORY(强制)
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
- REQUIRES_NEW(需要新的)
总是开启一个新的事务,如果当前存在事务,则把当前事务挂起。
- NOT_SUPPORTED(不支持)
总是非事务地执行,并挂起任何存在的事务。意思当前方法不会使用任何事务,但是不影响其他方法事务
- NEVER(从不)
总是非事务地执行,如果存在一个事务,则抛出异常。
- NESTED(嵌套)
如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则效果同REQUIRED。它使用了一个单独的事务,这个事务是一个嵌套的事务,可以独立于包装事务进行单独的提交和回滚。
使用举例:
@Transactional(propagation = Propagation.NESTED)
public void performService() {
// 业务逻辑
}
三、事务隔离级别
- Read Uncommitted(读未提交)
- Read Committed(读已提交)
- Repeatable Read(可重复读)(默认)
- Serializable(可串行化)
四、Spring事务未生效场景
1、抛出事务不支持的异常
原理:
Spring事务默认支持RuntimeException
异常,抛出的异常为RuntimeException异常及其子类异常事务均可生效,而我们日常常见的异常基本都继承自RuntimeException,所以无需指定异常类型事务也能生效。但若手动抛出Exception异常,而Exception是RuntimeException的父类,会导致事务不生效。
解决方案:
1.指定Spring事务异常捕获类型
@Transactional(rollbackFor = Exception.class)
2.抛出Spring事务支持的异常类型
throw new RuntimeException("手动抛出运行时异常");
2、使用了try catch
原理:
异常被try catch块捕获,导致事务失效
解决方案:
在catch中抛出Spring事务支持的异常。
3、添加事务的方法必须是public,并且不能带有static、final关键字
Spring使用基于代理的AOP来实现声明式事务,代理只能拦截公共方法的调用。
并且基于代理的AOP无法拦截静态方法和final方法的调用
4、类未被Spring管理
原理:
Spring实现对象的动态代理,首先这个对象要交由Spring管理。
解决方案:
将类交由Spring管理,可添加@Service注解,或使用其他能够注册成Spring Bean的注解或方法。
5、数据表不支持事务
原理:
Spring事务基于数据库事务实现,有些数据表本身不支持事务,如MySql的MyISAM引擎,事务自然不生效。
解决方案:
将数据表改用支持事务的引擎,如MySql的InnoDB引擎。
6、Spring事务传播级别设置为不支持事务
原理:
@Transactional(propagation = Propagation.NOT_SUPPORTED) 不支持事务,若存在事务则挂起
@Transactional(propagation = Propagation.NEVER) 不使用事务,若存在事务则抛异常
解决方案:
使用Spring默认的传播级别(PROPAGATION_REQUIRED),或其他支持事务的传播级别。
7、未开启事务
解决方案:
@EnableTransactionManagement开启事务,Spring boot已自动装配,无需显示使用此注解。
8、多线程调用
@Transactional注解只能对当前线程的事务进行管理,而对于多个线程之间的事务协调和管理,需要使用分布式事务进行控制。可以使用Spring Cloud提供的分布式事务管理器,如Seata、TCC等,来协调多个线程之间的事务,确保事务的一致性。
五、事务使用分析
1、多方法事务范围分析
1、如下三个方法,在方法1中声明事务
,只要方法1事务生效,方法2和方法3事务也会生效
@Transactional(rollbackFor = Exception.class)
public void method1() {
method2();
}
public void method2() {
method3();
}
public void method3() {
}
在方法1中事务可以看成这样,即方法1开始时便开启了事务,直到方法1结束的时候才提交事务。
其中方法2是包含在方法1的事务里面
的,因此方法2和方法3都属于这个事务
public void method1() {
开启事务。。。
method2();
事务提交。。。
}
注:这里方法2和方法3无论是直接调用,还是代理调用都成立
2、这里设定方法1中是通过代理类调用的方法2,所以方法2中事务生效
public void method1() {
method2();
}
@Transactional(rollbackFor = Exception.class)
public void method2() {
method3();
}
public void method3() {
}
因此可以看成下面这样,所以方法1中内容未被事务管理,方法2和方法3中会事务生效
public void method1() {
...
method2(){
开启事务。。。
method3();
事务提交。。。
}
...
}
2、方法1 直接调用 方法2,在方法2 中添加事务
如下通过bean实例调用 login1,事务是否能生效?
答案:login1 和 login2 事务均不生效。
在Spring框架中,当你配置了事务管理器并使用了@Transactional注解来声明事务时,Spring通过使用AOP代理(默认情况下,是基于JDK的动态代理或CGLIB代理)将你的方法包装起来,用以控制事务的边界(即事务的开始和结束)。只有通过这个代理对象调用的方法,Spring才会管理其事务行为
。因为代理的增强逻辑只会被应用在通过代理对象调用的方法上。
login1()在执行的时候调用了login2(),这时调用是直接的方法调用,并不通过代理对象
。因此,即使login2()被声明为@Transactional,Spring也不会为其创建新的事务。在这种情况下,login2()就会参与到login1()的事务中,如果login1()没有事务,那么login2()也不会有事务。
@PostMapping("/ceshi")
public void login1() {
AdminUserDO adminUserDO = adminUserMapper.selectById(131L);
adminUserDO.setNickname("哈哈");
adminUserMapper.updateById(adminUserDO);
login2(); // 调用方法2
if (1/0==1){
}
}
@Transactional(rollbackFor = Exception.class)
public void login2() {
AdminUserDO adminUserDO = adminUserMapper.selectById(146L);
adminUserDO.setNickname("嘻嘻");
adminUserMapper.updateById(adminUserDO);
}
3、方法1 调用 方法2,在方法1 中添加事务
如下通过bean实例调用 login1,事务是否能生效?
答案:login1 和 login2 事务 均能生效 ; 因为 login1 是通过代理对象调用,所以事务能生效,同事login2 也会参与到login1 中的事务,而不是因为他本身
login2中事务生效是因为它上面加了事务注解吗?
答案:不是;不管 login2 上面也没有事务注解都会被放入事务中。因为login1 是直接调用 login2,所以 login2中的事务注解未生效,而是引用了方法1中事务
@PostMapping("/ceshi")
@Transactional(rollbackFor = Exception.class)
public void login1() {
AdminUserDO adminUserDO = adminUserMapper.selectById(131L);
adminUserDO.setNickname("哈哈");
adminUserMapper.updateById(adminUserDO);
login2(); // 调用方法2
if (1/0==1){
}
}
@Transactional(rollbackFor = Exception.class)
public void login2() {
AdminUserDO adminUserDO = adminUserMapper.selectById(146L);
adminUserDO.setNickname("嘻嘻");
adminUserMapper.updateById(adminUserDO);
}
4、方法1 中通过 bean 调用方法2
如下 login1 中 事务不会生效,login 2中事务会生效。
因为 login1 是通过代理方式调用 login2.而login2中声明了事务。所以login2会事务回滚。而login1没有声明事务,所以不会回滚。其实可以看成调用方法2之前开启事务,方法2结束后提交事务,因此只有方法2中事务回滚
@Autowired
private AuthService authService ;
@PostMapping("/ceshi")
public void login1() {
AdminUserDO adminUserDO = adminUserMapper.selectById(131L);
adminUserDO.setNickname("哈哈");
adminUserMapper.updateById(adminUserDO);
authService.login2(); // 调用方法2
if (1/0==1){
}
}
@Transactional(rollbackFor = Exception.class)
public void login2() {
AdminUserDO adminUserDO = adminUserMapper.selectById(146L);
adminUserDO.setNickname("嘻嘻");
adminUserMapper.updateById(adminUserDO);
}