在日常开发中,为了保证数据的一致性和完整性,我们常常会使用事务。而在spring开发中,使用事务很简单,只需要添加@Transactional注解。但是开发过程中,总会碰到事务不生效的场景,以下是我总结的一些场景及其解决方案。
1、配置类中未启用事务管理
原因:未在配置类中启用事务管理,spring不会创建事务代理对象。
解决方案:在对应的配置类中配置事务管理器。
@Configuration
public class DataSourceConfig {
@Bean
public PlatformTransactionManager transactionManager(EntityManagerFactoryBuilder builder){
return new DataSourceTransactionManager(dataSource());
}
}
注意:在Spring Boot项目中,默认自动配置事务管理器并开启事务支持。但若想自定义事务管理器,也可以用上述方法自定义。
2、类未被注入容器
原因:Spring事务是基于AOP机制实现的,通过@Transactional 注解找到切点,而之后则需要在容器中找到切点所在类的实例化bean,然后为bean创建事务代理对象。因此需要将类注入到容器中。
解决方案:在类上添加@Service、@Component等注解。
3、事务方法被final、static关键字修饰
原因:方法被final或static修饰后,则方法不能被子类重写,也就不能在该方法上进行动态代理,也就无法生成事务代理对象来管理事务。
解决方案::去除final或static修饰
4、类内部调用
@Service
public class TestImpl implements TestService {
@Autowired
private TestMapper testMapper;
public void test1(User user){
// 调用内部的事务方法
this.test2(user);
}
@Transactional
public void test2(User user) {
testMapper.save(user);
}
}
原因:同一个类内test1方法调用test2时,不是通过事务代理类调用,而是直接调用的,所以不会通过事务处理。
解决方案:避免在同一个类内方法调用。
5、方法访问权限不是public
public Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable {
// 获取被代理方法的修饰符
int timeout = determineTimeout(method);
final TransactionAttribute txAttr = determineTransactionAttribute(method);
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
// 如果被代理方法不是公共方法,则直接执行方法调用而不使用事务管理
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager) || !method.getDeclaringClass().isInterface() && method.getModifiers() != Modifier.PUBLIC) {
// no transactional context -> invoke directly
return invocation.proceedWithInvocation();
}
// ...
}
原因:代理类是在运行时生成的,因此它只能够代理公共的方法。如果你定义的事务方法不是公共的,那么在运行时生成的代理类中就无法包含该方法的切面,从而无法实现事务管理。
解决方案:改为public
6、@Transactional属性配置问题
原因:(1)readOnly设置为true表示该方法为只读事务,所以进行更新操作时会抛出异常;
(2)timeout设置时间过短,超时时间过短,事务超时就会抛出异常;
(3)propagation传播机制设置为NOT_SUPPORTED或NEVER时,表示该方法以非事务方式操作,如果存在事务则挂起或抛出异常;
(4)rollbackFor用来指定引发事务回滚的异常,若抛出的异常非指定的异常或其子类,则不会回滚。
解决方案:正确设置。
7、异常被捕获
原因:若在方法内部捕获异常,并且未继续抛出,则会导致事务不能捕获异常,也就不会进行回滚操作。
解决方案:方法内部不捕获异常,或者捕获之后进行相关操作后仍要抛出。
8、多线程操作事务
原因:事务是基于线程绑定的,每个线程都有自己的事务上下文,而多线程环境下可能会存在多个线程共享同一个事务上下文的情况,导致事务不生效。