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方法上
@Service
public class UserService {@Transactionalprivate void updateUserData() { // private方法// ...}
}
由于代理机制会为 public 方法创建拦截器,事务可以正常生效。而非public得方法,JDK代理是不会创建拦截器的,虽然CGLIB可能支持,但行为不一致,不保证生效。
因此在使用时还是强烈建议放到public方法上。
类内部的调用,类内部方法自调用,内部类方法调用
@Service
public class UserService {public void businessMethod() {// 1. 执行一些业务逻辑// 2. 调用本类的事务方法this.transactionalMethod(); // 自调用,事务失效}@Transactionalpublic void transactionalMethod() {// 数据库操作}
}
public class OuterClass{private class InnerClass {@Transactionalpublic 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属性配置错误
public class UserService{@Transactional(propagation = Propagation.NOT_SUPPORTED)public void notSupportedMethod() {// 此方法不会运行在事务中}
}
不同的Propagation属性决定了事务的创建和参与方式。例如:Propagation.NOT_SUPPORTED
或Propagation.NEVER
会挂起或拒绝当前事务。
@Transactional的rollbackFor设置错误
public class UserService{@Transactional(rollbackFor = FileNotFoundException.class)public void someMethod(){// 抛出 IOExceptionthrow 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 />。
异常被捕获
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,但是你肯定间接的使用或者看到过。
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))
// 定义一个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的创建逻辑 |