spring事务失效的场景
1.访问权限
在AbstractFallbackTransactionAttributeSource
类的computeTransactionAttribute
方法中有个判断,如果目标方法不是 public,则TransactionAttribute
返回 null,即不支持事务。
也就是说,如果我们自定义的事务方法(即目标方法),它的访问权限不是public
,而是 private、default 或 protected 的话,spring 则不会提供事务功能。
2.final修饰
spring的事务基于AOP,而AOP基于cglib ,而cglib动态代理的本质是基于继承实现的,在java中一旦方法被final修饰,则不能被重写,类被final修饰不能被继承。
对于final修饰方法:下面这段话摘自《Java编程思想》第四版第143页:
“使用final方法的原因有两个。第一个原因是把方法锁定,以防任何继承类修改它的含义;第二个原因是效率。在早期的Java实现版本中,会将final方法转为内嵌调用。但是如果方法过于庞大,可能看不到内嵌调用带来的任何性能提升。在最近的Java版本中,不需要使用final方法进行这些优化了。“
3.方法内部调用
在同一个类中,方法直接内部的调用,会导致事务失效
实例代码:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
public void add(UserModel userModel) {
userMapper.insertUser(userModel);
updateStatus(userModel);
}
@Transactional
public void updateStatus(UserModel userModel) {
doSameThing();
}
}
具体原因:在于调用的updateStatus方法不是代理对象的updateStatus方法
在我们看到在事务方法 add 中,直接调用事务方法 updateStatus,updateStatus 方法拥有事务的能力是因为 spring aop 生成代理了对象,但是这种方法直接调用了 this 对象的方法,所以 updateStatus 方法不会生成事务。
解决:通过依赖注入获取代理对象,通过代理对象调用其updateStatus方法就可。
或者通过AopContext类获取到代理对象,再通过代理对象调用
4.未被spring管理
也就是未注入到spring,事务未生效
5.多线程调用
spring事务是通过数据库链接实现的,同一事务是同一数据链接,不同线程涉及的事务是属于不同的数据库链接,只有拥有同一数据库链接,事务才可以提交和回滚。
实例问题代码:
@Slf4j
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
new Thread(() -> {
roleService.doOtherThing();
}).start();
}
}
@Service
public class RoleService {
@Transactional
public void doOtherThing() {
System.out.println("保存role表数据");
}
}
/**
从上面的例子中,我们可以看到事务方法 add 中,调用了事务方法 doOtherThing,但是事务方法 doOtherThing 是在另外一个线程中调用的。
这样会导致两个方法不在同一个线程中,获取到的数据库连接不一样,从而是两个不同的事务。如果想 doOtherThing 方法中抛了异常,add 方法也回滚是不可能的
**/
6.表是否支持事务
在myisam 数据库引擎中是不支持事务的
扩展:myisam 不支持事务,不支持行锁,不支持外键
7.事务是否开启
传统spring项目需要在配置文件 applicationContext.xml 中开启事务,事务才可以生效
对于springboot项目 springboot 通过DataSourceTransactionManagerAutoConfiguration
类,已经默默地帮你开启了事务。只需要配置spring.datasource
相关参数即可。
8.错误的传播特性
使用@Transactional注解时,是可以指定propagation参数的。
该参数的作用是指定事务的传播特性,spring 目前支持 7 种传播特性:
-
REQUIRED 如果当前上下文中存在事务,则加入该事务,如果不存在事务,则创建一个事务,这是默认的传播属性值。
-
SUPPORTS 如果当前上下文中存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。
-
MANDATORY 当前上下文中必须存在事务,否则抛出异常。
-
REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。
-
NOT_SUPPORTED 如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。
-
NEVER 如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。
-
NESTED 如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
9.手动捕获处理了异常
程序员将操作数据库方法发生的异常自己捕获并处理,让spring认为该代码未发生异常,则便不会发生事务回滚
实例问题代码:
@Slf4j
@Service
public class UserService {
@Transactional
public void add(UserModel userModel) {
try {
saveData(userModel);
updateData(userModel);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
}
//如果想要 spring 事务能够正常回滚,必须抛出它能够处理的异常。如果没有抛异常,则 spring 认为程序是正常的。
10.手动抛出spring事务不捕获的异常
对于spring事务来说,默认只捕获RuntimeException和Error
默认情况下只会回滚RuntimeException
(运行时异常)和Error
(错误),对于普通的 Exception(非运行时异常),它不会回滚。
实例问题代码:
@Slf4j
@Service
public class UserService {
@Transactional
public void add(UserModel userModel) throws Exception {
try {
saveData(userModel);
updateData(userModel);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new Exception(e);
}
}
}
11.自定义了回滚异常
@Transactional 注解声明事务时,有时我们想自定义回滚的异常,通过设置rollbackFor
参数
实例代码:
@Slf4j
@Service
public class UserService {
@Transactional(rollbackFor = BusinessException.class)
public void add(UserModel userModel) throws Exception {
saveData(userModel);
updateData(userModel);
}
}
如果在执行上面这段代码,保存和更新数据时,程序报错了,抛了 SqlException、DuplicateKeyException 等异常。而 BusinessException 是我们自定义的异常,报错的异常不属于 BusinessException,所以事务也不会回滚。
即使rollbackFor有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。 这是为什么呢? 因为如果使用默认值,一旦程序抛出了Exception,事务不会回滚,这会出现很大的bug。所以,建议一般情况下,将该参数设置成: Exception或Throwable
12.嵌套事务异常未捕获导致未必要回滚
问题代码:
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
roleService.doOtherThing();
}
}
@Service
public class RoleService {
@Transactional(propagation = Propagation.NESTED)
public void doOtherThing() {
System.out.println("保存role表数据");
}
}
doOtherThing发生异常,该异常未捕获,会继续向上抛出到add方法,导致在add方法内正常更新操作的insertUser方法也发生不必要回滚
解决办法便是通过手动捕获doOtherThing发生的异常,使异常不再向上抛出。就不会使add方法捕获到异常发生updateUser的回滚。
扩展:其他注意点
大事务问题(声明式事务的问题)
直接在类或方法上加上@Transational注解,使得不必要使用事务的方法加上了事务,造成整个程序事务非常耗时
推荐使用编程式事务
编程式事务
:spring 还提供了另外一种创建事务的方式,即通过手动编写代码实现的事务。
@Autowired
private TransactionTemplate transactionTemplate;
...
public void save(final User user) {
queryData1();
queryData2();
transactionTemplate.execute((status) => {
addData1();
updateData2();
return Boolean.TRUE;
})
}
在 spring 中为了支持编程式事务,专门提供了一个类:TransactionTemplate,在它的 execute 方法中,就实现了事务的功能。
相较于@Transactional注解声明式事务,我更建议大家使用基于TransactionTemplate的编程式事务。主要原因如下:
避免由于 spring aop 问题导致事务失效的问题。
能够更小粒度地控制事务的范围,更直观。
建议在项目中少使用 @Transactional 注解开启事务。但并不是说一定不能用它,如果项目中有些业务逻辑比较简单,而且不经常变动,使用 @Transactional 注解开启事务也无妨,因为它更简单,开发效率更高,但是千万要小心事务失效的问题。
复习:事务的传播特性
事务的传播特性,spring 目前支持 7 种传播特性:
-
REQUIRED 如果当前上下文中存在事务,则加入该事务,如果不存在事务,则创建一个事务,这是默认的传播属性值。
-
SUPPORTS 如果当前上下文中存在事务,则支持事务加入事务,如果不存在事务,则使用非事务的方式执行。
-
MANDATORY 当前上下文中必须存在事务,否则抛出异常。
-
REQUIRES_NEW 每次都会新建一个事务,并且同时将上下文中的事务挂起,执行当前新建事务完成以后,上下文事务恢复再执行。
-
NOT_SUPPORTED 如果当前上下文中存在事务,则挂起当前事务,然后新的方法在没有事务的环境中执行。
-
NEVER 如果当前上下文中存在事务,则抛出异常,否则在无事务环境上执行代码。
-
NESTED 如果当前上下文中存在事务,则嵌套事务执行,如果不存在事务,则新建事务。
参考文章,原文地址:https://blog.csdn.net/hanjiaqian/article/details/120501741
标签:事务,场景,spring,失效,userModel,上下文,方法,public From: https://www.cnblogs.com/boyxiaokang/p/17449673.html