spring事务不生效的多种场景

数据库的存储引擎不支持事务

Spring事务的底层,还是依赖于数据库本身的事务支持。在MySQL中,MyISAM存储引擎是不支持事务的,InnoDB引擎才支持事务。因此开发阶段设计表的时候,确认你的选择的存储引擎是支持事务的。

service类没有被Spring管理

//@Service (注释了@Service)
public class ServiceImpl implements Service {
    
    

    @Autowired
    private Mapper1 mapper1;
	@Autowired
    private Mapper2 mapper2;
    
    @Transactional
    public void add(Test test) {
    
    
    
        mapper1.save(test);
        mapper2.save(test);

    }
}

事务不生效的原因:
上面例子中, @Service注解注释之后,spring事务(@Transactional)没有生效,因为Spring事务是由AOP机制实现的,也就是说从Spring IOC容器获取bean时,Spring会为目标类创建代理,来支持事务的。但是@Service被注释后,你的service类都不是spring管理的,那怎么创建代理类来支持事务呢。

解决方案:
加上@Service注解。

没有在Spring配置文件中启用事务管理器

事务不生效的原因:
没有在AppConfig中配置事务管理器,因此Spring无法创建事务代理对象,导致事务不生效。即使在MyService中添加了@Transactional注解,该方法也不会被Spring管理的事务代理拦截。
解决方案:
为了解决这个问题,应该在AppConfig中配置一个事务管器。例如:

@Configuration
public class AppConfig {
    
    
    @Bean
    public PlatformTransactionManager transactionManager() {
    
    
        return new DataSourceTransactionManager(dataSource());
    }
}

@Service
public class MyService {
    
    
    @Transactional
    public void doSomething() {
    
    
        // ...
    }
}

如果是Spring Boot项目,它默认会自动配置事务管理器并开启事务支持。

事务方法被final、static、private关键字修饰

@Service
public class ServiceImpl implements Service {
    
    

    @Autowired
    private Mapper1 mapper1;
	@Autowired
    private Mapper2 mapper2;
    
    @Transactional
    public final void add(Test test) {
    
    
    
        mapper1.save(test);
        mapper2.save(test);

    }
}

事务不生效的原因:
Spring事务基于Spring AOP,Spring AOP底层用的动态代理,动态代理有两种方式:
基于接口代理(JDK代理)
基于接口代理,凡是类的方法非public修饰,或者用了static、final关键字修饰,那这些方法都不能被Spring AOP增强
基于CGLib代理(子类代理)
基于子类代理,凡是类的方法使用了private、static、final修饰,那这些方法都不能被Spring AOP增强 由于是继承关系,无法代理final的类和方法(无法继承),或是private的方法(对子类不可见),static方法(不能被重写)

解决方案
删除 final 修饰或者 static 修饰

同一个类中,方法内部调用

@Service
public class ServiceImpl implements Service {
    
    

    @Autowired
    private Mapper1 mapper1;
	@Autowired
    private Mapper2 mapper2;
    
    public void execute(Test test) {
    
    
		//调用内部事务方法
		this.add(test);
	}

    @Transactional
    public void add(Test test) {
    
    
    
        mapper1.save(test);
        mapper2.save(test);

    }
}

事务不生效的原因:
事务是通过Spring AOP代理来实现的,而在同一个类中,一个方法调用另一个方法时,调用方法直接调用目标方法的代码,而不是通过代理类进行调用。即以上代码,调用目标executeAddTianLuo方法不是通过代理类进行的,因此事务不生效。
解决方案:
1.可以新建多一个类,让这两个方法分开,分别在不同的类中
2.在该 Service 类中注入自己,或者通过AopContext.currentProxy()获取代理对象

配置错误的 @Transactional 注解

@Transactional(readOnly = true, timeout = 1)
public void updateUser(User user) {
    
    
    userDao.updateUser(user);
}

事务不生效的原因:
虽然使用了@Transactional注解,但是注解中的readOnly=true属性指示这是一个只读事务,因此在更新User实体时会抛出异常。
timeout属性被设置为1秒,这意味着如果事务在1 秒内无法完成,则报事务超时了。

使用了错误的事务传播机制

@Service
public class ServiceImpl implements Service {
    
    

    @Autowired
    private Mapper1 mapper1;
	@Autowired
    private Mapper2 mapper2;
    
    public void execute(Test test) {
    
    
		//调用内部事务方法
		this.add(test);
	}

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void add(Test test) {
    
    
    
        mapper1.save(test);
        mapper2.save(test);

    }
}

事务不生效的原因:
Propagation.NOT_SUPPORTED传播特性不支持事务。
解决方案:
选择正确的事务传播机制。

