文章目录
概要
记录一些Sprging事务失效的一些场景。
Sprging事务失效的场景
1. 事务方法所在的类没有被Spring管理
Spring声明式事务的实现完全依赖于Spring的AOP代理机制,未被Spring管理的类中的方法不受Spring的AOP代理管理,因此,声明式事务失效。
2. 方法没有被public 修饰。
@Transactional 只能用于 public 的方法上,否则事务不会生效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。之所以会失效的原因是因为在Spring AOP 代理时,TransactionInterceptor (事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CglibAopProxy 的内部类)的 intercept 方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource的 computeTransactionAttribute 方法,获取Transactional 注解的事务配置信息。
同理:final、static 修饰的方法也会使事务失效。
protected TransactionAttribute computeTransactionAttribute(Method method,
Class<?> targetClass) {
// Don't allow no-public methods as required.
if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
return null;
}
3. 方法内部调用
@Service
public class UserServiceImpl {
@Autowired
UserMapper userMapper;
public void A() {
B();
}
@Transactional
public void B() {
userMapper.deleteById(1);
int i = 10 / 0; //模拟发生异常
}
}
同一个类中有A、B两个方法,B方法使用@Transactional注解标注,在A方法中调用了B方法,在外部调用A方法时,B方法的事务不会生效。这是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
解决办法:
(1)引入自身bean
@Service
public class UserServiceImpl {
@Autowired
UserMapper userMapper;
@Autowired
UserServiceImpl userServiceImpl;
public void A() {
userServiceImpl.B();
}
@Transactional
public void B() {
userMapper.deleteById(1);
int i = 10 / 0; //模拟发生异常
}
}
(2) AopContext获取当前代理类
在启动类上添加注解@EnableAspectJAutoProxy(exposeProxy = true),表示是否对外暴露代理对象,即是否可以获取AopContext。然后,在业务类上使用AopContext。
@Service
public class UserServiceImpl {
@Autowired
UserMapper userMapper;
public void A() {
UserServiceImpl userServiceImpl = ((UserServiceImpl) AopContext.currentProxy());
userServiceImpl.B();
}
@Transactional
public void B() {
userMapper.deleteById(1);
}
}
4. 多线程调用
同一个事务,其实是指同一个数据库连接,只有拥有同一个数据库连接才能同时提交和回滚。如果在不同的线程,拿到的数据库连接肯定是不一样的,所以是不同的事务。线程B的事务回滚,不会导致线程A的事务也回滚。
@Service
public class UserServiceImpl {
@Autowired
UserMapper userMapper;
@Transactional
public void A() {
userMapper.deleteById(1);
new Thread(()->{
userMapper.deleteById(2);
int i = 10/0; //模拟发生异常
}).start();
}
}
如上,A 方法中 userMapper.deleteById(1) 不会回滚。
5. 没有开启事务支持
如果是SpringBoot项目,那么SpringBoot通过DataSourceTransactionManagerAutoConfiguration自动配置类帮我们开启了事务。如果是传统的Spring项目,则需要我们自己配置。在Spring配置文件配置如下:
<!-- 配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!-- 注解式事务声明配置-->
<tx:annotation-driven transaction-manager="transactionManager" />
6. 设置了错误的事务传播特性
不同的Spring事务传播特性,可能导致事务不会发生回滚。
7. 错误的手动抛出异常
(1)使用了 try-catch 捕获异常没有向上抛出
@s1f4j
@Service
public class UserService {
@Transactional
public void add(UserMcdel userModel) {
try f
saveData(userModel);
updateData(userModel);
} catch(
Exception e)
{
log.error(e.getMessage(), e);
}
}
这种情况下spring事务当然不会回滚,因为开发者自己捕获了异常,又没有手动抛出,换句话说就是把异常吞
掉了。
如果想要Spring事务能够正常回滚,必须抛出它能够处理的异常。如果没有抛异常,则spring认为程序是正常的。
(2)抛出的异常不正确
@s1f4j
@Service
public class UserService {
@Transactional
public void add(UserMcdel userModel) {
try f
saveData(userModel);
updateData(userModel);
} catch(
Exception e)
{
log.error(e.getMessage(), e);
threw new Exception();
}
}
上面的这种情况,开发人员自己捕获了异常,又手动抛出了异常:Exception,事务同样不会回滚。
因为spring事务,默认情况下只会回滚 RuntimeException (运行时异常)和 Error (错误),对于普通的异常(非运行时异常),spring事务不会回滚。
(3)自定义回滚了别的异常
在使用@Transactional 注解声明事务时,有时我们想自定义回滚的异常,Spring也是支持的。可以通过设置 rollbackFor 参数来设置。但是如果这个参数值设置错了,就会导致事务不会回滚。
@S1f4j
@Service
public class UserService {
@Transactional(rollbackFor = BusinessException.class)
public void add(UserModel userModel) throws Exception {
saveData(userModel);
updateData(userModel);
}
}
如果在执行上面这段代码,保存和更新数据时,程序报错了,抛了SqlException、DuplicateKeyException等异常,事务也不会回滚。因为 BusinessException 是我们自定义的异常,SqlException、DuplicateKeyException等不属于BusinessException 。
因此,即使rollbackFor有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。
如果使用默认值,一旦程序抛出了Exception,事务不会回滚,这会出现很大的bug。所以,建议一般情况
下,将该参数设置成:Exception或Throwable。
Exception,Throwable 是父类,继承Exception,Throwable 的异常能被正常回滚。
8. 嵌套事务回滚多了
```java
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
roleService.addRole();
}
}
@Service
public class RoleService {
@Transactional(propagation = Propagation.NESTED)
public void addRole() {
System.out.println("保存Role表数据");
}
}
以上情况使用了嵌套的内部事务,原本是希望调用roleService.addRole()方法时,如果出现了异常,只回addRole()方法里的内容吗,不回滚 userMapper.insertUser(userModel);但事实是,insertUser也回滚了。因为addRole()方法出现了异常,没有手动捕获,会继续往上抛,到外层add()方法捕获了异常,所以,直接回滚了整个事务,不单只回滚保存点。
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private RoleService roleService;
@Transactional
public void add(UserModel userModel) throws Exception {
userMapper.insertUser(userModel);
try {
roleService.addRole();
} catch (Exception e) {
log.error(e.getMessage(),e);
}
}
}
@Service
public class RoleService {
@Transactional(propagation = Propagation.NESTED)
public void addRole() {
System.out.println("保存Role表数据");
}
}
将嵌套事务放在try-catch中,手动捕获并且不继续往上抛,这样当内部嵌套事务出现异常时,只回滚内部事务,不影响外部事务。
9. 数据引擎不支持事务
例如: MySQL 使用MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB。