spring框架学习 - Data Access之 事务管理 - 声明式事务管理

接上一篇博客:https://blog.csdn.net/qq_43605444/article/details/122085016?spm=1001.2014.3001.5502

4、声明式事务管理

大多数 Spring 框架用户选择声明式事务管理。 此选项对应用程序代码的影响最小,因此最符合非侵入式轻量级容器的理想。

Spring 框架的声明式事务管理通过 Spring 面向方面的编程 (AOP) 成为可能。 然而,由于事务方面代码随 Spring Framework 发行版一起提供,并且可以以样板方式使用,因此通常不必理解 AOP 概念即可有效地使用此代码。

Spring 框架的声明式事务管理类似于 EJB CMT,因为您可以将事务行为(或缺少它)指定到单个方法级别。 如果需要,您可以在事务上下文中调用 setRollbackOnly()。 两种事务管理的区别是:

  • 与绑定到 JTA 的 EJB CMT 不同,Spring 框架的声明式事务管理可在任何环境中工作。 它可以通过调整配置文件使用 JDBC、JPA 或 Hibernate 来处理 JTA 事务或本地事务。
  • 您可以将 Spring Framework 声明式事务管理应用于任何类,而不仅仅是诸如 EJB 之类的特殊类。
  • Spring Framework 提供了声明式回滚规则,这是一项没有 EJB 等效项的特性。 提供了对回滚规则的编程和声明性支持。
  • Spring 框架允许您通过使用 AOP 来自定义事务行为。 例如,您可以在事务回滚的情况下插入自定义行为。 您还可以添加任意通知以及事务通知。 使用 EJB CMT,您不能影响容器的事务管理,除非使用 setRollbackOnly()
  • Spring 框架不支持跨远程调用传播事务上下文,高端应用程序服务器则支持。 如果您需要此功能,我们建议您使用 EJB。 但是,在使用此类功能之前请仔细考虑,因为通常情况下,人们不希望事务跨越远程调用。

回滚规则的概念很重要。 它们让您指定哪些异常(和可抛出的)应该导致自动回滚。 您可以在配置中(而不是在 Java 代码中)以声明方式指定此项。 因此,尽管您仍然可以在 TransactionStatus 对象上调用 setRollbackOnly()来回滚当前事务,但大多数情况下您可以指定 MyApplicationException 必须始终导致回滚的规则。 此选项的显着优点是业务对象不依赖于事务基础结构。 例如,他们通常不需要导入 Spring 事务 API 或其他 Spring API。

尽管 EJB 容器默认行为会在系统异常(通常是运行时异常)时自动回滚事务,但 EJB CMT 不会在应用程序异常(即 java.rmi.RemoteException 以外的已检查异常)时自动回滚事务。 虽然声明性事务管理的 Spring 默认行为遵循 EJB 约定(仅在未检查异常时自动回滚),但自定义此行为通常很有用。

4.1 理解 Spring 框架的声明式事务实现

仅仅告诉您使用 @Transactional 注解来注解您的类,将 @EnableTransactionManagement 添加到您的配置中,并期望您了解它是如何工作的,这是不够的。 为了提供更深入的理解,本节在事务相关问题的上下文中解释了 Spring 框架的声明式事务基础结构的内部工作原理。

关于 Spring Framework 的声明式事务支持,需要掌握的最重要的概念是这种支持是通过 AOP 代理启用的,并且事务通知是由元数据(目前基于 XML 或注解)驱动的。 AOP 与事务元数据的组合产生了一个 AOP 代理,该代理使用 TransactionInterceptor 结合适当的 TransactionManager 实现来驱动围绕方法调用的事务。

Spring Framework 的 TransactionInterceptor 为命令式和响应式编程模型提供了事务管理。 拦截器通过检查方法返回类型来检测所需的事务管理风格。 返回响应式类型的方法,例如 Publisher 或 Kotlin Flow(或它们的子类型)有资格进行响应式事务管理。 所有其他返回类型包括 void 使用代码路径进行命令式事务管理。

事务管理风格影响需要哪个事务管理器。 命令式事务需要 PlatformTransactionManager,而响应式事务使用 ReactiveTransactionManager实现。

@Transactional 通常与由 PlatformTransactionManager 管理的线程绑定事务一起使用,将事务暴露给当前执行线程内的所有数据访问操作。 注意:这不会传播到方法内新启动的线程。

ReactiveTransactionManager 管理的响应式事务使用 Reactor 上下文而不是线程本地属性。 因此,所有参与的数据访问操作都需要在同一个反应管道中的同一个 Reactor 上下文中执行。