Spring提供了七种事务传播机制。它们分别是:
REQUIRED(默认):如果当前存在一个事务,则加入该事务;否则,创建一个新事务。该传播级别表示方法必须在事务中执行。
SUPPORTS:如果当前存在一个事务,则加入该事务;否则,以非事务的方式继续执行。
MANDATORY:如果当前存在一个事务,则加入该事务;否则,抛出异常。
REQUIRES_NEW:创建一个新的事务,并且如果存在一个事务,则将该事务挂起。
NOT_SUPPORTED:以非事务方式执行操作,如果当前存在一个事务,则将该事务挂起。
NEVER:以非事务方式执行操作,如果当前存在一个事务,则抛出异常。
NESTED:如果当前存在一个事务,则在嵌套事务内执行。如果没有事务,则按REQUIRED传播级别执行。嵌套事务是外部事务的一部分,可以在外部事务提交或回滚时部分提交或回滚。

异常被捕获并处理了,没有重新抛出

@Service
public class ServiceImpl implements Service {
    
    

    @Autowired
    private Mapper1 mapper1;
	@Autowired
    private Mapper2 mapper2;
    
    public void execute(Test test) {
    
    
		//调用内部事务方法
		this.add(test);
	}

    @Transactional
    public void add(Test test) {
    
    
    	try{
    
    
        	mapper1.save(test);
        	mapper2.save(test);
        } catch (Exception e) {
    
    
			log.error("xxxxx");
		}
    }
}

事务不生效的原因:
事务中的异常已经被业务代码捕获并处理,而没有被正确地传播回事务管理器,事务将无法回滚。
解决方案:
在spring事务方法中,当我们使用了try-catch,如果catch住异常,记录完异常日志什么的,一定要重新把异常抛出来

手动抛了别的异常

@Service
public class ServiceImpl implements Service {
    
    

    @Autowired
    private Mapper1 mapper1;
	@Autowired
    private Mapper2 mapper2;
    
    public void execute(Test test) {
    
    
		//调用内部事务方法
		this.add(test);
	}

    @Transactional
    public void add(Test test) throws Exception{
    
    

        	mapper1.save(test);
        	mapper2.save(test);
            throw new Exception();

    }
}

事务不生效的原因:
上面的代码例子中,手动抛了Exception异常,但是是不会回滚的,因为Spring默认只处理RuntimeException和Error,对于普通的Exception不会回滚,除非,用rollbackFor属性指定配置。
解决方案:
添加属性配置@Transactional(rollbackFor = Exception.class)

注解为事务范围的方法中,事务的回滚仅仅对于unchecked的异常有效。对于checked异常无效。也就是说事务回滚仅仅发生在,出现RuntimeException或Error的时候。通俗一点就是:代码中出现的空指针等异常,会被回滚。而文件读写、网络超时问题等,spring就没法回滚了。

rollbackFor属性配置错误

@Service
public class ServiceImpl implements Service {
    
    

    @Autowired
    private Mapper1 mapper1;
	@Autowired
    private Mapper2 mapper2;
    
    public void execute(Test test) {
    
    
		//调用内部事务方法
		this.add(test);
	}

    @Transactional(rollbackFor = Error.class)
    public void add(Test test) {
    
    
    
        mapper1.save(test);
        mapper2.save(test);
		//模拟异常抛出
        throw new Exception();
    }
}

事务不生效的原因:
其实rollbackFor属性指定的异常必须是Throwable或者其子类。默认情况下,RuntimeException和Error两种异常都是会自动回滚的。但是因为以上的代码例子,指定了rollbackFor = Error.class,但是抛出的异常又是Exception,而Exception和Error没有任何什么继承关系,因此事务就不生效。
解决方案:
rollbackFor属性指定的异常与抛出的异常匹配。

事务多线程调用

@Service
public class ServiceImpl implements Service {
    
    

    @Autowired
    private Mapper1 mapper1;
	@Autowired
    private ServiceImpl2 serviceImpl2;
    
    public void execute(Test test) {
    
    
		//调用内部事务方法
		this.add(test);
	}

    @Transactional(rollbackFor = Error.class)
    public void add(Test test) {
    
    
    
        mapper1.save(test);
        //多线程调用
        new Thread(() -> {
    
    
            serviceImpl2.saveFlow(test);
        }).start();
    }
}

@Service
public class ServiceImpl2 {
    
    

    @Autowired
    private Mapper2 mapper2;

    @Transactional
    public void save(Test test) {
    
    
        mapper2.saveFlow(test);
    }
}

事务不生效原因:
这是因为Spring事务是基于线程绑定的,每个线程都有自己的事务上下文,而多线程环境下可能会存在多个线程共享同一个事务上下文的情况,导致事务不生效。Spring事务管理器通过使用线程本地变量(ThreadLocal)来实现线程安全。

在Spring事务管理器中,通过TransactionSynchronizationManager类来管理事务上下文。TransactionSynchronizationManager内部维护了一个ThreadLocal对象,用来存储当前线程的事务上下文。在事务开始时,TransactionSynchronizationManager会将事务上下文绑定到当前线程的ThreadLocal对象中,当事务结束时,TransactionSynchronizationManager会将事务上下文从ThreadLocal对象中移除。

猜你喜欢

转载自blog.csdn.net/qq798280904/article/details/130728756