找回密码
 立即注册
首页 业界区 业界 技术面:Spring (事务传播机制、事务失效的原因、BeanF ...

技术面:Spring (事务传播机制、事务失效的原因、BeanFactory和FactoryBean的关系)

遗憩 前天 20:10
Spring的事务传播机制

什么是Spring事务传播机制
  1. Spring的事务传播机制,主要是用于控制多个事务方法相互调用时的事务行为。
复制代码
在后端复杂的业务场景中,多个事务之间的调用可能会导致事务的不一致,例如:数据重复提交,数据丢失等问题,使用事务传播机制可以避免这些问题的发生,从而保证事务的一致性和数据的完整性。
Spring的事务规定了7种传播行为

Spring 通过 @Transactional 注解的 propagation 属性来设置传播级别

  • Propagation.REQUIRED (默认)

    • 含义:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
    • 特点:这是最常用、且Spring默认的传播行为。适用于绝大多数业务场景。
    • 回滚:内部方法抛出异常未被捕获,会导致整个事务(包括外部方法的操作)回滚。

  • Propagation.REQUIRES_NEW

    • 含义:无论当前是否存在事务,都会创建一个新的事务,并将当前事务(如果存在)挂起。
    • 特点:创建的是一个完全独立的事务,有自己的提交和回滚边界。
    • 回滚:内部新事务回滚,不影响外部事务(如果外部事务正常提交)。
      外部事务回滚,会将内部新事务也回滚(因为外部事务的回滚会恢复到调用 REQUIRES_NEW 方法之前的状态)。
    • 场景:记录日志、发送通知、审计等需要独立提交的场景。

  • Propagation.NESTED

    • 含义:如果当前存在事务,则在嵌套事务内执行(基于数据库的 Savepoint 机制);如果不存在事务,则创建一个新事务。
    • 特点:不是创建一个真正的新事务,而是在当前事务中设置一个保存点(Savepoint)。它可以在不破坏外部事务的情况下进行部分回滚。
    • 回滚:内部嵌套事务回滚,只回滚到保存点,不影响外部事务已做的其他操作。外部事务回滚,会回滚整个事务,包括嵌套事务的操作。与 REQUIRES_NEW 区别:NESTED 是“子事务”,依赖于外部事务;REQUIRES_NEW 是“独立事务”,与外部事务并列。

  • Propagation.SUPPORTS

    • 含义:如果当前存在事务,则加入该事务;如果不存在事务,则以非事务方式执行。
    • 特点:对事务是“可有可无”的态度。
    • 场景:适用于只读操作或对事务不敏感的方法。

  • Propagation.NOT_SUPPORTED

    • 含义:以非事务方式执行操作。如果当前存在事务,则将当前事务挂起。
    • 特点:强制方法不运行在事务中,可以提高性能。
    • 场景:执行一些耗时长、不需要事务保证的操作。

  • Propagation.NEVER

    • 含义:以非事务方式执行,如果当前存在事务,则抛出异常。
    • 特点:强制禁止在事务中执行此方法。
    • 场景:某些特定操作明确要求不能在事务上下文中运行。

  • Propagation.MANDATORY

    • 含义:方法必须在一个已存在的事务中执行,如果当前没有事务,则抛出异常。
    • 特点:强制要求调用方必须提供一个事务。
    • 场景:用于那些必须作为更大事务一部分才能保证一致性的操作

面试题

面试官问:一个长的事务方法a,在读写分离的情况下,里面既有读库操作,也有写库操作,再调用个读库方法b,方法b该用什么传播机制呢?
答:这种情况,读方法如果是最后一步,直接not_supported就行了,避免读报错导致数据回滚。如果是中间步骤,最好还是要required,因为异常失败需要回滚一下。
例如:A B C三个操作,C就是最后一步,B就是中间步骤如果一个读操作在中间(如B操作)失败了,那么就需要让A做回滚,因为C还没执行,所以A必须回滚才能保证一致性。
Spring事务失效可能是哪些原因

首先,容易造成事务失效的方式是通过@Transactional注解方式的声明式事务。
@Transactional是基于Spring的AOP来实现的,而AOP机制又是基于动态代理实现的,如果代理失效那么事务也就失效了。
Spring事务失效的场景

AOP代理失效

@Transactional应用在非public方法上
  1. @Service
  2. public class UserService {
  3.     @Transactional
  4.     private void updateUserData() { // private方法
  5.         // ...
  6.     }
  7. }
复制代码
由于代理机制会为 public 方法创建拦截器,事务可以正常生效。而非public得方法,JDK代理是不会创建拦截器的,虽然CGLIB可能支持,但行为不一致,不保证生效。
因此在使用时还是强烈建议放到public方法上。
类内部的调用,类内部方法自调用,内部类方法调用
  1. @Service
  2. public class UserService {
  3.     public void businessMethod() {
  4.         // 1. 执行一些业务逻辑
  5.         // 2. 调用本类的事务方法
  6.         this.transactionalMethod(); // 自调用,事务失效
  7.     }
  8.     @Transactional
  9.     public void transactionalMethod() {
  10.         // 数据库操作
  11.     }
  12. }
复制代码
  1. public class OuterClass{
  2.     private class InnerClass {
  3.         @Transactional
  4.         public void doSomething() {
  5.             System.out.println("Doing something in inner class...");
  6.         }
  7.     }
  8.     public void invokeInnerClassMethod() {
  9.         InnerClass innerclass = new InnerClass();
  10.         innerclass.doSomething();//调用内部类方法,事务失效
  11.     }
  12. }
复制代码
在对象内部调用其他方法,就会用对象直接调用了,而不是用代理对象,因此代理会失效。
static、final方法

由于static方法是属于类级别的对象,所以代理对象无法代理,因此AOP也是无效的,因此@Transactional修饰这种方法时,事务也是会失效的。
final方法,是固定形式,而AOP的代理是通过子类或实现接口来实现的,final方法无法被子类覆盖,也无法通过实现类覆盖。因此如果将@Transactional修饰这种方法时,事务也是会失效的。
不存在代理

没有使用Spring管理bean,因此也就不会存在使用AOP来创建代理对象来保证事务。
@Transactional配置错误

@Transactional的propagation属性配置错误
  1. public class UserService{
  2.         @Transactional(propagation = Propagation.NOT_SUPPORTED)
  3.         public void notSupportedMethod() {
  4.             // 此方法不会运行在事务中
  5.         }
  6. }
复制代码
不同的Propagation属性决定了事务的创建和参与方式。例如:Propagation.NOT_SUPPORTED或Propagation.NEVER会挂起或拒绝当前事务。
@Transactional的rollbackFor设置错误
  1. public class UserService{
  2.         @Transactional(rollbackFor = FileNotFoundException.class)
  3.         public void someMethod(){
  4.             // 抛出 IOException
  5.             throw new IOException("文件读取失败");
  6.             // 事务不会回滚
  7.         }
  8. }
复制代码
rollbackFor配置的异常类型需和方法抛出的异常一致,事务才会进行回滚。改成使用@Transactional(rollbackFor = Exception.class)或@Transactional(rollbackFor = IOException.class)即可。
@Transactional注解引用来源错误

有时候,在写代码的时候,由于手快也没有注意@Transactional注解的引用来源,直接就用了,等出现问题的时候,排查了很久发现写的都没问题,但是还是不生效,然后找别人来帮你看,他上来就看了一下你用的@Transactional,发现并不是Spring中的,而是其他什么地方的,比如javax.transaction.Transactional ,这样也会导致事务失效。
没有启用事务管理


  • 原因:忘记在Spring配置中启用事务管理。
  • 解决方案:
    在Java配置中添加@EnableTransactionManagement。
    在XML配置中添加。
异常被捕获
  1. public class UserService{
  2.   @Transactional(rollbackFor = Exception.class)
  3.   public void doSomething() {
  4.       try {
  5.           // doSomething...
  6.       }catch (Exception e){
  7.           System.out.println("Exception:"+e);
  8.       }
  9.   }
  10. }
复制代码
异常被捕获后,不会抛出,也就走不到rollbackFor这样也就不会进行回滚了。
在多线程环境下使用了声明式事务

@Transactional的事务管理使用的是ThreadLocal来存储事务上下文,ThreadLocal存储的变量是线程隔离的,因此每个线程都有自己的事务上下文副本。所以Spring的声明式事务在多线程环境下会失效的风险。
数据库引擎不支持事务

如果使用的数据库表引擎不支持事务(如MySQL的MyISAM引擎),那么即使Spring配置了事务,也无法回滚。
解决方案:确保数据库表使用支持事务的引擎,如MySQL的InnoDB。
BeanFactory和FactoryBean的关系

从名字上看BeanFactory和FactoryBean看着很相似,但是实际上它俩没什么关系,是完全不相关的两个接口。
BeanFactory

BeanFactory就是Bean的工厂,是整个Spring的IOC其中的一部分,管理Bean的创建和生命周期
BeanFactory提供了一系列的方法,可以让我们获取到具体的Bean实例。
你可能没有直接用过BeanFactory,但是你肯定间接的使用或者看到过。
  1. applicationContent.getBean(type);
  2. applicationContent.getBean(name);
复制代码
这些代码通常用在一些测试用例,或者需要手动从IOC容器中获取指定的Bean的时候使用。
通过上面的代码使用示例也说明了,BeanFactory是IOC容器的一个接口,用来获取Bean以及管理Bean的依赖注入和生命周期
FactoryBean

FactoryBean本质是一个特殊的Bean,用于定义一个工厂Bean,可以用来生成某些特定的Bean。
当项目中定义了某一个Bean的时候,如果这个Bean实现了FactoryBean这个接口,那么使用这个Bean的时候,Spring的IOC容器不会直接返回这个Bean实例,而是返回FactoryBean的getObject()方法返回的实体对象。(获取FactoryBean本身:需要在ID前加&符号(如&myFactoryBean))
  1. // 定义一个FactoryBean
  2. public class MyFactoryBean implements FactoryBean<MyObject> {
  3.     public MyObject getObject() {
  4.         return new MyObject(); // 返回实际对象
  5.     }
  6.    
  7.     public Class<?> getObjectType() {
  8.         return MyObject.class;
  9.     }
  10.    
  11.     public boolean isSingleton() {
  12.         return true;
  13.     }
  14. }
  15. // 使用
  16. BeanFactory beanFactory = new DefaultListableBeanFactory();
  17. // 注册MyFactoryBean
  18. beanFactory.registerSingleton("myFactoryBean", new MyFactoryBean());
  19. // 获取FactoryBean创建的对象
  20. MyObject obj = (MyObject) beanFactory.getBean("myFactoryBean"); // 返回MyObject实例
  21. // 获取FactoryBean本身
  22. FactoryBean<MyObject> factoryBean = (FactoryBean<MyObject>) beanFactory.getBean("&myFactoryBean");
复制代码
FactoryBean常用于创建需要特殊初始化逻辑的Bean,如Spring AOP代理、JNDI数据源,kafka,Dubbo中都用FactoryBean与Spring做集成。
总结

特性BeanFactoryFactoryBean本质Spring容器核心接口一个特殊Bean名称含义Bean的工厂工厂类型的Bean获取对象获取Bean实例获取getObject()返回的对象是否为容器是否(它是容器中的一个Bean)主要用途管理所有Bean自定义特定Bean的创建逻辑
1.png

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册