下图显示了在事务代理上调用方法的概念视图:
spring框架学习 - Data Access之 事务管理 - 声明式事务管理_第1张图片

4.2 声明式事务实现示例

考虑以下接口及其伴随的实现。 此示例使用 Foo 和 Bar 类作为占位符,以便您可以专注于事务使用,而无需关注特定的域模型。 就本示例而言,DefaultFooService 类在每个实现的方法的主体中抛出 UnsupportedOperationException 实例这一事实是好的。 该行为让您可以看到正在创建的事务,然后回滚以响应 UnsupportedOperationException 实例。 以下清单显示了 FooService 接口:

// the service interface that we want to make transactional

package x.y.service;

public interface FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);

}

以下示例显示了上述接口的实现:

package x.y.service;

public class DefaultFooService implements FooService {

    @Override
    public Foo getFoo(String fooName) {
        // ...
    }

    @Override
    public Foo getFoo(String fooName, String barName) {
        // ...
    }

    @Override
    public void insertFoo(Foo foo) {
        // ...
    }

    @Override
    public void updateFoo(Foo foo) {
        // ...
    }
}

假设 FooService 接口的前两个方法 getFoo(String) 和 getFoo(String, String) 必须在具有只读语义的事务上下文中运行,而其他方法 insertFoo(Foo) 和 updateFoo(Foo ),必须在具有读写语义的事务上下文中运行。 下面几段详细解释下面的配置:



<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    
    <tx:advice id="txAdvice" transaction-manager="txManager">
        
        <tx:attributes>
            
            <tx:method name="get*" read-only="true"/>
            
            <tx:method name="*"/>
        tx:attributes>
    tx:advice>

    
    <aop:config>
        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
    aop:config>

    
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    bean>

    
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    bean>

    

beans>

检查上述配置。 它假定您想要创建一个服务对象,即 fooService bean,事务性的。 要应用的事务语义封装在 定义中。 定义读作“所有以 get 开头的方法都将在只读事务的上下文中运行,所有其他方法将使用默认事务语义运行”。 标记的事务管理器属性设置为将驱动事务的 TransactionManager bean 的名称(在本例中为 txManager bean)。

如果要连接的 TransactionManager 的 bean 名称具有名称 transactionManager,则可以省略事务通知 () 中的事务管理器属性。 如果要连接的 TransactionManager bean 具有任何其他名称,则必须显式使用 transaction-manager 属性,如前面的示例中所示。

定义确保 txAdvice bean 定义的事务通知在程序中的适当点运行。 首先,您定义一个切入点,该切入点与 FooService 接口 (fooServiceOperation) 中定义的任何操作的执行相匹配。 然后使用 advisor 将切入点与 txAdvice 相关联。 结果表明,在执行 fooServiceOperation 时,会运行 txAdvice 定义的通知。

元素中定义的表达式是一个 AspectJ 切入点表达式。 有关 Spring 中切入点表达式的更多详细信息,请参阅 AOP 部分。

一个常见的要求是使整个服务层具有事务性。 最好的方法是更改切入点表达式以匹配服务层中的任何操作。 以下示例显示了如何执行此操作:

<aop:config>
    <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
aop:config>

在前面的示例中,假设您的所有服务接口都在 x.y.service 包中定义。 有关更多详细信息,请参阅 AOP 部分。

既然我们已经分析了配置,您可能会问自己,“所有这些配置实际上做了什么?”

前面显示的配置用于围绕从 fooService bean 定义创建的对象创建事务代理。 代理配置了事务通知,以便在代理上调用适当的方法时,根据与该方法关联的事务配置,启动、暂停、标记为只读等事务。 考虑以下测试驱动前面显示的配置的程序:

public final class Boot {

    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml");
        FooService fooService = ctx.getBean(FooService.class);
        fooService.insertFoo(new Foo());
    }
}

运行上述程序的输出应类似于以下内容(为清楚起见,已截断了来自 DefaultFooService 类的 insertFoo(…) 方法抛出的 UnsupportedOperationException 的 Log4J 输出和堆栈跟踪):


