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

Spring的事务传播机制

什么是Spring事务传播机制

复制代码
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方法上
java 复制代码
@Service
public class UserService {
    @Transactional
    private void updateUserData() { // private方法
        // ...
    }
}

由于代理机制会为 public 方法创建拦截器,事务可以正常生效。而非public得方法,JDK代理是不会创建拦截器的,虽然CGLIB可能支持,但行为不一致,不保证生效。

因此在使用时还是强烈建议放到public方法上。

类内部的调用,类内部方法自调用,内部类方法调用
java 复制代码
@Service
public class UserService {
    public void businessMethod() {
        // 1. 执行一些业务逻辑
        // 2. 调用本类的事务方法
        this.transactionalMethod(); // 自调用,事务失效
    }

    @Transactional
    public void transactionalMethod() {
        // 数据库操作
    }
}
java 复制代码
public class OuterClass{
    private class InnerClass {
        @Transactional
        public void doSomething() {
            System.out.println("Doing something in inner class...");
        }
    }
    public void invokeInnerClassMethod() {
        InnerClass innerclass = new InnerClass();
        innerclass.doSomething();//调用内部类方法,事务失效
    }
}

在对象内部调用其他方法,就会用对象直接调用了,而不是用代理对象,因此代理会失效。

static、final方法

由于static方法是属于类级别的对象,所以代理对象无法代理,因此AOP也是无效的,因此@Transactional修饰这种方法时,事务也是会失效的。

final方法,是固定形式,而AOP的代理是通过子类或实现接口来实现的,final方法无法被子类覆盖,也无法通过实现类覆盖。因此如果将@Transactional修饰这种方法时,事务也是会失效的。

不存在代理

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

@Transactional配置错误

@Transactional的propagation属性配置错误
java 复制代码
public class UserService{
	@Transactional(propagation = Propagation.NOT_SUPPORTED)
	public void notSupportedMethod() {
	    // 此方法不会运行在事务中
	}
}

不同的Propagation属性决定了事务的创建和参与方式。例如:Propagation.NOT_SUPPORTEDPropagation.NEVER会挂起或拒绝当前事务。

@Transactional的rollbackFor设置错误
java 复制代码
public class UserService{
	@Transactional(rollbackFor = FileNotFoundException.class)
	public void someMethod(){
	    // 抛出 IOException
	    throw new IOException("文件读取失败");
	    // 事务不会回滚
	}
}

rollbackFor配置的异常类型需和方法抛出的异常一致,事务才会进行回滚。改成使用@Transactional(rollbackFor = Exception.class)@Transactional(rollbackFor = IOException.class)即可。

@Transactional注解引用来源错误

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

没有启用事务管理

  • 原因:忘记在Spring配置中启用事务管理。
  • 解决方案:
    在Java配置中添加@EnableTransactionManagement。
    在XML配置中添加<tx:annotation-driven />。

异常被捕获

java 复制代码
public class UserService{
  @Transactional(rollbackFor = Exception.class)
  public void doSomething() {
      try {
          // doSomething...
      }catch (Exception e){
          System.out.println("Exception:"+e);
      }
  }
}

异常被捕获后,不会抛出,也就走不到rollbackFor这样也就不会进行回滚了。

在多线程环境下使用了声明式事务

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

数据库引擎不支持事务

如果使用的数据库表引擎不支持事务(如MySQL的MyISAM引擎),那么即使Spring配置了事务,也无法回滚。

解决方案:确保数据库表使用支持事务的引擎,如MySQL的InnoDB。

BeanFactory和FactoryBean的关系

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

BeanFactory

BeanFactory就是Bean的工厂,是整个Spring的IOC其中的一部分,管理Bean的创建和生命周期

BeanFactory提供了一系列的方法,可以让我们获取到具体的Bean实例。

你可能没有直接用过BeanFactory,但是你肯定间接的使用或者看到过。

java 复制代码
applicationContent.getBean(type);

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))

java 复制代码
// 定义一个FactoryBean
public class MyFactoryBean implements FactoryBean<MyObject> {
    public MyObject getObject() {
        return new MyObject(); // 返回实际对象
    }
    
    public Class<?> getObjectType() {
        return MyObject.class;
    }
    
    public boolean isSingleton() {
        return true;
    }
}

// 使用
BeanFactory beanFactory = new DefaultListableBeanFactory();
// 注册MyFactoryBean
beanFactory.registerSingleton("myFactoryBean", new MyFactoryBean());

// 获取FactoryBean创建的对象
MyObject obj = (MyObject) beanFactory.getBean("myFactoryBean"); // 返回MyObject实例

// 获取FactoryBean本身
FactoryBean<MyObject> factoryBean = (FactoryBean<MyObject>) beanFactory.getBean("&myFactoryBean");

FactoryBean常用于创建需要特殊初始化逻辑的Bean,如Spring AOP代理、JNDI数据源,kafka,Dubbo中都用FactoryBean与Spring做集成。

总结

特性 BeanFactory FactoryBean
本质 Spring容器核心接口 一个特殊Bean
名称含义 Bean的工厂 工厂类型的Bean
获取对象 获取Bean实例 获取getObject()返回的对象
是否为容器 否(它是容器中的一个Bean)
主要用途 管理所有Bean 自定义特定Bean的创建逻辑
相关推荐
keyan33896 小时前
#include<iostream>usingnamespacestd;intmain(){cout
spring
红衣小蛇妖6 小时前
LeetCode-704-二分查找
java·算法·leetcode·职场和发展
!chen6 小时前
【Spring Boot】自定义starter
java·数据库·spring boot
koko426 小时前
天津小公司面经
java·学习·面试
zjjuejin6 小时前
Maven 现代开发流程的集成
java·后端·maven
Zzzzmo_6 小时前
【Java】杨辉三角、洗牌算法
java·数据结构·算法
码界奇点6 小时前
基于飞算JavaAI的在线图书借阅平台从设计到实现的完整指南
java·飞书
QiZhang | UESTC6 小时前
JAVA算法练习题day27
java·开发语言·c++·算法·leetcode·hot100
hrrrrb6 小时前
【Spring Boot】Spring Boot 中常见的加密方案
java·spring boot·后端