在日常开发中,为了保证数据的一致性和完整性,我们常常会使用事务。而在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、多线程操作事务

原因:事务是基于线程绑定的,每个线程都有自己的事务上下文,而多线程环境下可能会存在多个线程共享同一个事务上下文的情况,导致事务不生效。