[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors


[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]


[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo


[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction


[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException]


[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@a53de4]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
[DataSourceUtils] - Returning JDBC Connection to DataSource

Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)

at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)

要使用响应式事务管理,代码必须使用响应式类型。

Spring Framework 使用 ReactiveAdapterRegistry 来确定方法返回类型是否是响应式的。

以下清单显示了之前使用的 FooService 的修改版本,但这次代码使用了反应式类型:

// the reactive service interface that we want to make transactional

package x.y.service;

public interface FooService {

    Flux<Foo> getFoo(String fooName);

    Publisher<Foo> getFoo(String fooName, String barName);

    Mono<Void> insertFoo(Foo foo);

    Mono<Void> updateFoo(Foo foo);

}

以下示例显示了上述接口的实现:

package x.y.service;

public class DefaultFooService implements FooService {

    @Override
    public Flux<Foo> getFoo(String fooName) {
        // ...
    }

    @Override
    public Publisher<Foo> getFoo(String fooName, String barName) {
        // ...
    }

    @Override
    public Mono<Void> insertFoo(Foo foo) {
        // ...
    }

    @Override
    public Mono<Void> updateFoo(Foo foo) {
        // ...
    }
}

声明式和响应式事务管理共享相同的事务边界和事务属性定义语义。 声明式事务和响应式事务之间的主要区别在于后者的延迟性质。 TransactionInterceptor 使用事务运算符修饰返回的响应类型以开始和清理事务。 因此,调用事务响应方法将实际事务管理推迟到激活响应类型处理的订阅类型。

响应式事务管理的另一方面涉及数据转义,这是编程模型的自然结果。

声明式事务的方法返回值在方法成功终止时从事务方法返回,以便部分计算的结果不会逃脱方法闭包。

响应式事务方法返回一个响应式包装器类型,它表示一个计算序列以及开始和完成计算的承诺。

Publisher可以在事务正在进行但不一定完成时发出数据。 因此,依赖于成功完成整个事务的方法需要确保完成并在调用代码中缓冲结果。

4.3 回滚声明式事务

上一节概述了如何在应用程序中以声明方式为类(通常是服务层类)指定事务设置的基础知识。 本节介绍如何以简单的声明方式控制事务的回滚。

向 Spring Framework 的事务基础结构表明事务的工作将被回滚的推荐方法是从当前在事务上下文中执行的代码抛出异常。 Spring Framework 的事务基础结构代码会捕获任何未处理的异常,因为它会在调用堆栈中冒泡并决定是否将事务标记为回滚。

在其默认配置中,Spring Framework 的事务基础结构代码仅在运行时、未检查异常的情况下才将事务标记为回滚。 也就是说,当抛出的异常是 RuntimeException 的实例或子类时。 (默认情况下,错误实例也会导致回滚)。 从事务方法抛出的已检查异常不会导致默认配置中的回滚。

您可以准确配置哪些异常类型将事务标记为回滚,包括已检查的异常。 以下 XML 片段演示了如何为已检查的特定于应用程序的异常类型配置回滚:

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
    <tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
    <tx:method name="*"/>
    tx:attributes>
tx:advice>

如果您不希望在抛出异常时回滚事务,您还可以指定“无回滚规则”。 以下示例告诉 Spring Framework 的事务基础结构即使面对未处理的 InstrumentNotFoundException 也提交伴随的事务:

<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
    <tx:method name="*"/>
    tx:attributes>
tx:advice>

当 Spring Framework 的事务基础架构捕获异常并查询配置的回滚规则以确定是否将事务标记为回滚时,最强匹配规则获胜。 因此,在以下配置的情况下,除 InstrumentNotFoundException 之外的任何异常都会导致伴随事务回滚:

<tx:advice id="txAdvice">
    <tx:attributes>
    <tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
    tx:attributes>
tx:advice>

您还可以以编程方式指示需要的回滚。 虽然简单,但这个过程非常具有侵入性,并且将您的代码与 Spring Framework 的事务基础结构紧密耦合。 以下示例显示了如何以编程方式指示所需的回滚:

public void resolvePosition() {
    try {
        // some business logic...
    } catch (NoProductInStockException ex) {
        // trigger rollback programmatically
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

如果可能,强烈建议您使用声明性方法进行回滚。 如果您绝对需要它,则可以使用程序回滚,但它的使用与实现基于 POJO 的干净架构相悖。

4.4 为不同的 Bean 配置不同的事务语义

考虑这样一个场景,您有多个服务层对象,并且您希望对每个对象应用完全不同的事务配置。 您可以通过定义具有不同切入点和通知引用属性值的不同 元素来实现。

作为比较点,首先假设您的所有服务层类都定义在根 x.y.service 包中。 要使作为该包(或子包)中定义的类的实例且名称以 Service 结尾的所有 bean 具有默认事务配置,您可以编写以下内容:


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:config>

        <aop:pointcut id="serviceOperation"
                expression="execution(* x.y.service..*Service.*(..))"/>

        <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>

    aop:config>

    
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <bean id="barService" class="x.y.service.extras.SimpleBarService"/>

    
    <bean id="anotherService" class="org.xyz.SomeService"/> 
    <bean id="barManager" class="x.y.service.SimpleBarManager"/> 

    <tx:advice id="txAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        tx:attributes>
    tx:advice>

    

beans>

以下示例显示如何使用完全不同的事务设置配置两个不同的 bean:


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:config>

        <aop:pointcut id="defaultServiceOperation"
                expression="execution(* x.y.service.*Service.*(..))"/>

        <aop:pointcut id="noTxServiceOperation"
                expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>

        <aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>

        <aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>

    aop:config>

    
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    
    <bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>

    <tx:advice id="defaultTxAdvice">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        tx:attributes>
    tx:advice>

    <tx:advice id="noTxAdvice">
        <tx:attributes>
            <tx:method name="*" propagation="NEVER"/>
        tx:attributes>
    tx:advice>

    

beans>

4.5 设置

本节总结了您可以使用 标签指定的各种事务设置。 默认的 设置是:

  • 传播设置是 REQUIRED
  • 隔离级别为 DEFAULT
  • 事务是读写的。
  • 事务超时默认为底层事务系统的默认超时,如果不支持超时,则不设置。
  • 任何 RuntimeException 都会触发回滚,而任何已检查的 Exception 都不会。

您可以更改这些默认设置。 下表总结了嵌套在 标签中的 标签的各种属性:

属性 必需的? 默认 描述
name 与事务属性相关联的方法名称。 通配符 (*) 可用于将相同的事务属性设置与多种方法(例如,get*、handle*、on*Event 等)相关联。
propagation REQUIRED 事务传播行为。
isolation DEFAULT 事务隔离级别。 仅适用于 REQUIRED 或 REQUIRES_NEW 的传播设置。
timeout -1 事务超时(秒)。 仅适用于传播 REQUIRED 或 REQUIRES_NEW。
read-only false 读写与只读事务。 仅适用于 REQUIRED 或 REQUIRES_NEW。
rollback-for 触发回滚的 Exception 实例的逗号分隔列表。 例如,com.foo.MyBusinessException,ServletException。
no-rollback-for 不触发回滚的 Exception 实例的逗号分隔列表。 例如,com.foo.MyBusinessException,ServletException。

4.6 使用 @Transactional

除了用于事务配置的基于 XML 的声明性方法之外,您还可以使用基于注解的方法。 直接在 Java 源代码中声明事务语义会使声明更接近受影响的代码。 不存在过度耦合的危险,因为旨在以事务方式使用的代码几乎总是以这种方式部署的。

标准 javax.transaction.Transactional 注解也被支持作为 Spring 自己注解的替代。 有关更多详细信息,请参阅 JTA 1.2 文档。

使用 @Transactional 注解提供的易用性最好通过一个例子来说明,这在下面的文本中进行了解释。 考虑以下类定义:

// the service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

    @Override
    public Foo getFoo(String fooName) {
        // ...
    }

    @Override
    public Foo getFoo(String fooName, String barName) {
        // ...
    }

    @Override
    public void insertFoo(Foo foo) {
        // ...
    }

    @Override
    public void updateFoo(Foo foo) {
        // ...
    }
}

如上在类级别使用,注解指示声明类(及其子类)的所有方法的默认值。 或者,每个方法都可以单独注解。 有关 Spring 将哪些方法视为事务性的更多详细信息,请参阅方法可见性和 @Transactional。 请注意,类级别的注解不适用于在类层次结构中向上的祖先类; 在这种情况下,需要在本地重新声明继承的方法才能参与子类级别的注解。

当上面的 POJO 类在 Spring 上下文中定义为 bean 时,您可以通过 @Configuration 类中的 @EnableTransactionManagement 注解使 bean 实例具有事务性。 有关完整详细信息,请参阅 javadoc。

在 XML 配置中, 标签提供了类似的便利:



<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    
    
    <tx:annotation-driven transaction-manager="txManager"/> 

    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        
        <property name="dataSource" ref="dataSource"/>
    bean>

    

beans>

如果要连接的 TransactionManager 的 bean 名称具有名称 transactionManager,则可以省略 标记中的 transaction-manager 属性。 如果要依赖注入的 TransactionManager bean 具有任何其他名称,则必须使用 transaction-manager 属性,如前面的示例所示。

与声明式编程安排相比,响应式事务方法使用响应式返回类型,如下面的清单所示:

// the reactive service class that we want to make transactional
@Transactional
public class DefaultFooService implements FooService {

    @Override
    public Publisher<Foo> getFoo(String fooName) {
        // ...
    }

    @Override
    public Mono<Foo> getFoo(String fooName, String barName) {
        // ...
    }

    @Override
    public Mono<Void> insertFoo(Foo foo) {
        // ...
    }

    @Override
    public Mono<Void> updateFoo(Foo foo) {
        // ...
    }
}

请注意,对于 Reactive Streams 取消信号,返回的 Publisher 有一些特殊注意事项。 有关更多详细信息,请参阅“使用 TransactionOperator”下的取消信号部分。

方法可见性和 @Transactional

  • 当您在 Spring 的标准配置中使用事务代理时,您应该仅将 @Transactional 注解应用于具有公共可见性的方法。 如果您使用 @Transactional 注解对受保护的、私有的或包可见的方法进行注解,则不会引发错误,但带注解的方法不会显示配置的事务设置。 如果您需要注解非公共方法,请考虑以下段落中有关基于类的代理的提示,或考虑使用 AspectJ 编译时或加载时编织(稍后描述)。
  • @Configuration 类中使用 @EnableTransactionManagement 时,也可以通过注册自定义 transactionAttributeSource bean 来使受保护或包可见的方法对基于类的代理具有事务性,如下例所示。 但是请注意,基于接口的代理中的事务方法必须始终是公共的并在代理接口中定义。
/**
 * Register a custom AnnotationTransactionAttributeSource with the
 * publicMethodsOnly flag set to false to enable support for
 * protected and package-private @Transactional methods in
 * class-based proxies.
 *
 * @see ProxyTransactionManagementConfiguration#transactionAttributeSource()
 */
@Bean
TransactionAttributeSource transactionAttributeSource() {
    return new AnnotationTransactionAttributeSource(false);
}
  • Spring TestContext Framework 默认支持非私有的@Transactional 测试方法。 有关示例,请参阅测试章节中的事务管理。

您可以将 @Transactional 注解应用于接口定义、接口上的方法、类定义或类上的方法。 然而,仅仅存在 @Transactional 注解并不足以激活事务行为。 @Transactional 注解只是一些运行时基础设施可以使用的元数据,这些基础设施是 @Transactional-aware 并且可以使用元数据来配置具有事务行为的适当 bean。 在前面的示例中, 元素开启了事务行为。

Spring 团队建议您只使用 @Transactional 注解来注释具体类(和具体类的方法),而不是注解接口。 您当然可以将 @Transactional 注解放在接口(或接口方法)上,但是如果您使用基于接口的代理,这只能像您期望的那样工作。Java 注释不是从接口继承的事实意味着,如果您使用基于类的代理 (proxy-target-class=“true”) 或基于编织的方面(mode=“aspectj”),代理和编织基础设施无法识别事务设置,并且对象未包装在事务代理中。

在代理模式下(默认),只有通过代理进入的外部方法调用才会被拦截。 这意味着自调用(实际上,目标对象中的一个方法调用目标对象的另一个方法)在运行时不会导致实际事务,即使被调用的方法用 @Transactional 标记。 此外,代理必须完全初始化以提供预期的行为,因此您不应在初始化代码中依赖此功能 — 例如,在 @PostConstruct 方法中。

如果您希望自调用也与事务一起包装,请考虑使用 AspectJ 模式(请参阅下表中的 mode 属性)。 在这种情况下,首先没有代理。 相反,目标类被编织(即,它的字节码被修改)以支持任何类型的方法上的 @Transactional 运行时行为。

XML 属性 注解属性 默认 描述
transaction-manager N/A(请参阅TransactionManagementConfigurer javadoc) transactionManager 要使用的事务管理器的名称。 仅当事务管理器的名称不是 transactionManager 时才需要,如前面的示例所示。
mode mode proxy 默认模式(代理)通过使用 Spring 的 AOP 框架(遵循代理语义,如前所述,仅适用于通过代理传入的方法调用)处理带注释的 bean。 替代模式 (aspectj) 而是将受影响的类与 Spring 的 AspectJ 事务方面编织在一起,修改目标类字节码以应用于任何类型的方法调用。 AspectJ 编织需要类路径中的 spring-aspects.jar 以及启用加载时编织(或编译时编织)。 (有关如何设置加载时编织的详细信息,请参阅 Spring 配置。)
proxy-target-class proxyTargetClass false 仅适用于代理模式。 控制为使用 @Transactional 注解的类创建哪种类型的事务代理。 如果 proxy-target-class 属性设置为 true,则会创建基于类的代理。 如果 proxy-target-class 为 false 或省略该属性,则会创建标准的基于 JDK 接口的代理。 (有关不同代理类型的详细检查,请参阅代理机制。)
order order Ordered.LOWEST_PRECEDENCE 定义应用于用@Transactional 注解的 bean 的事务建议的顺序。 (有关与 AOP 建议排序相关的规则的更多信息,请参阅建议排序。)没有指定的排序意味着 AOP 子系统确定建议的顺序。

处理 @Transactional 注解的默认通知模式是代理,它只允许通过代理拦截调用。 不能以这种方式拦截同一类中的本地调用。 对于更高级的拦截模式,可以考虑结合编译时或加载时编织切换到 aspectj 模式。

proxy-target-class 属性控制为使用 @Transactional 注解的类创建什么类型的事务代理。 如果 proxy-target-class 设置为 true,则会创建基于类的代理。 如果 proxy-target-class 为 false 或省略该属性,则会创建标准的基于 JDK 接口的代理。 (有关不同代理类型的讨论,请参阅代理机制。)

@EnableTransactionManagement仅在定义它们的同一应用程序上下文中的 bean 上查找 @Transactional。 这意味着,如果您将注解驱动的配置放在 DispatcherServlet 的 WebApplicationContext 中,它只会在您的控制器中而不是在您的服务中检查 @Transactional bean。 有关更多信息,请参阅 MVC。

在评估方法的事务设置时,最派生的位置优先。在以下示例的情况下, DefaultFooService 类在类级别使用只读事务的设置进行注解,但同一类中 updateFoo(Foo) 方法上的 @Transactional 注解优先于在类级别定义的事务设置。

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        // ...
    }

    // these settings have precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // ...
    }
}

