Spring中事务的失效场景

目录

1.代理失效

1.1 非public方法

1.2 方法用final或者static修饰

1.3 方法内部调用

解决办法

1.3.1 通过AopContext拿到代理对象,来调用

1.3.2 依赖注入自己(代理)来调用

1.3.3 使用编程式事务来管理

1.4 类未被Spring管理 

2.@Transaction使用问题

2.1 用错注解

2.2 设置了错误的传播特性

2.3 rollbackFor属性设置错误

3.其他

3.1 数据库引擎不支持事务

3.2 自己捕获了异常

3.3 多线程调用


1.代理失效

1.1 非public方法

        spring 要求被代理方法必须是public的。也就是说,如果我们自定义的事务方法(即目标方法),它的访问权限不是public,而是 private、default 或 protected 的话,spring 则不会提供事务功能。

  @Service
    public class UserService {
        
        @Transactional
        private void add(UserModel userModel) {
             saveData(userModel);
             updateData(userModel);
        }
    }

1.2 方法用final或者static修饰

        有时候,某个方法不想被子类重写,这时可以将该方法定义成 final 的。普通方法这样定义是没问题的,但如果将事务方法定义成 final,例如:

 @Service
    public class UserService {
     
        @Transactional
        public final void add(UserModel userModel){
            saveData(userModel);
            updateData(userModel);
        }
    }

        spring 事务底层使用了 aop,也就是通过 jdk 动态代理或者 cglib,帮我们生成了代理类,在代理类中实现了事务功能。 但如果某个方法用 final 修饰了,那么在它的代理类中,就无法重写该方法,而无法添加事务功能。

        注意:如果某个方法是 static 的,同样无法通过动态代理,因为它是属于类的,并不是对象的,所以无法被AOP

1.3 方法内部调用

        有时候我们需要在某个 Service 类的某个方法中,调用另外一个事务方法,比如:

   @Service
    public class UserService {
     
        @Autowired
        private UserMapper userMapper;
     
      
        public void add(UserModel userModel) {
            userMapper.insertUser(userModel);
            updateStatus(userModel);
        }
     
        @Transactional
        public void updateStatus(UserModel userModel) {
            doSameThing();
        }
    }

        我们看到在非事务方法 add 中,直接调用事务方法 updateStatus。从前面介绍的内容可以知道,updateStatus 方法拥有事务的能力是因为 spring aop 生成代理了对象,但是这种方法直接调用了 this 对象的方法,所以 updateStatus 方法不会生成事务。

解决办法

1.3.1 通过AopContext拿到代理对象,来调用

        注意:(需要在配置类中开启 @EnableAspectJAutoProxy(exposeProxy = true))

   @Service
    public class UserService {
     
        @Autowired
        private UserMapper userMapper;
     
      
        public void add(UserModel userModel) {
            userMapper.insertUser(userModel);
            ((UserService) AopContext.currentProxy()).updateStatus(userModel);
        }
     
        @Transactional
        public void updateStatus(UserModel userModel) {
            doSameThing();
        }
    }

1.3.2 依赖注入自己(代理)来调用

        其实本质就是将调用的方法移动到另一个Bean中

 @Service
 public class UserService {
 
     @Autowired
     private UserService1 proxy; 
 	
     @Autowired
     private UserMapper userMapper;
     
     public void add(UserModel userModel) {
          userMapper.insertUser(userModel);
          proxy.updateStatus(userModel);
     }
 }
@Service
public class UserService1 {
     @Transactional
     public void updateStatus(UserModel userModel){
         doSameThing();
     }
}
1.3.3 使用编程式事务来管理

        如果无法避免使用 this 调用,并且需要确保事务的正确管理,可以考虑使用编程式事务管理,手动控制事务的开启、提交和回滚。虽然相对于声明式事务管理来说更为复杂,但是可以绕过Spring代理的限制。

1.4 类未被Spring管理 

        在我们平时开发过程中,有个细节很容易被忽略,即使用 spring 事务的前提是:对象要被 spring 管理,需要创建 bean 实例。通常情况下,我们通过 @Controller、@Service、@Component、@Repository 等注解,可以自动实现 bean 实例化和依赖注入的功能。

如果有一天,你匆匆忙忙地开发了一个 Service 类,但忘了加 @Service 注解,比如:

    //@Service
    public class UserService {
     
        @Transactional
        public void add(UserModel userModel) {
             saveData(userModel);
             updateData(userModel);
        }    
    }

        从上面的例子,我们可以看到 UserService 类没有加@Service注解,那么该类不会交给 spring 管理,所以它的 add 方法也不会生成事务。

2.@Transaction使用问题

关于@Transaction注解相关参数问题可以看下写的这篇博客:Spring事务中的@Transactional注解中的参数说明-CSDN博客

2.1 用错注解

        使用的不是org.springframework.transaction.annotation包下的注解

2.2 设置了错误的传播特性

        其实,我们在使用@Transactional注解时,是可以指定propagation参数的。

如果我们在手动设置 propagation 参数的时候,把传播特性设置错了,比如:

   @Service
    public class UserService {
     
        @Transactional(propagation = Propagation.NEVER)
        public void add(UserModel userModel) {
            saveData(userModel);
            updateData(userModel);
        }
    }

          我们可以看到 add 方法的事务传播特性定义成了 Propagation.NEVER,这种类型的传播特性不支持事务,如果有事务则会抛异常。

