来源:捡田螺的美团面小男孩 大家好,我是事生效田螺。 日常开发中,场景我们经常使用到spring事务。美团面最近星球一位还有去美团面试,事生效被问了这么一道面试题: Spring 事务在哪几种情况下会不生效?场景今天田螺哥跟大家聊聊,spring事务不生效的美团面15种场景。 //@Service (注释了@Service) public class TianLuoServiceImpl implements TianLuoService { @Autowired private TianLuoMapper tianLuoMapper; @Autowired private TianLuoFlowMapper tianLuoFlowMapper; @Transactional public void addTianLuo(TianLuo tianluo) { //保存tianluo实体数据库记录 tianLuoMapper.save(tianluo); //保存tianluo流水数据库记录 tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo)); } } @Configuration public class AppConfig { // 没有配置事务管理器 } @Service public class MyService { @Transactional () { // ... } } :为了解决这个问题,应该在AppConfig中配置一个事务管器。例如:@Configuration public class AppConfig { @Bean () { new DataSourceTransactionManager(dataSource()); } } @Service public class MyService { @Transactional () { // ... } } 如果是Spring Boot项目,它默认会自动配置事务管理器并开启事务支持。 @Service public class TianLuoServiceImpl { @Autowired private TianLuoMapper tianLuoMapper; @Autowired private TianLuoFlowMapper tianLuoFlowMapper; @Transactional public final void addTianLuo(TianLuo tianluo) { //保存tianluo实体数据库记录 tianLuoMapper.save(tianluo); //保存tianluo流水数据库记录 tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo)); } } @Service public class TianLuoServiceImpl implements TianLuoService { @Autowired private TianLuoMapper tianLuoMapper; @Autowired private TianLuoFlowMapper tianLuoFlowMapper; public void addTianLuo(TianLuo tianluo){ // 调用内部的事务方法 this.executeAddTianLuo(tianluo); } @Transactional public void executeAddTianLuo(TianLuo tianluo) { tianLuoMapper.save(tianluo); tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo)); } } :可以新建多一个类,让这两个方法分开,分别在不同的类中。如下:@Service public class TianLuoExecuteServiceImpl implements TianLuoExecuteService { @Autowired private TianLuoMapper tianLuoMapper; @Autowired private TianLuoFlowMapper tianLuoFlowMapper; @Transactional public void executeAddTianLuo(TianLuo tianluo) { tianLuoMapper.save(tianluo); tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo)); } } @Service public class TianLuoAddServiceImpl implements TianLuoAddService { @Autowired private TianLuoExecuteService tianLuoExecuteService; public void addTianLuo(User user){ tianLuoExecuteService.executeAddTianLuo(user); } } 当然,有时候你也可以在该 Service 类中注入自己,或者通过AopContext.currentProxy()获取代理对象。 @Service public class TianLuoServiceImpl implements TianLuoService { @Autowired private TianLuoMapper tianLuoMapper; @Autowired private TianLuoFlowMapper tianLuoFlowMapper; @Transactional private void addTianLuo(TianLuo tianluo) { tianLuoMapper.save(tianluo); tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo)); } } Spring事务的服务器租用底层,还是依赖于数据库本身的事务支持。在MySQL中,MyISAM存储引擎是不支持事务的,InnoDB引擎才支持事务。因此开发阶段设计表的时候,确认你的选择的存储引擎是支持事务的。 ) public void updateUser(User user) { userDao.updateUser(user); } @Transactional(timeout = 1) () { //... } @Service public class TianLuoServiceImpl { @Autowired private TianLuoMapper tianLuoMapper; @Autowired private TianLuoFlowMapper tianLuoFlowMapper; @Transactional(propagation = Propagation.NOT_SUPPORTED) public void doInsertTianluo(TianLuo tianluo) throws Exception { tianLuoMapper.save(tianluo); tianLuoFlowMapper.saveFlow(buildFlowByTianLuo(tianluo)); } } 帮大家复习一下,Spring提供了七种事务传播机制。它们分别是: @Service public class TianLuoServiceImpl implements TianLuoService { @Autowired private TianLuoMapper tianLuoMapper; @Autowired private TianLuoFlowMapper tianLuoFlowMapper; @Transactional(rollbackFor = Error.class) public void addTianLuo(TianLuo tianluo) { //保存tianluo数据库记录 tianLuoMapper.save(tianluo); //保存tianluo流水数据库记录 tianLuoFlowMapper.saveFlow(tianluo); //模拟异常抛出 throw new Exception(); } } 大家可以看一下Transactional注解源码哈: public interface MyRepository { @Transactional void save(String data); } public class MyRepositoryImpl implements MyRepository { @Override public void save(String data) { // 数据库操作 } } public class MyService { @Autowired private MyRepository myRepository; @Transactional public void doSomething(String data) { myRepository.save(data); } } public class MyTianluoService extends MyService { @Transactional(propagation = Propagation.REQUIRES_NEW) public void doSomething(String data) { super.doSomething(data); } } @Service public class TianLuoServiceInOutService { @Autowired private TianLuoFlowService tianLuoFlowService; @Autowired private TianLuoMapper tianLuoMapper; @Transactional public void addTianLuo(TianLuo tianluo) throws Exception { tianLuoMapper.save(tianluo); tianLuoFlowService.saveFlow(tianluo); } } @Service public class TianLuoFlowService { @Autowired private TianLuoFlowMapper tianLuoFlowMapper; @Transactional(propagation = Propagation.NESTED) public void saveFlow(TianLuo tianLuo) { tianLuoFlowMapper.save(tianLuo); throw new RuntimeException(); } } 以上代码使用了嵌套事务,如果saveFlow出现运行时异常,会继续往上抛,到外层addTianLuo的方法,导致tianLuoMapper.save也会回滚啦。如果不想因为被内部嵌套的事务影响,可以用try-catch包住,如下: @Transactional public void addTianLuo(TianLuo tianluo) throws Exception { tianLuoMapper.save(tianluo); try { tianLuoFlowService.saveFlow(tianluo); } catch (Exception e) { ,e.getMessage()); } } @Service public class TianLuoService { @Autowired private TianLuoMapper tianLuoMapper; @Autowired private TianLuoFlowService tianLuoFlowService; @Transactional public void addTianLuo(TianLuo tianluo) { //保存tianluo数据库记录 tianLuoMapper.save(tianluo); //多线程调用 new Thread(() -> { tianLuoFlowService.saveFlow(tianluo); }).start(); } } @Service public class TianLuoFlowService { @Autowired private TianLuoFlowMapper tianLuoFlowMapper; @Transactional public void save(TianLuo tianLuo) { tianLuoFlowMapper.saveFlow(tianLuo); } } 在Spring事务管理器中,通过TransactionSynchronizationManager类来管理事务上下文。TransactionSynchronizationManager内部维护了一个ThreadLocal对象,用来存储当前线程的事务上下文。在事务开始时,TransactionSynchronizationManager会将事务上下文绑定到当前线程的ThreadLocal对象中,当事务结束时,TransactionSynchronizationManager会将事务上下文从ThreadLocal对象中移除。 @Service public class TianLuoServiceImpl implements TianLuoService { @Autowired private TianLuoMapper tianLuoMapper; @Autowired private TianLuoFlowMapper tianLuoFlowMapper; @Transactional public void addTianLuo(TianLuo tianluo) { try { //保存tianluo数据库记录 tianLuoMapper.save(tianluo); //保存tianluo flow数据库记录 tianLuoFlowMapper.saveFlow(tianluo); } catch (Exception e) { , tianluo.getId(),e.getMessage()); } } } : 事务中的异常已经被业务代码捕获并处理,而没有被正确地传播回事务管理器,事务将无法回滚。我们可以从spring源码(TransactionAspectSupport这个类)中找到答案:public abstract class TransactionAspectSupport implements BeanFactoryAware, InitializingBean { //这方法会省略部分代码,只留关键代码哈 @Nullable 在invokeWithinTransaction方法中,当Spring catch到Throwable异常的时候,就会调用completeTransactionAfterThrowing()方法进行事务回滚的逻辑。但是,在TianLuoServiceImpl类的spring事务方法addTianLuo中,直接把异常catch住了,并没有重新throw出来,因此 Spring自然就catch不到异常啦,因此事务回滚的逻辑就不会执行,事务就失效了。 :在spring事务方法中,当我们使用了try-catch,如果catch住异常,记录完异常日志什么的,一定要重新把异常抛出来,正例如下:@Service public class TianLuoServiceImpl implements TianLuoService { @Autowired private TianLuoMapper tianLuoMapper; @Autowired private TianLuoFlowMapper tianLuoFlowMapper; @Transactional(rollbackFor = Exception.class) public void addTianLuo(TianLuo tianluo) { try { //保存tianluo数据库记录 tianLuoMapper.save(tianluo); //保存tianluo flow数据库记录 tianLuoFlowMapper.saveFlow(tianluo); } catch (Exception e) { , tianluo.getId(),e.getMessage()); throw e; } } } @Service public class TianLuoServiceImpl implements TianLuoService { @Autowired private TianLuoMapper tianLuoMapper; @Autowired private TianLuoFlowMapper tianLuoFlowMapper; @Transactional public void addTianLuo(TianLuo tianluo) throws Exception { //保存tianluo数据库记录 tianLuoMapper.save(tianluo); //保存tianluo流水数据库记录 tianLuoFlowMapper.saveFlow(tianluo); throw new Exception(); } } 注解为事务范围的方法中,事务的回滚仅仅对于unchecked的异常有效。对于checked异常无效。也就是说事务回滚仅仅发生在,出现RuntimeException或Error的时候。通俗一点就是:代码中出现的空指针等异常,会被回滚。而文件读写、网络超时问题等,spring就没法回滚了。前言
1. 你的service类没有被Spring管理
2.没有在Spring配置文件中启用事务管理器
3. 事务方法被final、static关键字修饰
4. 同一个类中,方法内部调用
5.方法的访问权限不是public
解决方案:addTianLuo事务方法的访问权限修改为public。
6. 数据库的存储引擎不支持事务
7 .配置错误的 @Transactional 注解
@Transactional(readOnly = true 8.事务超时时间设置过短
9. 使用了错误的事务传播机制
10. rollbackFor属性配置错误
解决方案:rollbackFor属性指定的异常与抛出的异常匹配。
11.事务注解被覆盖导致事务失效
12.嵌套事务的坑
13. 事务多线程调用
14.异常被捕获并处理了,没有重新抛出
15. 手动抛了别的异常