4.6.1 @Transactional 设置

@Transactional 注解是元数据,指定接口、类或方法必须具有事务语义(例如,“在调用此方法时启动全新的只读事务,暂停任何现有事务”)。 默认的@Transactional 设置如下:

  • 传播设置是 PROPAGATION_REQUIRED
  • 隔离级别为 ISOLATION_DEFAULT
  • 事务是读写的。
  • 事务超时默认为底层事务系统的默认超时,如果不支持超时,则为无。
  • 任何 RuntimeException 都会触发回滚,而任何已检查的 Exception 都不会。

您可以更改这些默认设置。 下表总结了@Transactional 注解的各种属性:

属性 类型 描述
value String 指定要使用的事务管理器的可选限定符。
propagation enum: Propagation 可选的传播设置。
isolation enum: Isolation 可选的隔离级别。 仅适用于“REQUIRED”或“REQUIRES_NEW”的传播值。
timeout int(以秒为单位) 可选的事务超时。 仅适用于“REQUIRED”或“REQUIRES_NEW”的传播值。
readOnly boolean 读写与只读事务。 仅适用于“REQUIRED”或“REQUIRES_NEW”的值。
rollbackFor Class 对象数组,必须从 Throwable 派生。 必须导致回滚的异常类的可选数组。
rollbackForClassName 类名数组。 这些类必须从Throwable. 派生。 必须导致回滚的异常类名称的可选数组。
noRollbackFor Class 对象数组,必须从 Throwable 派生。 必须不会导致回滚的异常类的可选数组。
noRollbackForClassName String 类名的数组,必须从 Throwable 派生。 不得导致回滚的异常类名称的可选数组。
label String 标签数组,用于向事务添加富有表现力的描述。 标签可以由事务管理器评估以将特定于实现的行为与实际事务相关联。

