在Spring框架中,事务管理是一个核心功能,它允许你声明性地管理事务边界和事务属性,包括传播行为、隔离级别、回滚规则等。事务的传播机制(Propagation Behavior)定义了事务方法被另一个事务方法调用时,事务应该如何进行。Spring 提供了七种标准的事务传播行为,这些行为控制了一个事务方法如何与另一个事务方法相互作用。
Spring的七种事务传播机制:
1 PROPAGATION_REQUIRED(默认): 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
-
当父方法A执行时,如果它带有一个事务,并且它调用了子方法B,而子方法B的事务传播机制被设置为
PROPAGATION_REQUIRED
,那么子方法B会加入到父方法A的事务中。这意味着,子方法B的数据库操作将与父方法A的数据库操作在同一个事务边界内执行。 -
如果在子方法B执行期间发生异常,并且该异常导致事务需要回滚,那么不仅子方法B所做的更改会被回滚,父方法A中到目前为止所做的所有更改也会被回滚,因为它们共享同一个事务。
-
如果在调用子方法B之前,父方法A的事务已经存在,并且子方法B也因为没有自己的事务而加入到这个事务中,那么就不存在“新建事务”的情况。只有当父方法A执行时没有事务,并且子方法B被调用时,Spring才会为它们共同创建一个新的事务。
-
如果子方法B在没有事务的上下文中被单独调用(即不是作为父方法A的一部分),并且它的传播机制是
PROPAGATION_REQUIRED
,那么Spring将会为这个调用创建一个新的事务。
因此,PROPAGATION_REQUIRED
的核心在于确保方法执行时总是有事务存在,要么加入到现有的事务中,要么在没有事务的情况下创建一个新的事务。
2 PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
-
如果当前存在事务:那么当前方法(即标有
PROPAGATION_SUPPORTS
的方法)将会加入(或称为“参与”)到已经存在的事务中。这意味着,该方法的执行将会在同一个事务的上下文中进行,与调用它的方法共享同一个事务。 -
如果当前没有事务:那么当前方法将会以非事务的方式执行。这意味着,它不会启动一个新的事务,也不会受到任何事务管理的影响。它的执行就像是在没有事务管理的环境下进行的。
这种传播机制非常有用,当你不确定调用你的方法时是否会有事务存在,但你希望在有事务存在时能够利用它,而在没有事务存在时则不强制要求事务时。它提供了一种灵活的方式来处理事务,允许方法在不同的事务环境中以不同的方式运行。
然而,需要注意的是,当方法以非事务方式执行时,它所做的任何数据库操作都不会受到事务的保护,即如果系统发生故障,这些操作可能会部分完成或完全不完成,从而导致数据不一致的问题。因此,在使用PROPAGATION_SUPPORTS
时,你需要仔细考虑这一点,并确保你的应用逻辑能够处理这种情况。
另外,由于PROPAGATION_SUPPORTS
允许方法在没有事务时以非事务方式执行,因此它不会触发任何事务的创建或管理开销,这在性能敏感的应用中可能是一个优势。但是,这也意味着你需要自己处理可能的事务一致性问题。
3 PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
-
如果当前存在事务:那么当前方法(即标有
PROPAGATION_MANDATORY
的方法)将会加入(或称为“参与”)到已经存在的事务中。这意味着,该方法的执行将会在同一个事务的上下文中进行,与调用它的方法共享同一个事务。 -
如果当前没有事务:那么当前方法将会抛出一个异常,通常是一个
IllegalTransactionStateException
。这个异常表明,该方法需要在事务的上下文中执行,但当前并没有事务存在。
PROPAGATION_MANDATORY
传播机制用于确保一个方法总是在事务的上下文中被调用。它通常用于那些必须在事务中运行,并且不能单独执行的业务逻辑。通过使用PROPAGATION_MANDATORY
,你可以强制要求调用者必须在一个事务中调用你的方法,从而避免了数据不一致的风险。
然而,需要注意的是,由于PROPAGATION_MANDATORY
要求必须存在事务,因此在使用时需要确保调用者已经开启了事务。如果调用者没有开启事务,那么你的方法将会因为找不到事务而抛出异常。这可能会导致意外的错误和难以调试的问题,因此在使用时需要特别小心。
此外,PROPAGATION_MANDATORY
与PROPAGATION_REQUIRED
在某些方面相似,因为它们都要求在事务中执行。但是,PROPAGATION_REQUIRED
在没有事务时会创建一个新的事务,而PROPAGATION_MANDATORY
则会抛出异常。这种差异使得PROPAGATION_MANDATORY
更加严格,适用于那些绝对需要事务环境的场景。
4 PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
当事务的传播行为被设置为PROPAGATION_REQUIRES_NEW
时,Spring会采取以下行动:
-
如果当前存在事务:Spring会为当前方法启动一个新的事务,并将当前存在的事务挂起(即暂停执行)。这意味着,新事务的开始和结束不会受到原事务的影响,它们是两个完全独立的事务。当
PROPAGATION_REQUIRES_NEW
方法执行完毕后,挂起的原事务将恢复执行。 -
如果当前没有事务:Spring仍然会为当前方法启动一个新的事务,并在这个新事务中执行方法。
PROPAGATION_REQUIRES_NEW
的用途非常广泛,特别是在需要确保数据的一致性和隔离性的场景中。例如,你可能有一个服务层方法,它执行了一些关键的业务操作,这些操作必须在一个全新的事务中执行,以确保它们不会被其他并发事务干扰。通过使用PROPAGATION_REQUIRES_NEW
,你可以确保这些操作总是在一个干净的事务环境中执行,无论外部是否有其他事务存在。
此外,PROPAGATION_REQUIRES_NEW
还可以用于解决循环依赖和事务嵌套导致的问题。在某些复杂的应用中,可能会出现方法A调用方法B,而方法B又间接调用方法A的情况。如果这些方法都使用相同的事务,就可能导致死锁或事务管理上的混乱。通过使用PROPAGATION_REQUIRES_NEW
,你可以确保每次调用都是在一个全新的事务中进行的,从而避免了这些问题。
5 PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则把当前事务挂起。
-
如果当前存在事务:Spring 会将当前存在的事务挂起(即暂停执行),然后在一个没有事务的上下文中执行当前方法。这意味着,即使调用者是在事务中运行的,被调用的方法也不会参与到该事务中。
-
如果当前没有事务:那么当前方法将直接以非事务的方式执行,这是显而易见的,因为没有事务需要挂起。
使用 PROPAGATION_NOT_SUPPORTED
的场景通常包括那些不需要事务支持的操作,或者与事务管理无关的操作。例如,你可能有一个读取数据的操作,它不会修改数据库中的任何数据,因此不需要事务来保证数据的一致性。在这种情况下,使用 PROPAGATION_NOT_SUPPORTED
可以避免不必要的事务开销,并可能提高性能。
然而,需要注意的是,由于 PROPAGATION_NOT_SUPPORTED
会挂起当前事务(如果存在),因此它可能会影响到方法的调用者和被调用者之间的事务性。如果调用者依赖于事务的某些特性(如回滚),并且被调用的方法使用了 PROPAGATION_NOT_SUPPORTED
,那么调用者可能会遇到意外的行为。
因此,在使用 PROPAGATION_NOT_SUPPORTED
时,需要仔细考虑其对事务边界和事务一致性的影响,并确保它符合你的业务逻辑和事务管理策略。
最后,值得注意的是,PROPAGATION_NOT_SUPPORTED
与 PROPAGATION_NEVER
在某些方面相似,但它们的区别在于对当前事务的处理方式。PROPAGATION_NEVER
会检查当前是否存在事务,如果存在,则抛出异常;而 PROPAGATION_NOT_SUPPORTED
则会挂起当前事务并继续执行。因此,在选择使用哪个传播行为时,需要根据具体的业务需求和事务管理策略来决定。
6 PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
当事务的传播行为被设置为 PROPAGATION_NEVER
时,Spring 会检查当前是否存在事务:
-
如果当前不存在事务:那么当前方法将直接以非事务的方式执行。
-
如果当前存在事务:那么 Spring 会抛出一个
IllegalTransactionStateException
异常,表明该方法不应在事务的上下文中执行。
使用 PROPAGATION_NEVER
的场景通常包括那些绝对不应该在事务中运行的操作。这些操作可能是非事务性的,或者它们与当前事务的上下文不兼容,可能会导致数据不一致或其他问题。
例如,你可能有一个方法用于执行一些日志记录或审计操作,这些操作不需要事务支持,并且如果它们在事务中执行,可能会因为事务的回滚而丢失重要的日志信息。在这种情况下,你可以将该方法的事务传播行为设置为 PROPAGATION_NEVER
,以确保它不会在事务的上下文中执行。
然而,需要注意的是,由于 PROPAGATION_NEVER
会检查当前是否存在事务,并在存在事务时抛出异常,因此它可能会对你的应用程序的健壮性和错误处理策略产生影响。如果你的应用程序中的某些方法可能会在不期望的事务上下文中被调用,并且这些方法使用了 PROPAGATION_NEVER
,那么你需要确保你的应用程序能够妥善处理由此产生的异常,并为用户提供清晰的错误消息。
最后,与 PROPAGATION_NOT_SUPPORTED
不同,PROPAGATION_NEVER
不会在存在事务时挂起事务,而是直接抛出异常。这种差异使得 PROPAGATION_NEVER
更加严格,适用于那些绝对不能在事务中执行的操作。
7 PROPAGATION_NESTED(如果底层数据源支持):如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则行为等同于 PROPAGATION_REQUIRED
。
PROPAGATION_NESTED
是 Spring 框架中事务传播行为的一种,它提供了一种特殊的事务嵌套能力。然而,需要注意的是,PROPAGATION_NESTED
的行为和支持程度取决于底层的事务管理器(特别是底层的数据源是否支持保存点)。
当事务的传播行为被设置为 PROPAGATION_NESTED
时,Spring 会采取以下行动:
-
如果当前存在事务:Spring 会为当前方法启动一个嵌套事务。嵌套事务是外部事务的一个子事务,它有自己的隔离范围,可以独立于外部事务进行提交或回滚。但是,嵌套事务的提交并不立即提交外部事务,而是等待外部事务的提交。如果外部事务回滚,那么嵌套事务也会被回滚。嵌套事务的主要优点是它可以在需要时提供额外的回滚保护点,而不会影响外部事务的完整性。
然而,嵌套事务的实现依赖于底层数据库是否支持保存点(savepoints)。如果数据库不支持保存点,那么事务管理器可能会以不同的方式模拟嵌套事务的行为,或者简单地回退到
PROPAGATION_REQUIRED
的行为。 -
如果当前没有事务:那么
PROPAGATION_NESTED
的行为就等同于PROPAGATION_REQUIRED
。即,Spring 会为当前方法启动一个新的事务,并在这个新事务中执行方法。
使用 PROPAGATION_NESTED
的场景通常包括那些需要在方法内部进行额外的错误处理或回滚控制的情况。例如,你可能有一个方法需要在执行一系列操作时,在某个关键点提供额外的回滚保护。通过在这些关键点上启动嵌套事务,你可以确保即使内部操作失败,也不会影响外部事务的完整性,同时提供了在必要时回滚内部操作的能力。
然而,需要注意的是,嵌套事务可能会增加事务管理的复杂性,并且不是所有的数据库和事务管理器都支持嵌套事务。因此,在决定使用 PROPAGATION_NESTED
之前,你应该仔细评估你的需求,并了解你的底层数据源和事务管理器的限制。
最后,需要指出的是,虽然 PROPAGATION_NESTED
提供了一种强大的事务嵌套能力,但在许多情况下,通过仔细设计事务边界和错误处理逻辑,可能并不需要使用嵌套事务。因此,在决定使用 PROPAGATION_NESTED
之前,你应该考虑是否有更简单、更直接的方法来实现你的需求。
父方法回滚对子方法事务的影响:
-
当使用 PROPAGATION_REQUIRED、PROPAGATION_SUPPORTS、PROPAGATION_MANDATORY 时,子方法会使用父方法的事务。如果父方法回滚,子方法中所做的更改也会被回滚,因为它们共享同一个事务。
-
当使用 PROPAGATION_REQUIRES_NEW 时,子方法会创建一个新的事务,与父方法的事务完全独立。因此,父方法的回滚不会影响子方法的事务。
-
使用 PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER 时,子方法不会使用事务,因此父方法的回滚对子方法没有影响(但子方法也无法享受事务的保护)。
-
PROPAGATION_NESTED 的行为取决于底层事务管理器是否支持嵌套事务。如果支持,则子事务是父事务的一个嵌套部分,父事务的回滚将回滚子事务的更改,但具体行为可能因事务管理器而异。
理解和正确使用这些传播机制对于开发可靠的事务性应用程序至关重要。