目前只有这三种传播特性才会创建新事务:REQUIRED,REQUIRES_NEW,NESTED。

2.3 rollbackFor属性设置错误

        在使用 @Transactional 注解声明事务时,有时我们想自定义回滚的异常,spring 也是支持的。可以通过设置rollbackFor参数,来完成这个功能。

但如果这个参数的值设置错了,就会引出一些莫名其妙的问题,例如:

@Slf4j
@Service
public class UserService {
        
     @Transactional(rollbackFor = BusinessException.class)
     public void add(UserModel userModel) throws Exception {
         saveData(userModel);
         updateData(userModel);
  	}
 }

        如果在执行上面这段代码,保存和更新数据时,程序报错了,抛了 SqlException、DuplicateKeyException 等异常。而 BusinessException 是我们自定义的异常,报错的异常不属于 BusinessException,所以事务也不会回滚。

即使 rollbackFor 有默认值,但阿里巴巴开发者规范中,还是要求开发者重新指定该参数。

这是为什么呢?

        因为如果使用默认值 (rollbackFor默认值为UncheckedException,包括了RuntimeException和Error.),一旦程序抛出了 Exception,事务不会回滚,这会出现很大的 bug。所以,建议一般情况下,将该参数设置成:Exception 或 Throwable。

3.其他

3.1 数据库引擎不支持事务

        众所周知,在 mysql5 之前,默认的数据库引擎是myisam。

它的好处就不用多说了:索引文件和数据文件是分开存储的,对于查多写少的单表操作,性能比 innodb 更好。有些老项目中,可能还在用它。在创建表的时候,只需要把ENGINE参数设置成MyISAM即可:

    CREATE TABLE `category` (
      `id` bigint NOT NULL AUTO_INCREMENT,
      `one_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
      `two_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
      `three_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
      `four_category` varchar(20) COLLATE utf8mb4_bin DEFAULT NULL,
      PRIMARY KEY (`id`)
    ) ENGINE=MyISAM AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin

        myisam 好用,但有个很致命的问题是:不支持事务。

如果只是单表操作还好,不会出现太大的问题。但如果需要跨多张表操作,由于其不支持事务,数据极有可能会出现不完整的情况。

3.2 自己捕获了异常

事务不会回滚,最常见的问题是:开发者在代码中手动 try...catch 了异常。比如:

    @Slf4j
    @Service
    public class UserService {
        
        @Transactional
        public void add(UserModel userModel) {
            try {
                saveData(userModel);
                updateData(userModel);
            } catch (Exception e) {
                log.error(e.getMessage(), e);
            }
        }
    }

        这种情况下 spring 事务当然不会回滚,因为开发者自己捕获了异常,又没有手动抛出,换句话说就是把异常吞掉了。如果想要 spring 事务能够正常回滚,必须抛出它能够处理的异常。如果没有抛异常,则 spring 认为程序是正常的。

3.3 多线程调用

        如果是使用的@Transactional这种声明式事务的话,在多线程情况下是无法生效的,主要是因为这种方式是通过ThreadLocal来存储事务上下文的,而ThreadLocal是线程隔离的,即每个线程都有自己的事务上下文,因为在多线程情况下这种方式会失效,即新线程中的操作不会被包含在原有的事务中,不过,如果需要管理跨线程的事务,我们可以使用编程式事务,即自己用TransactionTemplate来控制事务的提交

相关推荐

  1. Spring事务失效场景

    2024-05-15 15:46:07       48 阅读
  2. Spring事务失效场景

    2024-05-15 15:46:07       38 阅读
  3. Spring事务失效场景

    2024-05-15 15:46:07       36 阅读
  4. Spring事务几种失效场景

    2024-05-15 15:46:07       30 阅读
  5. spring事务失效场景

    2024-05-15 15:46:07       37 阅读
  6. Spring事务失效场景

    2024-05-15 15:46:07       36 阅读
  7. spring 事务失效几种场景

    2024-05-15 15:46:07       26 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-05-15 15:46:07       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-05-15 15:46:07       100 阅读
  3. 在Django里面运行非项目文件

    2024-05-15 15:46:07       82 阅读
  4. Python语言-面向对象

    2024-05-15 15:46:07       91 阅读

热门阅读

  1. Windows下打包项目成Linux版本

    2024-05-15 15:46:07       34 阅读
  2. Scala编程基础7:模式匹配、隐式转换详解

    2024-05-15 15:46:07       34 阅读
  3. 前端下载文件流

    2024-05-15 15:46:07       31 阅读
  4. 5.14 力扣每日一题 贪心

    2024-05-15 15:46:07       42 阅读
  5. dom驱动和数据驱动的理解

    2024-05-15 15:46:07       38 阅读
  6. Vue学习v-on

    2024-05-15 15:46:07       35 阅读
  7. 连接和断开与服务器的连接

    2024-05-15 15:46:07       28 阅读
  8. uniapp实现拖拽排序+滑动删除功能

    2024-05-15 15:46:07       31 阅读
  9. SQL Server BULK INSERT

    2024-05-15 15:46:07       38 阅读