目前,您无法显式控制事务的名称,其中“名称”表示出现在事务监视器(如果适用)(例如,WebLogic 的事务监视器)和日志输出中的事务名称。 对于声明性事务,事务名称始终是完全限定的类名 +.+ 事务通知类的方法名称。 例如,如果 BusinessService 类的 handlePayment(…) 方法启动了一个事务,那么该事务的名称将是:com.example.BusinessService.handlePayment。

4.6.2 使用 @Transactional 的多个事务管理器

大多数 Spring 应用程序只需要一个事务管理器,但在某些情况下,您可能需要在一个应用程序中使用多个独立的事务管理器。 您可以使用 @Transactional 注解的 value 或 transactionManager 属性来选择性地指定要使用的 TransactionManager 的标识。 这可以是事务管理器 bean 的 bean 名称或限定符值。 例如,使用限定符表示法,您可以将以下 Java 代码与应用程序上下文中的以下事务管理器 bean 声明组合起来:

public class TransactionalService {

    @Transactional("order")
    public void setSomething(String name) { ... }

    @Transactional("account")
    public void doSomething() { ... }

    @Transactional("reactive-account")
    public Mono<Void> doSomethingReactive() { ... }
}

以下清单显示了 bean 声明:

<tx:annotation-driven/>

    <bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        ...
        <qualifier value="order"/>
    bean>

    <bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        ...
        <qualifier value="account"/>
    bean>

    <bean id="transactionManager3" class="org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager">
        ...
        <qualifier value="reactive-account"/>
    bean>

