Spring事务全景解码:探究`@Transactional`的七种传播行为及其面试真题解析
引言
Spring框架几乎成为了Java界的一个标准选项,而事务管理作为企业软件不可或缺的一环,其重要性自不必说。Spring通过@Transactional
注解提供了强大而灵活的声明式事务管理机制,使得开发者可以轻松地控制事务边界和传播行为。但是,随着七种不同的传播行为选项——REQUIRED
, SUPPORTS
, MANDATORY
, REQUIRES_NEW
, NOT_SUPPORTED
, NEVER
, 和 NESTED
——的出现,开发者在面对复杂业务场景时往往会感到困惑。本文将深入探究这七种传播行为的内在逻辑,通过详细的分析和典型的面试题目,帮助读者全面解码Spring事务管理的奥秘,确保你能在面试中和实际工作中都能游刃有余地应用这些知识。
七种不同的传播行为详细概述:
在Spring框架的@Transactional
注解中,传播行为(propagation behavior)定义了事务的边界。以下是七种不同的传播行为枚举及其作用:
1. REQUIRED (0):
- 这是最常用的传播行为。
- 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- 这意味着被调用的方法将在调用者的事务上下文中运行。
REQUIRED
是Spring框架中@Transactional
注解的默认传播行为。当一个方法被@Transactional(propagation = Propagation.REQUIRED)
注解时,其行为如下:
- 如果当前没有事务存在,Spring会启动一个新的事务,并且方法的执行会在这个新创建的事务上下文中进行。
- 如果当前已经存在一个事务,那么这个方法会加入到这个已经存在的事务中,与其他在同一事务中的方法共享相同的事务属性。
这种传播行为适用于大多数的事务需求,确保了事务的存在性,同时也支持了事务的嵌套。
代码实现原理
在Spring框架中,事务管理是通过AOP(面向切面编程)实现的。具体来说,Spring会在运行时为被@Transactional
注解的方法创建一个代理(Proxy),代理会包裹实际的方法调用。
以下是REQUIRED
传播行为的简化实现逻辑:
- 当一个被
@Transactional(propagation = Propagation.REQUIRED)
注解的方法被调用时,Spring的事务拦截器(TransactionInterceptor)会检查当前线程是否已经关联了一个事务。 - 如果没有关联的事务,事务拦截器会通过事务管理器(PlatformTransactionManager)创建一个新的事务。下面是一个使用
REQUIRED
传播行为的示例:
@Service
public class MyService {
@Transactional(propagation = Propagation.REQUIRED)
public void performServiceOperation() {
// 执行业务逻辑,该逻辑会在事务上下文中运行
}
}
在上面的例子中,当performServiceOperation
方法被调用时,如果当前没有事务,Spring会为它创建一个新的事务。如果已经存在一个事务,该方法会加入到这个现有的事务中。
底层支持
Spring的事务抽象层允许使用不同的事务管理器,这意味着REQUIRED
传播行为不仅限于JDBC事务,还可以应用于JPA、Hibernate或其他资源的事务。这种灵活性和一致性是Spring事务管理的一个关键优势。
2. SUPPORTS (1):
- 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
- 这种传播行为不会启动新的事务,它会将事务的创建决策留给调用者。
SUPPORTS是Spring框架中
@Transactional注解的一种传播行为。当一个方法被
@Transactional(propagation = Propagation.SUPPORTS)`注解时,其行为如下:
- 如果当前存在事务,那么方法会在这个已经存在的事务上下文中执行。
- 如果当前没有事务,那么方法会在非事务的上下文中执行,也就是说,不会为这个方法的执行创建任何新的事务。
这种传播行为适用于不需要事务管理的操作,例如只读查询或者可选事务操作,但同时它允许在事务环境中调用而不会破坏事务的完整性。
代码实现原理
Spring的事务管理同样是通过AOP实现的。对于SUPPORTS
传播行为,以下是其简化实现逻辑:
- 当一个被
@Transactional(propagation = Propagation.SUPPORTS)
注解的方法被调用时,Spring的事务拦截器(TransactionInterceptor)会检查当前线程是否已经关联了一个事务。 - 如果当前线程已经关联了一个事务,那么方法会在这个事务的上下文中执行。所有的数据库操作将作为这个现有事务的一部分来处理。
- 如果当前线程没有关联事务,方法会以非事务方式执行。这意味着方法内的数据库操作不会受到任何事务管理,就像普通的方法调用一样。
- 方法执行完成后,不会有任何事务提交或回滚的操作,因为
SUPPORTS
行为不负责事务的管理。
下面是一个使用SUPPORTS
传播行为的示例:
@Service
public class MyService {
@Transactional(propagation = Propagation.SUPPORTS)
public void performServiceOperation() {
// 执行业务逻辑,该逻辑可能在事务上下文中运行,也可能不在
}
}
在上面的例子中,performServiceOperation
方法的执行将依赖于调用它的上下文。如果调用者已经在事务中,该方法将参与该事务;如果调用者不在事务中,该方法将以非事务方式执行。
底层支持
与REQUIRED
传播行为一样,SUPPORTS
传播行为也不限于特定类型的事务,它可以应用于JDBC、JPA、Hibernate或其他资源的事务。Spring的事务抽象层确保了不同事务管理器的一致性和可互操作性。
注意事项
使用SUPPORTS
传播行为时,需要注意以下几点:
- 由于
SUPPORTS
不负责事务的创建,所以在这种模式下执行的方法必须能够接受在事务性和非事务性环境中运行。 - 如果方法可能会执行写操作或需要事务性保障,那么使用
SUPPORTS
可能不合适,因为在非事务性上下文中运行可能会导致数据不一致。 - 对于只读操作,
SUPPORTS
是一个合适的选择,因为它不会创建不必要的事务,从而避免了开销。
总之,SUPPORTS
传播行为提供了灵活性,允许方法在事务性和非事务性环境中透明地运行,但需要谨慎使用以避免不合适的场景。
3. MANDATORY (2):
-
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
-
这种传播行为要求调用方法必须在事务的上下文中执行,否则将会失败。
MANDATORY
是 Spring 框架中 @Transactional
注解的一种传播行为。使用 Propagation.MANDATORY
时,Spring 的事务管理行为如下:
- 如果当前存在事务,那么方法会在这个已经存在的事务上下文中执行。
- 如果当前没有事务,则抛出异常,具体来说是
IllegalTransactionStateException
。
这种传播行为适用于那些必须在事务环境中运行的操作,确保方法不会在没有事务的情况下执行。
代码实现原理
在 Spring 中,事务管理同样是通过 AOP(面向切面编程)机制实现的。对于 MANDATORY
传播行为,以下是其简化的实现逻辑:
- 当一个被
@Transactional(propagation = Propagation.MANDATORY)
注解的方法被调用时,Spring 的事务拦截器(TransactionInterceptor)会检查当前线程是否已经关联了一个事务。 - 如果当前线程已经关联了一个事务,那么方法会在这个事务的上下文中执行。所有的数据库操作将作为这个现有事务的一部分来处理。
- 如果当前线程没有关联事务,Spring 的事务拦截器会抛出
IllegalTransactionStateException
或类似的异常,表明一个事务是必需的,但并不存在。 - 因为
MANDATORY
行为不负责创建新的事务,所以它不会进行事务的提交或回滚操作。这些操作将由实际的事务发起者负责。
下面是一个使用 MANDATORY
传播行为的示例:
@Service
public class MyService {
@Transactional(propagation = Propagation.MANDATORY)
public void performServiceOperation() {
// 执行业务逻辑,该逻辑必须在事务上下文中运行
}
}
在上面的例子中,如果 performServiceOperation
方法在没有事务的上下文中被调用,它会抛出异常,因为它期望调用者已经启动了一个事务。
底层支持
MANDATORY
传播行为与其他传播行为一样,不限于特定类型的事务,它可以应用于 JDBC、JPA、Hibernate 或其他资源的事务。Spring 的事务抽象层确保了不同事务管理器的一致性和可互操作性。
注意事项
使用 MANDATORY
传播行为时,需要注意以下几点:
MANDATORY
传播行为非常严格,它要求调用方法必须已经在事务的上下文中,否则会抛出异常。- 这种传播行为适用于那些必须在事务中执行的操作,例如,当业务规则要求操作必须是原子性的时。
- 开发者需要确保
MANDATORY
传播行为用于正确的场景,并且调用者确实已经启动了事务。
总的来说,MANDATORY
传播行为是一种强制性的事务管理策略,它确保了方法只在现有事务的上下文中执行,从而保障了事务的强制性要求。
4. REQUIRES_NEW (3):
- 总是启动一个新的事务。
- 如果当前存在事务,则挂起当前事务,并创建一个新的事务给调用的方法。
- 这意味着被调用的方法将在自己的事务上下文中运行,与调用者的事务相独立。
REQUIRES_NEW
是 Spring 框架中 @Transactional
注解的一种传播行为。使用 Propagation.REQUIRES_NEW
时,Spring 的事务管理行为如下:
- 无论当前是否存在事务,都会创建一个新的事务。
- 如果当前已经存在一个事务,那么这个现有的事务会被挂起,直到新事务完全执行完毕。
这种传播行为适用于那些需要独立于当前事务环境执行的操作,例如,当你需要确保某些操作不被当前事务影响,或者不影响当前事务时。
代码实现原理
在 Spring 中,事务管理通过 AOP(面向切面编程)机制实现。对于 REQUIRES_NEW
传播行为,以下是其简化的实现逻辑:
- 当一个被
@Transactional(propagation = Propagation.REQUIRES_NEW)
注解的方法被调用时,Spring 的事务拦截器(TransactionInterceptor)会检查当前线程是否已经关联了一个事务。 - 如果当前线程已经关联了一个事务,那么这个现有事务会被挂起。挂起操作通常涉及到保存事务的状态,以便于后续恢复。
- 无论现有事务是否存在,Spring 的事务拦截器都会通过事务管理器(PlatformTransactionManager)创建一个新的事务。这个新事务具有自己的新的事务上下文。
- 被注解的方法会在这个新创建的事务上下文中执行。所有的数据库操作将作为这个新事务的一部分来处理。
- 方法执行完成后,无论成功还是异常结束,新的事务都会被提交或回滚。
- 新事务结束后,如果之前有被挂起的事务,那么这个挂起的事务会被恢复,并继续执行。
下面是一个使用 REQUIRES_NEW
传播行为的示例:
@Service
public class MyService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void performServiceOperation() {
// 执行业务逻辑,该逻辑会在自己的新事务上下文中运行
}
}
在上面的例子中,performServiceOperation
方法将始终在一个新的事务中执行,不依赖于外部的事务环境。
底层支持
REQUIRES_NEW
传播行为与其他传播行为一样,不限于特定类型的事务,它可以应用于 JDBC、JPA、Hibernate 或其他资源的事务。Spring 的事务抽象层确保了不同事务管理器的一致性和可互操作性。
注意事项
使用 REQUIRES_NEW
传播行为时,需要注意以下几点:
REQUIRES_NEW
传播行为可能会导致多个事务同时存在,这可能会增加数据库的锁定和并发管理的复杂性。- 如果频繁使用
REQUIRES_NEW
,可能会对性能产生负面影响,因为每次都需要创建和管理一个新的事务。 - 开发者需要确保
REQUIRES_NEW
传播行为用于正确的场景,特别是当需要事务独立性时。
总的来说,REQUIRES_NEW
传播行为提供了一种机制,允许方法在自己的事务上下文中执行,这对于需要保证操作独立性的场景非常有用。
5. NOT_SUPPORTED (4):
- 总是以非事务方式执行,并挂起任何存在的事务。
- 如果当前存在事务,则挂起事务,被调用的方法会在没有事务的情况下执行。
NOT_SUPPORTED
是 Spring 框架中@Transactional
注解的一种传播行为。当你使用Propagation.NOT_SUPPORTED
时,Spring 的事务管理行为如下:
- 如果当前存在事务,则该事务会被挂起,直到方法执行完毕。
- 方法会在没有事务的上下文中执行,即使调用者有一个活跃的事务,调用的方法也不会在事务中运行。
这种传播行为适用于那些不应该在事务中运行的操作,例如,当你想执行一些只读操作,并且不希望这些操作受到现有事务影响时。
代码实现原理
在 Spring 中,事务管理也是通过 AOP(面向切面编程)机制实现的。对于 NOT_SUPPORTED
传播行为,以下是其简化的实现逻辑:
- 当一个被
@Transactional(propagation = Propagation.NOT_SUPPORTED)
注解的方法被调用时,Spring 的事务拦截器(TransactionInterceptor)会检查当前线程是否已经关联了一个事务。 - 如果当前线程已经关联了一个事务,那么这个现有事务会被挂起。挂起操作通常涉及到保存事务的状态,以便于后续恢复。
- 被注解的方法会在没有事务的上下文中执行。所有的数据库操作将作为普通操作来处理,不会有事务管理的特性,如原子性或隔离级别。
- 方法执行完成后,不会有任何事务提交或回滚的操作,因为方法是在非事务性上下文中执行的。
- 如果之前有被挂起的事务,那么这个挂起的事务会在方法执行完毕后被恢复,并继续执行。
下面是一个使用 NOT_SUPPORTED
传播行为的示例:
@Service
public class MyService {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void performNonTransactionalOperation() {
// 执行业务逻辑,该逻辑会在没有事务的上下文中运行
}
}
在上面的例子中,performNonTransactionalOperation
方法将始终在非事务性上下文中执行,不依赖于外部的事务环境。
底层支持
NOT_SUPPORTED
传播行为与其他传播行为一样,不限于特定类型的事务,它可以应用于 JDBC、JPA、Hibernate 或其他资源的事务。Spring 的事务抽象层确保了不同事务管理器的一致性和可互操作性。
注意事项
使用 NOT_SUPPORTED
传播行为时,需要注意以下几点:
NOT_SUPPORTED
传播行为适用于那些不需要事务管理特性的操作。- 如果方法内部有需要事务性的操作,那么使用
NOT_SUPPORTED
是不合适的,因为它会导致这些操作在非事务性上下文中执行,从而可能导致数据不一致。 - 开发者需要确保
NOT_SUPPORTED
传播行为用于正确的场景,特别是当需要避免事务性开销时。
总的来说,NOT_SUPPORTED
传播行为提供了一种机制,允许方法在没有事务的上下文中执行,这对于需要避免事务性开销的场景非常有用。
6. NEVER (5):
- 总是以非事务方式执行,如果当前存在事务,则抛出异常。
- 这种传播行为要求方法必须在没有事务的上下文中运行,否则将会失败。
NEVER
是 Spring 框架中 @Transactional
注解的一种传播行为。当一个方法使用 Propagation.NEVER
时,Spring 的事务管理行为如下:
- 如果当前没有事务存在,方法会以非事务方式执行。
- 如果当前存在事务,会抛出异常,具体来说是
IllegalTransactionStateException
。
这种传播行为适用于那些绝对不能在事务环境中运行的操作。例如,当你想确保某些操作不会被任何现有事务影响时,可以使用 NEVER
。
代码实现原理
在 Spring 中,事务管理是通过 AOP(面向切面编程)机制实现的。对于 NEVER
传播行为,以下是其简化的实现逻辑:
- 当一个被
@Transactional(propagation = Propagation.NEVER)
注解的方法被调用时,Spring 的事务拦截器(TransactionInterceptor)会检查当前线程是否已经关联了一个事务。 - 如果当前线程没有关联事务,方法会以非事务方式执行。这意味着方法内的数据库操作不会受到任何事务管理的约束。
- 如果当前线程已经关联了一个事务,Spring 的事务拦截器会抛出
IllegalTransactionStateException
或类似的异常,表明一个事务已经存在,但当前的传播行为要求绝对不能在事务中执行。 - 由于
NEVER
行为不会创建或管理事务,所以不涉及事务的提交或回滚。
下面是一个使用 NEVER
传播行为的示例:
@Service
public class MyService {
@Transactional(propagation = Propagation.NEVER)
public void performNonTransactionalOperation() {
// 执行业务逻辑,该逻辑绝对不会在事务上下文中运行
}
}
在上面的例子中,如果 performNonTransactionalOperation
方法在事务的上下文中被调用,它会抛出异常,因为它期望在非事务环境中执行。
底层支持
NEVER
传播行为与其他传播行为一样,不限于特定类型的事务,它可以应用于 JDBC、JPA、Hibernate 或其他资源的事务。Spring 的事务抽象层确保了不同事务管理器的一致性和可互操作性。
注意事项
使用 NEVER
传播行为时,需要注意以下几点:
NEVER
传播行为非常严格,它确保方法不会在事务中执行,如果存在事务就会抛出异常。- 这种传播行为适用于那些不应在事务环境中执行的操作,例如,可能会干扰现有事务的批量作业或长时间运行的任务。
- 开发者需要确保
NEVER
传播行为用于正确的场景,并且调用者没有启动事务。
总的来说,NEVER
传播行为提供了一种机制,确保方法在没有事务的上下文中执行,这对于需要明确避免事务性开销的场景非常有用。
7. NESTED (6):
- 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则其行为与
REQUIRED
相同,即创建一个新的事务。 - 嵌套事务是一个子事务,它依赖于父事务。子事务可以单独提交或回滚,而不影响父事务。
- 这需要底层数据库支持保存点(savepoints)机制。
NESTED
是 Spring 框架中 @Transactional
注解的一种传播行为。当一个方法使用 Propagation.NESTED
时,Spring 的事务管理行为如下:
- 如果当前存在事务,它会启动一个嵌套事务,这是一个子事务,它依赖于父事务,但���以单独进行提交或回滚。
- 如果当前没有事务,则
NESTED
的行为类似于REQUIRED
,它会启动一个新的标准事务。这种传播行为适用于那些需要执行一系列独立的操作,这些操作应该作为主事务的一部分,但又能够独立回滚而不影响整个事务。
代码实现原理
在 Spring 中,事务管理通过 AOP(面向切面编程)机制实现。对于 NESTED
传播行为,以下是其简化的实现逻辑:
- 当一个被
@Transactional(propagation = Propagation.NESTED)
注解的方法被调用时,Spring 的事务拦截器(TransactionInterceptor)会检查当前线程是否已经关联了一个事务。 - 如果当前线程已经关联了一个事务,Spring 的事务拦截器会通过事务管理器(PlatformTransactionManager)创建一个嵌套事务。嵌套事务通常实现为一个带有保存点(savepoint)的事务。保存点允许事务回滚到特定的状态而不影响整个事务。
- 如果当前线程没有关联事务,将会启动一个新的标准事务。
- 被注解的方法会在这个嵌套事务或新事务的上下文中执行。所有的数据库操作将作为嵌套事务的一部分来处理。
- 方法执行完成后,如果是嵌套事务,它可以独立地提交或回滚到保存点。如果是新事务,它将被正常提交或回滚。
- 如果有异常发生,嵌套事务可以选择回滚到保存点,而不影响父事务的其余部分。
下面是一个使用 NESTED
传播行为的示例:
@Service
public class MyService {
@Transactional(propagation = Propagation.NESTED)
public void performNestedOperation() {
// 执行业务逻辑,该逻辑会在嵌套事务的上下文中运行
}
}
在上面的例子中,performNestedOperation
方法将在一个嵌套事务中执行,如果当前存在一个事务的话。否则,它将在一个新的事务中执行。
底层支持
NESTED
传播行为的实现依赖于底层数据库和 JDBC 驱动的支持。不是所有的数据库管理系统都支持嵌套事务。在不支持嵌套事务的数据库上,NESTED
可能会被模拟为独立事务,或者可能无法使用。
注意事项
使用 NESTED
传播行为时,需要注意以下几点:
NESTED
可以提供更细粒度的事务控制,但也增加了复杂性。开发者需要明确理解其工作原理和适用场景。- 嵌套事务的使用需要数据库支持保存点功能。在不支持保存点的数据库上,可能需要选择其他传播行为。
NESTED
适用于那些需要部分操作能够独立回滚,而不影响整个事务的场景。
总的来说,NESTED
传播行为提供了一种机制,允许在父事务内部创建子事务,这些子事务可以独立提交或回滚,从而提供了更多的灵活性和控制。
相同与不同:
REQUIRED
, SUPPORTS
, MANDATORY
, REQUIRES_NEW
, NOT_SUPPORTED
, NEVER
, 和 NESTED
是 Spring 框架中 @Transactional
注解支持的事务传播行为。这些行为决定了一个事务性方法是如何与周围的事务环境交互的。
相同点
- 事务性上下文管理:所有这些传播行为都是用来定义方法如何在事务性上下文中执行。它们是 Spring 的声明式事务管理策略的一部分,通过 AOP 实现,允许统一的事务管理而不需要修改业务代码。
- 与 @Transactional
注解配合使用:这些传播行为都是 @Transactional
注解的属性,用于指定期望的事务行为。
- 底层资源透明:这些传播行为抽象了底层资源(如数据库)的事务管理,使得应用程序能够以统一的方式处理不同的事务管理器。
- 异常处理:它们都涉及到对方法执行期间发生的异常的处理。如果在事务性方法中抛出了未捕获的异常,事务通常会被标记为回滚。
不同点
- 事务边界的影响:
- REQUIRED
:如果当前存在事务,则加入该事务;如果没有,则新建事务。
- SUPPORTS
:如果当前存在事务,则加入该事务;如果没有,则以非事务方式执行。
- MANDATORY
:如果当前存在事务,则加入该事务;如果没有,则抛出异常。
- REQUIRES_NEW
:总是新建一个事务,如果当前存在事务,则挂起当前事务。
- NOT_SUPPORTED
:总是以非事务方式执行,如果当前存在事务,则挂起当前事务。
- NEVER
:总是以非事务方式执行,如果当前存在事务,则抛出异常。
- NESTED
:如果当前存在事务,则创建一个嵌套事务;如果没有,则新建事务。
- 对现有事务的影响:
- REQUIRED
, SUPPORTS
, 和 MANDATORY
都是参与现有事务的行为(如果有的话)。
- REQUIRES_NEW
和 NOT_SUPPORTED
会挂起现有事务并创建一个新的事务上下文或以非事务方式执行。
- NEVER
确保当前没有活跃的事务,否则会抛出异常。
- NESTED
允许在现有事务中创建一个子事务,这个子事务可以独立于父事务回滚。
- 新事务的创建:
- REQUIRED
, SUPPORTS
, MANDATORY
, 和 NEVER
不会在当前没有事务的情况下主动创建新事务(SUPPORTS
和 NEVER
从不创建,MANDATORY
抛出异常)。
- REQUIRES_NEW
和 NESTED
会创建一个新事务,无论当前是否存在事务。
- NOT_SUPPORTED
总是执行非事务操作,即使没有现有事务也不会创建新事务。
- 适用场景:
- REQUIRED
是最常用的,适用于大多数事务操作。
- SUPPORTS
适用于可选事务操作,特别是只读操作。
- MANDATORY
适用于必须在事务环境中运行的操作。
- REQUIRES_NEW
适用于需要独立事务环境的操作。
- NOT_SUPPORTED
适用于不应在事务环境中运行的操作。
- NEVER
适用于绝对不能在事务环境中运行的操作。
- NESTED
适用于需要执行一系列操作,这些操作作为主事务的一部分但又能够独立回滚。
在选择合适的传播行为时,开发者需要根据具体的业务需求和事务管理策略来决定。正确的传播行为选择对于确保数据一致性、避免不必要的性能开销和满足事务隔离要求至关重要。
图表描述:
- A
代表所有事务传播行为的起点。
- B
, C
, D
, E
, 和 F
代表不同的事务行为决策点。
- REQUIRED
, SUPPORTS
, MANDATORY
, REQUIRES_NEW
, NOT_SUPPORTED
, NEVER
, 和 NESTED
是具体的事务传播行为。
相同点
- 所有行为都是为了管理事务性上下文。
- 它们都用于 @Transactional
注解。
- 它们都提供了对异常的处理。
不同点
- REQUIRED
, SUPPORTS
, MANDATORY
, 和 NESTED
可以参与到当前事务中。
- REQUIRES_NEW
和 NESTED
可以新建事务。
- SUPPORTS
, NOT_SUPPORTED
, 和 NEVER
可以在非事务上下文中执行。
- REQUIRES_NEW
和 NOT_SUPPORTED
会挂起当前事务。
- MANDATORY
和 NEVER
在特定条件下会抛出异常。
使用场景描述
- REQUIRED
:适用于大多数标准的事务操作,如业务逻辑处理,如果当前有事务则加入,否则新建一个事务。
- SUPPORTS
:适用于可选的事务操作,如只读查询,如果当前有事务则加入,否则也可以不在事务中执行。
- MANDATORY
:适用于必须在事务中执行的操作,如果当前没有事务则抛出异常,确保操作的事务性。
- REQUIRES_NEW
:适用于需要独立于当前事务执行的操作,如日志记录或者需要独立提交的操作,无论当前事务状态如何,都会新建一个事务。
- NOT_SUPPORTED
:适用于不应该在事务中执行的长时间运行的操作,如大量数据的批处理作业,会挂起当前事务并以非事务方式执行。
- NEVER
:适用于绝对不能在事务中执行的操作,如某些配置读取,如果当前有事务则抛出异常。
- NESTED
:适用于需要独立提交或回滚的复杂业务逻辑,如果当前有事务,则在嵌套事务中执行,否则新建事务。
面试题:
- Q:1.使用NESTED和REQUIRES_NEW 新建事务 返回回滚 是否都会影响主事务:
答案:
使用 NESTED
和 REQUIRES_NEW
传播行为时,新建事务的回滚行为对主事务的影响是不同的。
REQUIRES_NEW
当使用 REQUIRES_NEW
传播行为时,方法会始终运行在一个新的事务中,独立于任何现有的事务。如果这个新事务需要回滚,它只会回滚自己的操作,不会影响到主事务。一旦新事务完成(无论是提交还是回滚),如果之前有主事务存在,主事务会被恢复并继续执行。因此,REQUIRES_NEW
传播行为创建的事务与主事务相互独立,回滚不会相互影响。
NESTED
使用 NESTED
传播行为时,如果当前存在一个事务,将在这个事务中创建一个嵌套事务。这个嵌套事务是依赖于主事务的,但它可以独立地回滚到一个保存点(savepoint)。如果嵌套事务回滚,它只会回滚到最近的保存点,而不会影响到主事务的其他部分。然而,如果主事务回滚,那么包括嵌套事务在内的所有操作都会被回滚,因为它们是在同一个物理事务中的。所以,嵌套事务的回滚不会影响主事务的其他操作,但主事务的回滚会影响嵌套事务。
总结来说:
- REQUIRES_NEW
:新建的事务与主事务完全独立,回滚互不影响。
- NESTED
:嵌套事务可以独立回滚到保存点,不影响主事务,但主事务的回滚会影响嵌套事务。
-Q:2.七种事务方法调用与事务回滚
假设你有两个服务类ServiceA
和 ServiceB
。ServiceA
有一个方法 methodA()
,ServiceB
有一个方法 methodB()
。现在你要在 methodA()
中调用 methodB()
,但是根据不同的业务场景,methodB()
可能会有不同的事务传播行为配置。请描述在以下不同 methodB()
的事务传播行为配置下,如果 methodB()
抛出一个运行时异常,methodA()
和 methodB()
的事务会如何表现:
- 1.
REQUIRED
2.SUPPORTS
3.MANDATORY
4.REQUIRES_NEW
5.NOT_SUPPORTED
6.NEVER
7.NESTED
答案:
1. REQUIRED
:如果 methodA()
正在一个事务中运行,methodB()
会加入这个事务。如果 methodB()
抛出异常,整个事务会被标记为回滚,所以 methodA()
和 methodB()
的操作都会回滚。如果 methodA()
没有在事务中运行,那么 methodB()
会启动一个新的事务,如果异常发生,只有 methodB()
的操作会回滚。
2. SUPPORTS
:如果 methodA()
正在一个事务中运行,methodB()
会加入这个事务。如果 methodB()
抛出异常,整个事务会被标记为回滚。如果 methodA()
没有在事务中运行,methodB()
也会以非事务方式执行,异常不会导致事务回滚,因为没有事务存在。
3. MANDATORY
:methodB()
必须在一个现有的事务中运行。如果 methodA()
没有在事务中运行,调用 methodB()
时会抛出异常。如果 methodA()
正在一个事务中运行,methodB()
加入这个事务,并且如果 methodB()
抛出异常,整个事务会回滚。
4. REQUIRES_NEW
:methodB()
总是会启动一个新的事务,如果 methodA()
正在一个事务中运行,这个事务会被挂起。如果 methodB()
抛出异常,只有 methodB()
的事务会回滚,methodA()
的事务不会受到影响,除非 methodA()
也有异常抛出。
5. NOT_SUPPORTED
:methodB()
总是以非事务方式执行,如果 methodA()
正在一个事务中运行,这个事务会被挂起。methodB()
抛出异常不会影响到任何事务,因为它本身不在事务中运行。
6. NEVER
:methodB()
必须在没有事务的上下文中运行。如果 methodA()
正在一个事务中运行,调用 methodB()
会立即抛出异常。如果 methodA()
没有在事务中运行,methodB()
会正常执行,但如果抛出异常,不会有事务回滚,因为它不在事务中。
7. NESTED
:如果 methodA()
正在一个事务中运行,methodB()
会在一个嵌套事务中运行。如果 methodB()
抛出异常,可以选择只回滚 methodB()
的嵌套事务,而不影响 methodA()
的事务。如果 methodA()
没有在事务中运行,methodB()
会启动一个新的事务,如果异常发生,只有 methodB()
的操作会回滚。
Q:3 订单下发与付款直接方法之间详细事务传播的面试论述
面试题:假设你正在维护一个电子商务应用程序,并且你需要处理一个订单提交的业务逻辑。你有两个服务类 OrderService
和 PaymentService
。OrderService
有一个 submitOrder()
方法,用于提交订单。PaymentService
有一个 processPayment()
方法,用于处理付款。
现在,你需要在 submitOrder()
方法中调用 processPayment()
方法。但是,你发现 processPayment()
方法的事务传播行为被错误地配置了,这可能会导致数据一致性问题或不必要的事务开销。以下是 processPayment()
方法的一些错误配置,请为每种情况提供可能出现的问题,并解释为什么这些配置是错误的。
1. processPayment()
配置为 Propagation.NEVER
2. processPayment()
配置为 Propagation.NOT_SUPPORTED
3. processPayment()
配置为 Propagation.REQUIRES_NEW
,但 submitOrder()
也需要在同一个事务中更新库存
答案:
1. processPayment()
配置为 Propagation.NEVER
:
问题:如果 submitOrder()
已经在一个事务中执行,调用 processPayment()
会导致异常,因为 NEVER
传播行为表明 processPayment()
不能在任何现有事务的上下文中运行。这会导致订单提交失败,即使订单数据已经被创建或更新。
解释:在电子商务应用程序中,订单提交和付款处理通常需要在同一个事务中完成,以保证数据的一致性。如果付款处理失败,整个订单应该被回滚,避免出现无法支付的订单。
2. processPayment()
配置为 Propagation.NOT_SUPPORTED
:
问题:这意味着 processPayment()
将不会在 submitOrder()
的事务上下文中执行。这可能导致付款处理在非事务环境中进行,如果 submitOrder()
失败并回滚,付款可能已经处理,这会导致数据不一致。
解释:付款处理是订单提交流程的关键部分,它应该与订单的创建或更新在同一个事务中被管理。使用 NOT_SUPPORTED
会使付款处理与订单创建的事务分离,增加了数据不一致的风险。
3. processPayment()
配置为 Propagation.REQUIRES_NEW
,但 submitOrder()
也需要在同一个事务中更新库存:
问题:REQUIRES_NEW
会启动一个新的事务,如果 submitOrder()
中更新库存的操作和 processPayment()
不在同一个事务中,可能会出现库存更新了但是付款未成功的情况,或者付款成功了但库存未正确更新的情况。
解释:订单处理和付款通常需要原子性,意味着它们要么全部成功,要么全部失败。REQUIRES_NEW
创建的新事务会导致付款处理与订单其他部分的事务分离,这违背了原子性的原则,可能会导致库存和财务数据的不一致。
在实际应用中,processPayment()
方法应该配置为 Propagation.REQUIRED
,这样它就可以加入 submitOrder()
方法的事务中,确保订单提交和付款处理的原子性。如果 submitOrder()
方法没有在事务中执行,REQUIRED
也会启动一个新的事务。这样,无论是订单创建、库存更新还是付款处理,都能在同一个事务中一致地管理。
- Q:4.REQUIRES_NEW 和NESTED新建的子事务或者新事务 是否可以获取到前者主事务已经插入或者修改的数据
答案:
在使用 REQUIRES_NEW
和 NESTED
事务传播行为时,子事务或新事务对前者主事务已经插入或修改的数据的可见性取决于数据库的隔离级别和事务的特定行为。
REQUIRES_NEW
当方法使用 REQUIRES_NEW
时,将会启动一个新的事务,该事务与任何现有的事务完全独立。这意味着新事务拥有自己的隔离级别和锁定策略。
- 如果主事务尚未提交,新事务通常看不到主事务所做的更改,因为大多数数据库的默认隔离级别会防止“脏读”(即读取未提交的更改)。 - 如果主事务已经提交了更改,新事务可以看到这些更改,因为它们已经被持久化到数据库中。
NESTED
当方法使用 NESTED
时,如果当前存在主事务,它将在主事务中创建一个嵌套事务。嵌套事务可以使用保存点来实现部分回滚而不影响整个事务。
- 在大多数情况下,嵌套事务可以看到并且可以操作主事务中的数据,因为它们实际上是在同一个物理事务内部进行操作,但是拥有一个回滚点。 - 嵌套事务的更改对主事务立即可见,因为它们在同一个事务上下文中。如果嵌套事务回滚到保存点,只有从保存点到回滚点之间的更改会被撤销。
需要注意的是,NESTED
事务传播行为在所有的数据库管理系统中都不是通用支持的。例如,MySQL不支持真正意义上的嵌套事务,而是通过保存点来模拟这种行为。
总结来说,REQUIRES_NEW
创建的新事务不能看到主事务未提交的更改(除非数据库隔离级别设置为允许脏读),而 NESTED
创建的嵌套事务可以看到并且操作主事务中的数据。这些行为可能会受到具体数据库和隔离级别设置的影响。
- Q:5.REQUIRED SUPPORTS MANDATORY 是否可以获取到获取到前者主事务已经插入或者修改的数据
答案:
在 REQUIRED
, SUPPORTS
, 和 MANDATORY
事务传播行为中,方法对主事务已经插入或修改的数据的可见性主要取决于事务的隔离级别。下面是每种传播行为的具体情况:
REQUIRED
- 行为:如果当前存在事务,REQUIRED
行为的方法会加入该事务。如果没有事务,它会启动一个新的事务。
- 数据可见性:由于方法是在现有事务的上下文中执行的,它可以看到主事务中已经进行的所有插入和修改操作,前提是这些操作没有被回滚。
SUPPORTS
- 行为:如果当前存在事务,SUPPORTS
行为的方法会加入该事务。如果没有事务,方法会以非事务方式执行。
- 数据可见性:当方法在事务中运行时,它可以看到主事务中的所有更改。如果方法在非事务环境中执行,它只能看到已经提交到数据库的更改。
MANDATORY
- 行为:MANDATORY
行为要求必须在现有事务的上下文中执行。如果调用时没有事务,会抛出异常。
- 数据可见性:因为 MANDATORY
行为的方法必须在现有事务中运行,所以它可以看到并且操作主事务中的所有数据,包括未提交的更改。
在这三种情况中,只要方法是在事务的上下文中执行的,它们都能看到主事务中未提交的更改。这是因为它们要么直接加入了主事务 (REQUIRED
和 MANDATORY
),要么在事务存在时加入了事务 (SUPPORTS
)。然而,这些未提交的更改是否对其他并发事务可见,取决于数据库的隔离级别。例如,在 READ COMMITTED
隔离级别下,只有当更改被提交后,它们才对其他事务可见;而在 READ UNCOMMITTED
隔离级别下,即使更改未提交,其他事务也可能看到它们(脏读)。
Q:6.方法上面 备注的是 @Transactional(propagation = Propagation.NOT_SUPPORTED) 但是 在xml 事务配置tx:annotation-driven /是REQUIRES_NEW,请问 该方法到底 启用新事务还是不开启新事务
答案:
当方法上使用了@Transactional(propagation = Propagation.NOT_SUPPORTED)
注解,而在Spring配置文件中通过<tx:annotation-driven />
启用了事务注解的支持时,注解中定义的传播行为将具有优先权。这意味着@Transactional(propagation = Propagation.NOT_SUPPORTED)
注解指定了该方法的事务传播行为为NOT_SUPPORTED
,这将被应用于该方法。因此,即使在XML配置中默认的事务传播行为被设置为REQUIRES_NEW
,方法上显式声明的NOT_SUPPORTED
仍将生效。
总结论述:
通过本文的深入分析和对典型面试题的详细解答,我们不仅理解了Spring的@Transactional
注解中七种不同的事务传播行为,还学会了如何根据不同的业务需求来选择合适的传播策略。明白了在实际应用中,正确的事务管理策略对于保障数据的一致性和系统的稳定性至关重要。每种传播行为都有其适用的场景,没有绝对的好坏之分,关键于如何根据具体情况做出恰当的选择。希望本文能够为读者在面对事务管理的复杂性时提供清晰的指导,不仅在面试中脱颖而出,更在日常开发中写出更加健壮、可靠的代码。记住,掌握了Spring事务的精髓,就已经在成为一名出色的Spring开发者的路上又迈进了一大步。
转载自:https://juejin.cn/post/7386497602194145321