在这种情况下, TransactionalService 上的各个方法在单独的事务管理器下运行,由 order、account 和 react-account 限定符区分。 如果没有找到特别限定的 TransactionManager bean,则仍然使用默认的 目标 bean 名称 transactionManager。

4.6.3 自定义合成注解

如果您发现在许多不同的方法上重复使用带有 @Transactional 的相同属性,Spring 的元注解支持允许您为特定用例定义自定义组合注解。 例如,考虑以下注解定义:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "order", label = "causal-consistency")
public @interface OrderTx {
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional(transactionManager = "account", label = "retryable")
public @interface AccountTx {
}

前面的注解让我们把上一节的例子写成如下:

public class TransactionalService {

    @OrderTx
    public void setSomething(String name) {
        // ...
    }

    @AccountTx
    public void doSomething() {
        // ...
    }
}

在前面的示例中,我们使用语法来定义事务管理器限定符和事务标签,但我们也可以包含传播行为、回滚规则、超时和其他功能。

4.7 事务传递

本节介绍 Spring 中事务传播的一些语义。 请注意,本节不是对事务传播的正确介绍。 相反,它详细介绍了有关 Spring 中事务传播的一些语义。

在 Spring 管理的事务中,请注意物理事务和逻辑事务之间的差异,以及传播设置如何应用于这种差异。

4.7.1 了解 PROPAGATION_REQUIRED

spring框架学习 - Data Access之 事务管理 - 声明式事务管理_第2张图片
PROPAGATION_REQUIRED 强制执行物理事务,如果尚不存在事务,则在当前范围本地执行或参与为更大范围定义的现有“外部”事务。 这是同一线程内公共调用堆栈安排中的一个很好的默认设置(例如,委托给多个存储库方法的服务外观,其中所有底层资源都必须参与服务级别事务)。

默认情况下,参与事务加入外部作用域的特征,静默忽略本地隔离级别、超时值或只读标志(如果有)。 如果您希望在参与具有不同隔离级别的现有事务时拒绝隔离级别声明,请考虑将事务管理器上的 validateExistingTransactions 标志切换为 true。 这种非宽松模式还拒绝只读不匹配(即尝试参与只读外部作用域的内部读写事务)。

当传播设置为 PROPAGATION_REQUIRED 时,将为应用该设置的每个方法创建一个逻辑事务范围。 每个这样的逻辑事务范围可以单独确定仅回滚状态,外部事务范围在逻辑上独立于内部事务范围。 在标准 PROPAGATION_REQUIRED 行为的情况下,所有这些范围都映射到同一个物理事务。 因此,在内部事务范围内设置的仅回滚标记确实会影响外部事务实际提交的机会。

但是,在内部事务作用域设置了仅回滚标记的情况下,外部事务本身并没有决定回滚,因此回滚(由内部事务作用域静默触发)是意外的。 此时会抛出相应的 UnexpectedRollbackException。 这是预期的行为,因此事务的调用者永远不会被误导,认为提交已执行,而实际上并没有执行。 所以,如果一个内部事务(外部调用者不知道)悄悄地将一个事务标记为仅回滚,外部调用者仍然调用 commit。 外部调用者需要接收一个 UnexpectedRollbackException 来清楚地表明执行了回滚。

4.7.2 了解 PROPAGATION_REQUIRES_NEW

spring框架学习 - Data Access之 事务管理 - 声明式事务管理_第3张图片
PROPAGATION_REQUIRES_NEW 与 PROPAGATION_REQUIRED 相比,始终为每个受影响的事务范围使用独立的物理事务,从不参与外部范围的现有事务。 在这种安排下,底层资源事务是不同的,因此可以独立提交或回滚,外部事务不受内部事务回滚状态的影响,内部事务的锁在完成后立即释放。 这样一个独立的内部事务也可以声明自己的隔离级别、超时和只读设置,而不继承外部事务的特性。

4.7.3 了解 PROPAGATION_NESTED

PROPAGATION_NESTED 使用具有多个可以回滚到的保存点的单个物理事务。 这种部分回滚让内部事务作用域触发其作用域的回滚,尽管某些操作已回滚,但外部事务仍能够继续物理事务。 此设置通常映射到 JDBC 保存点,因此它仅适用于 JDBC 资源事务。 参见 Spring 的 DataSourceTransactionManager。

4.8 为事务性操作提供通知

假设您想要运行事务操作和一些基本的分析通知。 您如何在 的上下文中实现这一点?

当您调用 updateFoo(Foo) 方法时,您希望看到以下操作:

  • 已配置的分析切面启动。
  • 事务性通知运行。
  • 被通知对象的方法运行。
  • 事务提交。
  • 分析切面报告整个事务方法调用的确切持续时间。

本章不涉及详细解释 AOP(除非它适用于事务)。 有关 AOP 配置和 AOP 总体的详细介绍,请参阅 AOP。

以下代码显示了前面讨论的简单分析切面:

package x.y;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
import org.springframework.core.Ordered;

public class SimpleProfiler implements Ordered {

    private int order;

    // allows us to control the ordering of advice
    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    // this method is the around advice
    public Object profile(ProceedingJoinPoint call) throws Throwable {
        Object returnValue;
        StopWatch clock = new StopWatch(getClass().getName());
        try {
            clock.start(call.toShortString());
            returnValue = call.proceed();
        } finally {
            clock.stop();
            System.out.println(clock.prettyPrint());
        }
        return returnValue;
    }
}

通知的排序是通过 Ordered 接口控制的。 有关建议排序的完整详细信息,请参阅通知排序。

以下配置创建了一个 fooService bean,该 bean 按所需顺序应用了分析和事务切面:


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    
    <bean id="profiler" class="x.y.SimpleProfiler">
        
        <property name="order" value="1"/>
    bean>

    <tx:annotation-driven transaction-manager="txManager" order="200"/>

    <aop:config>
            
            <aop:aspect id="profilingAspect" ref="profiler">
                <aop:pointcut id="serviceMethodWithReturnValue"
                        expression="execution(!void x.y..*Service.*(..))"/>
                <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
            aop:aspect>
    aop:config>

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
        <property name="username" value="scott"/>
        <property name="password" value="tiger"/>
    bean>

    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    bean>

beans>

您可以以类似的方式配置任意数量的附加切面。

以下示例创建与前两个示例相同的设置,但使用纯 XML 声明方法:


<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        https://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        https://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    
    <bean id="profiler" class="x.y.SimpleProfiler">
        
        <property name="order" value="1"/>
    bean>

    <aop:config>
        <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>
        

        <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" order="2"/>
        

        <aop:aspect id="profilingAspect" ref="profiler">
            <aop:pointcut id="serviceMethodWithReturnValue"
                    expression="execution(!void x.y..*Service.*(..))"/>
            <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
        aop:aspect>

    aop:config>

    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true"/>
            <tx:method name="*"/>
        tx:attributes>
    tx:advice>

    

beans>

前面配置的结果是一个 fooService bean,它按顺序应用了分析和事务切面。 如果您希望分析通知在进入事务通知之后和退出事务通知之前运行,您可以交换分析方面 bean 的 order 属性的值,使其高于事务通知的 order 值。

您可以以类似的方式配置其他切面。

4.9 在 AspectJ 中使用 @Transactional

您还可以通过 AspectJ 方面在 Spring 容器之外使用 Spring Framework 的 @Transactional 支持。为此,首先使用 @Transactional 注解来注解您的类(以及可选的类的方法),然后将您的应用程序与 spring-aspects.jar 文件中定义的 org.springframework.transaction.aspectj.AnnotationTransactionAspect链接(编织)。您还必须使用事务管理器配置切面。 您可以使用 Spring Framework 的 IoC 容器来处理方面的依赖注入。 配置事务管理切面的最简单方法是使用 元素并将 mode 属性指定给 aspectj,如使用 @Transactional 中所述。 因为我们在这里专注于在 Spring 容器之外运行的应用程序,所以我们向您展示了如何以编程方式执行此操作。

在继续之前,您可能需要分别阅读 Using @Transactional 和 AOP。

以下示例显示了如何创建事务管理器并配置 AnnotationTransactionAspect 以使用它:

// construct an appropriate transaction manager
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());

// configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);

当您使用这个切面时,您必须注解实现类(或该类中的方法或两者),而不是该类实现的接口(如果有)。 AspectJ 遵循 Java 的规则,即接口上的注解不能被继承。

类上的 @Transactional 注解指定了执行类中任何公共方法的默认事务语义。

类中方法上的 @Transactional 注解覆盖了类注解(如果存在)给出的默认事务语义。 您可以注解任何方法,而不管可见性如何。

要使用 AnnotationTransactionAspect 编织您的应用程序,您必须使用 AspectJ(请参阅 AspectJ 开发指南)构建您的应用程序或使用加载时编织。 有关使用 AspectJ 进行加载时编织的讨论,请参阅 Spring 框架中的使用 AspectJ 进行加载时编织。

文章参考:https://docs.spring.io/spring-framework/docs/current/reference/html/data-access.html#transaction-declarative

你可能感兴趣的:(spring框架,spring,java,intellij-idea,后端,maven)