spring framework-数据访问

这部分文档关于数据访问以及数据访问层和业务或服务层的交互。

Spring提供了全面的事务管理支持,并详细介绍了Spring框架集成的各种数据访问框架和技术。

事务管理

全面的事务支持是使用Spring Framework的最有说服力的原因之一。Spring Framework为事务管理提供一致性抽象,有下列好处:

  • 一致性编程模型横跨不同的事务API,比如Java事务API(JTA),JDBC,Hibernate和Java持久化API(JPA)。
  • 支持声明式事务管理
  • 比复杂的事务API,比如JTA,更简单的编程式事务管理
  • 和Spring的数据访问抽象完美集成

下列章节描述了Spring Framework的事务特性和技术:

  • Spring Framework事务支持模型的优势描述了为什么使用Spring Framework的事务抽象而不是EJB容器-管理事务(CMT)或选择通过专门的API来驱动本地事务,比如Hibernate
  • 理解Spring Framework事务抽象概述了核心类以及描述了如何配置和从各种源获取DataSource。
  • 使用事务同步资源描述了程序代码如何保证资源被合理地创建,复用和清理。
  • 声明式事务管理描述了声明式事务管理的支持。
  • 编程式事务管理覆盖了编程式(显式代码)事务管理支持。
  • 事务绑定事件描述了在事务内使用程序事件

本章节包括最佳事件,程序服务器击沉和常见问题解决方法的讨论。

Spring Framework的事务支持模型的优势

传统地,Java EE开发者关于事务管理有两个选择:全局或本地事务,两者都有很大的限制。全局和本地事务管理在下面两节回顾,然后是关于Spring Framework事务管理支持如何称呼全局和本地模型的限制的讨论。

全局事务

全局事务让你和多个事务资源工作,一般是关系型数据库和消息队列。程序服务器通过JTA管理全局事务,这是一个复杂的API(部分由于它的异常模型)。此外,JTA的UserTransaction通常需要从JNDI的资源,意味着为了使用JTA也需要也需要使用JNDI。全局事务的使用限制了任何程序代码的复用,因为JTA通常只在程序服务器环境可用。

先前提到,使用全局事务最好的方式是通过EJB CMT(容器管理事务)。CMT是一种声明式事务管理形式(区别于编程式事务管理)。EJB CMT移除了事务-相关JNDI查找的需要,尽管使用EJB本身是需要使用JNDI的。它移除了大部分但不是所有写Java代码控制事务的需求。重大的缺点是CMT和JTA和程序服务器环境捆绑。另外,只有选择在EJB实现事务逻辑CMT才可用(或至少在事务EJB门面之后)。一般说来EJB的缺点如此之大以至于不是一个有吸引力的建议,特别是面对声明式事务管理这个不可抗拒的的另一条途径。

本地事务

本地事务是资源相关的,比如和JDBC连接关联的事务。本地事务可能更容易使用,但是有一个明显的缺点:不能跨多个事务资源工作。比如,通过使用JDBC连接管理事务的代码不能在全局JTA事务运行。因为程序服务器和事务管理无关,所以不能保证跨多个资源的正确性。(这对于大多数只使用一个事务资源的程序不算什么。)本地事务的另一个缺点是对于编程模型的侵入性。

Spring Framework的一致性编程模型

Spring解决了全局和本地事务的缺点。让程序开发者在任何环境使用一致性编程模型。写代码一次,受益于不同环境的不同事务管理策略。Spring Framework提供声明式和编程式事务管理。大部分用户更喜欢声明式事务管理,也是大部分情况推荐的。
通过编程式事务管理,开发者和Spring Framework事务抽象工作,这层抽象可以在任何底层事务框架运行。通过更好的声明式模型,开发者通常书写很少或没有和事务管理相关的代码,这样,不依赖Spring Framework事务API或任何其他事务API。

需要一个程序服务器来事务管理吗?
Spring Framework的事务管理支持改变了传统规则,关于企业级Java程序需要程序服务器。
特别地,单纯通过EJB的声明事务不需要程序服务器。事实上,即使程序服务器有更强大的JTA能力,也会觉得Spring Framework的声明式事务提供更有力和更多产的编程模型,相比于EJB CMT。
通常地,只有程序需要处理跨多个资源地事务时才需要程序服务器地JTA能力,这对很多程序不是一个必要条件。很多高端程序使用单一,高伸缩性数据库(比如Oracle RAC)。单独地事务管理者(比如Atomikos事务和JOTM)是其他选择。当然,可能需要其他程序服务器能力,比如Java Message Service(JMS)和Java EE Connector Architecture(JCA)。
Spring Framework给你何时升级程序为一个完全加载的程序服务器的选择。只能使用EJB CMT或JTA来书写本地事务(比如JDBC连接上的事务)代码和大量重做如果需要代码在全局,容器-管理事务运行的日子已经过去。在Spring Framework中,只有一些配置文件中的bean定义需要改变(而不是代码)。

理解Spring Framework事务抽象

Spring事务抽象的关键是事务策略的理解。一个事务策略通过org.springframework.transaction.PlatformTransactionManager接口定义,如下列列表所示:

public interface PlatformTransactionManager {

    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;

    void commit(TransactionStatus status) throws TransactionException;

    void rollback(TransactionStatus status) throws TransactionException;
}

这是主要的服务提供者接口(SPI),尽管可以编程式地在程序代码中使用它。因为PlatformTransactionManager是一个接口,可以根据需要轻易地被模仿或占位。它没有和查找策略捆绑,比如JNDI。PlatformTransactionManager的实现和Spring Framework Ioc容器的任何其他对象(或bean)一样定义。单单这个好处使得Spring Framework事务是一个有价值的抽象,即使和JTA打交道。测试事务代码比直接使用JTA更加简单。

再次,和Spring的哲学保持一致,可以被任何PlatformTransactionManager的接口方法抛出的TransactionException是未检查的(也就是说,它继承java.lang.RuntimeException类)。事务框架错误通常是一贯的致命。很少情况下,程序代码实际上能从事务失败中恢复,程序开发者依然可以选择捕获和处理TransactionException。显著的点是开发者不会被强制这么做。

getTransaction(…)方法返回TransactionStatus对象,取决于TransactionDefinition参数。返回的TransactionStatus可以表示一个新的事务或表示一个存在的事务,如果一个匹配的事务在当前调用栈中存在。后者隐含的意思是,在Java EE事务上下文中,TransactionStatus和执行线程相关。

TransactionDefinition接口指定:

  • 传播:通常,在一个事务范围执行的所有代码在那个事务运行。然而,可以指定行为,如果事务方法在事务上下文已经存在时。比如,代码可以继续在存在的事务继续运行(通常情况),或存在的事务可能被中止并且一个新的事务创建。Spring提供左右的事务传播选线,和EJB CMT类似。要阅读Spring的事务传播的语义,参见事务传播
  • 隔离:这个事务和其他事务的隔离程度。比如,这个事务可以看到其他事务未提交的写入吗?
  • 超时:这个事务运行多少时间在超时并自动通过底层事务框架回滚之前?
  • 只读状态:可以使用只读事务如果代码只读取而不修改数据。只读事务在某些场景非常有用,比如使用Hibernate时。

这些设置影响标准事务概念。如果必要,参考讨论事务隔离层次和其他核心事务概念的资源。理解这些概念对于使用Spring Framework或任何事务管理方案是必不可少的。

TransactionStatus接口提供简单的事务代码方式来控制事务执行和查询事务状态。这些概念应该熟悉,因为它们和所有的事务API类似。下面列表展示了TransactionStatus接口:

public interface TransactionStatus extends SavepointManager {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();
}

无论是否选择Spring的声明式或编程式事务管理,定义正确的PlatformTransactionManager实现是完全必要的。通过通过依赖注入定义实现。

PlatformTransactionManager实现通常需要知道它们工作的环境:JDBC,JTA,Hibernate,等等。下列例子展示了如何定义本地PlatformTransactionManager实现(本例中,用JDBC)。

可以定义JDBC的DataSource,通过创建类似下面的bean:

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>

相关的PlatformTransactionManager对象定义有一个DataSource定义的引用。应该像下面的例子:

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

如果在Java EE容器中使用JTA,可以使用容器DataSource,从JNDI获取,连同Spring的JtaTransactionManager。下面例子展示了JTA和JNDI查找:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jee
        https://www.springframework.org/schema/jee/spring-jee.xsd">

    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>

    <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />

    <!-- other <bean/> definitions here -->

</beans>

JtaTransactionManager不需要知道DataSource(或其他任何相关资源)因为它使用容器的全局事务管理框架。

先前定义的dataSource对象使用<jndi-lookup/>标签,来自jee命名空间。更多信息参见JEE Schema

也可以轻松地使用Hibernate本地事务,如下面例子所展示。这种情况下,需要定义Hibernate的LocalSessionFactoryBean,程序代码可用来获取Hibernate的Session实例。

DataSource定义和先前本地JDBC的例子类似,这样,就不在本例展示了。

如果DataSource(被任何non-JTA事务管理器使用)是通过JNDI查找并且被Java EE容器管理,它应该是non-transactional,因为Spring Framework(而不是Java EE容器)管理这些事务。

这种情况下txManager是HibernateTransactionManager类型。正如DataSourceTransactionManager需要一个DataSource的引用,HibernateTransactionManager需要一个SessionFactory的引用。下面的例子申明了sessionFactory和txManager对象:

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="mappingResources">
        <list>
            <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
        </list>
    </property>
    <property name="hibernateProperties">
        <value>
            hibernate.dialect=${hibernate.dialect}
        </value>
    </property>
</bean>

<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

如果使用Hibernate和Java EE容器-管理的JTA事务,可以使用先前JDBC例子相同的JtaTransactionManager,如下例所示:

<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

如果使用JTA,事务管理器定义应该看起来相同,无论使用哪种数据访问技术,比如JDBC,Hibernate JPA或其他任何支持的技术。这是由于事实JTA事务是全局事务,可以争取任何事务资源。

在所有的情况中,程序代码无需改变。仅需改变配置来改变事务的管理,即使改变意味着从本地转移到全局事务或反之亦然。

用事务同步资源

如何创建不同的事务管理器以及如何链接到相关的需要同步事务(比如DataSourceTransactionManager之于JDBC的DataSource,HibernateTransactionManager之于Hibernate的SessionFactory,等等)的资源现在应该清楚了。本节讨论程序代码(直接或间接,使用持久化API比如JDBC,Hibernate或JPA)如何保证这些资源被正确地创建,复用和清理。本节也讨论了事务同步(随意地)如何通过相关的PlatformTransactionManager触发的。

高层次同步方法

最好地方法是使用Spring的最高层次模板,基于持久化集成API或使用携带事务感知工厂对象或管理本地资源工厂的代理的本地ORM API。这些事务感知方案内部地处理资源创建和复用,清理,资源可选的事务同步,和异常映射。这样,用户数据访问代码不必处理这些任务而只需关注非模板化的持久化逻辑。一般地,使用本地ORM API或使用JdbcTemplate作为JDCBC访问的模板方法。这些方案本文档下面几章详述。

低层次同步方法

类比如DataSourceUtils(JDBC),EntityManagerFactoryUtils(JPA),SessionFactoryUtils(Hibernate),等等存在于低层次。当想程序代码直接使用本地持久化API处理资源类型,使用这些类来保证合理的Spring Framework管理的实例被获取,事务(可选的)被同步,并且过程发生的异常被合理地映射为一致的API。

比如,JDBC,除了传统的JDBC方法调用DataSource的getConnection()方法,也可使用Spring的org.springframework.jdbc.datasource.DataSourceUtils类,如下:

Connection conn = DataSourceUtils.getConnection(dataSource);

如果一个已存的事务已经有一个连接同步(链接)到它,那个实例返回。否则,这个方法调用触发新连接的创建,(随意地)同步到任何已存事务,使得后续在同一事务上的复用成为可能。如前面提到,任何的SQLException被包装成Spring Framework的CannotGetJdbcConnectionException,一个Spring Framework的未检查DataAccessException类型的继承链的一员。这个方法给你更多信息,比从SQLException更轻松地获取,并保证了跨数据库的可移植性,即使跨不同的持久化技术。

这个方法也能没有Spring事务管理工作(事务同步是可选的),所以可以使用它无论是否使用Spring来事务管理。

当然,一旦使用Spring的JDBC支持,JPA支持,或Hibernate支持,通常不喜欢用DataSourceUtils或其他帮助类,因为更高兴于使用Spring抽象而不是直接使用相关的API。比如,如果使用Spring的JdbcTemplate或jdbc.object包来简化JDBC的使用,修正连接数据检索发生在场景背后,而无需书写任何特殊代码。

TransactionAwareDataSourceProxy

在非常低层次存在着TransactionAwareDataSourceProxy类。这是一个目标DataSource的代理,包装着目标DataSource来添加Spring管理的事务的认识。从这个层面看,和传统的JNDI的DataSource类似,由Java EE服务器提供。
大部分情况应该从不需要或想要使用这个类,除了现有的代码必须被调用并且传入一个标准的JDBC DataSource接口实现。这样,这些代码是可用的但是参与Spring管理的事务。可以书写新的代码,通过使用前面提到的高层次抽象。

声明式事务管理

大部分Spring Framework用户选择声明式事务管理。这个选择对程序代码由最少的影响,并且这样,是最一致的,考虑到非倾入式轻量级容器的理想。

Spring Framework的声明式事务管理通过Spring基于切面编程(AOP)成为可能。然而,由于事务切面代码来自Spring Framework分布,并且可能以样本代码风格使用,一般不用太理解AOP概念来高效使用这些代码。

Spring Framework的声明式事务管理类似EJB CMT,可以指定事务行为(或不指定)到单个方法级别。可以在事务上下文范围调用setRollbackOnly(),如果需要的话。两种乐星的事务管理的区别是:

  • 不同于EJB CMT,和JTA捆绑,Spring Framework的声明式事务管理在任何环境工作。可以调整配置文件和JTA事务或和使用JDBC,JPA或Hibernate的本地事务一起工作。
  • 可以应用Spring Framework声明式事务管理到任何类,不仅仅是特殊类比如EJB。
  • Spring Framework提供声明式回滚规则,一个没有EJB等价的特性。编程式和声明式支持的回滚规则都提供。
  • Spring Framework能通过使用AOP自定义事务行为。比如,能在事务回滚时插入自定义行为。也能添加任意的建议,跟事务建议一起。通过EJB CMT,不能影响容器的事务管理,除了setRollbackOnly()。
  • Spring Framework不支持跨远程调用的事务上下文的传播,就像高端的程序服务器。如果需要这个特性,建议使用EJB。然而,使用这个特性前要考虑仔细,因为,一般情况下,不想事务跨远程调用。

回滚规则概念是非常重要的。让你指定哪些异常应该导致自动回滚。可以声明式地指定这些,在配置文件中,而不是Java代码。所以,尽管也能通过调用TransactionStatus对象的setRollbackOnly()来回滚当前事务回去,多数情况可以指定一个规则,MyApplicationException一定导致回滚。这个选项最明显的优点是业务对象不取决于事务框架。比如,不需要导入Spring事务API或其他Spring API。

尽管EJB容器默认行为在系统异常(通常运行时异常)时自动回滚事务,EJB CMT不会再程序异常(即检查异常不仅仅是java.rmi.RemoteException)时自动回滚事务。因为Spring声明式事务管理的默认行为遵守EJB惯例(只在未检查异常时回滚),自定义这个行为通常有用。

理解Spring Framework的声明式事务实现

仅仅告诉你用@Transactional注解你的类,添加@EnableTransactionManagement到配置文件,并且希望你理解它是如何工作的,是不足够的。为提供更深层次的理解,本节解释了Spring Framework的声明式事务框架发生事务相关问题的内部工作原理。
关于Spring Framework的声明式事务支持最重要需要把握的概念是这个支持通过AOP代理开启的,并且事务建议通过元数据驱动(当前是XML-或基于注解)。AOP和事务元数据的结合产生一个AOP代理,使用一个TransactionInterceptor结合一个合适的PlatformTransactionManager实现来驱动方法执行周围的事务。

Spring AOP在AOP节提到。

下图展示了调用事务代理方法的概念视图。
在这里插入图片描述

声明式事务实现的例子

考虑下面接口和它伴随的实现。这个例子使用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),必须在事务上下文以读写语义执行。下列配置在下面少量段落详细阐述:

<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<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">

    <!-- this is the service object that we want to make transactional -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!-- the transactional semantics... -->
        <tx:attributes>
            <!-- all methods starting with 'get' are read-only -->
            <tx:method name="get*" read-only="true"/>
            <!-- other methods use the default transaction settings (see below) -->
            <tx:method name="*"/>
        </tx:attributes>
    </tx:advice>

    <!-- ensure that the above transactional advice runs for any execution
        of an operation defined by the FooService interface -->
    <aop:config>
        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
    </aop:config>

    <!-- don't forget the DataSource -->
    <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>

    <!-- similarly, don't forget the PlatformTransactionManager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- other <bean/> definitions here -->

</beans>

检查先前的配置。假设想要一个服务对象,fooService,事务化。要应用的事务语义在<tx:advice/>定义中概括。<tx:advice/>定义解读为“所有的方法,以get开头,在只读事务上下文执行,其他的方法在默认事务语义下执行”。<tx:advice/>的transaction-manager属性设置了PlatformTransactionManager对象的名称,该对象将要用来驱动事务(这种情况下,txManager对象)。

可以忽略事务建议(<tx:advice/>)的transaction-manager属性,如果将要织入的PlatformTransactionManager对象的名称是transactionManager。如果想要织入的PlatformTransactionManager有其他任何名称,必须显示使用transaction-manager属性,如先前的例子。

<aop:config/>定义保证了txAdvice定义的事务建议在程序合适的切点执行。首先,定义一个匹配FooService接口(fooServiceOperation)定义的所有操作执行的切点。然后同使用advisor来关联切点到txAdvice。结果表明,fooServiceOperation执行期间,txAdvice定义的建议运行。
<aop:pointcut/>定义的表达式是一个AspectJ切点表达式。查看AOP节获取更多关于Spring切点表达式的信息。
一般的需求是使整个服务层事务化。最好的办法是改变切点表达式来匹配服务层的任何操作。下列例子展示了如何做:

<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定义创建的对象。这个代理配置了事务建议,以便于,当代理的相关方法执行时,一个事务开启,中止,标志位只读,等等,取决于方法关联的事务配置。考虑下面测试驱动上面配置的程序:

public final class Boot {

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

运行先前的程序应该出现类似下面的输出形式(Log4J输出和DefaultFooService类的insertFoo(…)方法抛出的UnsupportedOperationException栈迹为清楚起见被截断了):

<!-- the Spring container is starting up... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors

<!-- the DefaultFooService is actually proxied -->
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]

<!-- ... the insertFoo(..) method is now being invoked on the proxy -->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo

<!-- the transactional advice kicks in here... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction

<!-- the insertFoo(..) method from DefaultFooService throws an exception... -->
[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]

<!-- and the transaction is rolled back (by default, RuntimeException instances cause rollback) -->
[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)
<!-- AOP infrastructure stack trace elements removed for clarity -->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)

回滚一个声明式事务

前面一节概述了如何指定声明在程序中的类的事务设置,一般是服务层类。本节描述了如何以简单的声明式的风格来控制事务的回滚。
表明Spring Framework事务框架一个事务工作需要回滚,推荐的方式是在当前执行事务的上下文抛出一个异常。Spring Framework的事务框架代码捕获任何未处理Exception,当它在调用栈冒泡时,并且决定是否标记事务回滚。
在默认的配置中,Spring Framework的事务框架代码标记事务回滚,只有在运行时的未检查异常。也就是说,当抛出的异常时一个RuntimeException实例或子类。(Error实例也,默认情况下,导致回滚)。从事务方法抛出的检查异常在默认配置下不会导致回滚。
可以明确地指定哪些Exception类型标记事务回滚,包括检查异常。下面XML小段演示了如何配置检查的,程序制定的Exception类型的回滚:

<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的架构背道而驰。

为不同的对象配置不同的事务语义

考虑这样的场景,有一堆的服务层对象,想为每个应用一个完全不同的事务配置。可以通过定义单独的<aop:advisor/>元素携带不同的pointcut和advice-ref属性值来实现。

作为对比,首先假设所有的服务层类定义在根为x.y.service的包中。为了使在包(或子包)定义的类实例并且名称以Service结尾的对象有默认的事务配置,可以像下面这么写:

<?xml version="1.0" encoding="UTF-8"?>
<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>

    <!-- these two beans will be transactional... -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>
    <bean id="barService" class="x.y.service.extras.SimpleBarService"/>

    <!-- ... and these two beans won't -->
    <bean id="anotherService" class="org.xyz.SomeService"/> <!-- (not in the right package) -->
    <bean id="barManager" class="x.y.service.SimpleBarManager"/> <!-- (doesn't end in 'Service') -->

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

    <!-- other transaction infrastructure beans such as a PlatformTransactionManager omitted... -->

</beans>

下面例子展示了如何用两个完全不同的事务设置来配置不同的对象:

<?xml version="1.0" encoding="UTF-8"?>
<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>

    <!-- this bean will be transactional (see the 'defaultServiceOperation' pointcut) -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- this bean will also be transactional, but with totally different transactional settings -->
    <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>

    <!-- other transaction infrastructure beans such as a PlatformTransactionManager omitted... -->

</beans>

<tx:advice/>设置

本节概括了各种用<tx:advice/>标签指定的事务配置。默认<tx:advice/>设置是:

  • 传播设置是REQUIRED。
  • 隔离级别是DEFAULT。
  • 事务是读写。
  • 默认事务超时时间是底层事务系统的默认超时时间或没有超时时间如果不支持的话。
  • 任何RuntimeException导致回滚,任何检查异常则不会。

可以改变这些默认设置。下表概括了<tx:method/>标签的各种属性,嵌套在<tx:advice/><tx:attributes/>标签。

表1 <tx:method/>设置

属性必须默认描述
nameYes方法名称
propagationNoREQUIRED
isolationNoDEFAULT
timeoutNo-1
read-onlyNofalse
rollback-forNo
no-rollback-forNo

使用@Transactional

除了基于XML声明式方法来事务配置,也可使用基于注解的方法。在Java源代码直接声明事务语义使得声明离影响代码更近。这没有过分结合的危险,因为要使用事务的代码总是这样子被部署的。

标准的javax.transaction.Transactional注解也被支持,作为Spring注解的替代。请参考JTA1.2文档获取更多信息。

使用@Transactional注释所提供的易用性最好通过一个示例来说明,下面的文本将对此进行解释。考虑下面的类定义:

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

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

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

    void insertFoo(Foo foo) {
        // ...
    }

    void updateFoo(Foo foo) {
        // ...
    }
}

如上所述,该注释在类级别上使用,表示声明类(及其子类)的所有方法的默认值。另外,每个方法都可以得到单独的注释。请注意,类级别的注释不应用于类层次结构上的祖先类;在这种情况下,为了参与子类级别的注释,需要在本地重新声明方法。

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

在XML配置中,tx:annotation-driven/标签提供了类似的便利:

<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<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">

    <!-- this is the service object that we want to make transactional -->
    <bean id="fooService" class="x.y.service.DefaultFooService"/>

    <!-- enable the configuration of transactional behavior based on annotations -->
    <tx:annotation-driven transaction-manager="txManager"/><!-- a PlatformTransactionManager is still required --> 

    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- (this dependency is defined somewhere else) -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- other <bean/> definitions here -->

</beans>

使bean实例具有事务性的行。

如果您想要连接的平台transactionManager的bean名称为transactionManager,那么您可以省略<tx: annotationdriven />标记中的transaction-manager属性。如果要依赖注入的PlatformTransactionManager bean有任何其他名称,就必须使用transaction-manager属性,如前面的示例所示。

方法可见性和@Transactional
在使用代理时,应该只将@Transactional注释应用于具有公共可见性的方法。如果使用@Transactional注释注释受保护的、私有的或包可见的方法,则不会引发错误,但是注释的方法不显示配置的事务设置。如果需要注释非公共方法,可以考虑使用AspectJ(后面会介绍)。

您可以将@Transactional注释应用于接口定义、接口上的方法、类定义或类上的公共方法。然而,仅仅存在@Transactional注释并不足以激活事务行为。@Transactional注释只是一些运行时基础设施可以使用的元数据,这些运行时基础设施支持@ transactional,并且可以使用元数据配置适当的bean和事务行为。在前面的示例中,<tx:annotation-driven/>元素切换到事务行为。

Spring团队建议使用@Transactional注释只注释具体的类(和具体类的方法),而不是注释接口。当然,您可以将@Transactional注释放在接口(或接口方法)上,但这仅在使用基于接口的代理时才有效。Java注释的事实并不意味着继承接口,如果使用基于类的代理(proxy-target-class=" true ")或weaving-based方面(mode=“aspectj”),事务设置不被代理和编织的基础设施识别,并且对象不包装到一个事务代理。

在代理模式(这是缺省模式)中,只拦截通过代理传入的外部方法调用。这意味着自调用(实际上,目标对象中的一个方法调用目标对象的另一个方法)在运行时不会导致实际的事务,即使被调用的方法被标记为@Transactional。另外,代理必须被完全初始化以提供预期的行为,因此您不应该在初始化代码(即@PostConstruct)中依赖于此特性。

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

表2 注解驱动事务设置

XML属性注解属性默认描述
transaction-managerN/A(参见TransactionManagementConfigurer 文档)transactionManager事务管理者名称
modemodeproxy默认只作用到从代理来的方法调用
proxy-target-classproxyTargetClassfalse只作用于proxy模式
orderorderOrdered.LOWEST_PRECEDENCE作用到对象的事务建议顺序

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

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

@EnableTransactionManagement和<tx:annotation-driven/>仅在定义@Transactional的相同应用程序上下文中查找@Transactional。这意味着,如果您将注释驱动的配置放在DispatcherServlet的WebApplicationContext中,它将只检查控制器中的@Transactional bean,而不检查服务中的@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) {
        // ...
    }
}

@transactional设置

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

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

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

表3 @Transactional设置

属性类型描述
valueString事务管理器
propagationenum: Propagation
isolationenum: Isolation
timeoutint(以秒为单位的粒度)
readOnlyboolean
rollbackFor类对象的数组,它必须派生自Throwable
rollbackForClassName类名数组。类必须派生自Throwable
noRollbackFor类对象的数组,它必须派生自Throwable
noRollbackForClassName字符串类名的数组,它必须派生自Throwable

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

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

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

public class TransactionalService {

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

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

下面的清单显示了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>

在本例中,TransactionalService上的两个方法在单独的事务管理器下运行,由订单和帐户限定符区分。如果没有找到特别符合条件的PlatformTransactionManager bean,则仍然使用缺省的<tx:annotation-driven>目标bean名transactionManager。

自定义快捷键的注释

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("order")
public @interface OrderTx {
}

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

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

public class TransactionalService {

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

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

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

事务传播

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

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

理解PROPAGATION_REQUIRED
在这里插入图片描述
PROPAGATION_REQUIRED执行物理事务,如果当前范围内还不存在事务,则在本地执行物理事务,或者参与为更大范围定义的现有“外部”事务。在同一线程中的公共调用堆栈安排中,这是一个很好的默认设置(例如,一个服务facade,它将委托给几个存储库方法,其中所有底层资源都必须参与服务级事务)。

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

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

但是,在内部事务范围设置仅回滚标记的情况下,外部事务没有决定回滚本身,因此回滚(由内部事务范围静默触发)是意外的。这时抛出一个对应的tedrollbackexception。这是预期的行为,因此事务的调用者永远不会被误导,以为提交是在实际没有执行的情况下执行的。因此,如果一个内部事务(外部调用者不知道它的存在)悄悄地将一个事务标记为仅回滚,那么外部调用者仍然调用commit。外部调用者需要接收一个意想不到的drollbackexception,以清楚地表明执行了回滚。

理解PROPAGATION_REQUIRES_NEW

在这里插入图片描述
与PROPAGATION_REQUIRED不同,PROPAGATION_REQUIRES_NEW总是为每个受影响的事务范围使用独立的物理事务,而从不参与外部范围的现有事务。在这种安排中,底层的资源事务是不同的,因此可以独立地提交或回滚,外部事务不受内部事务回滚状态的影响,内部事务的锁在完成后立即释放。这样一个独立的内部事务还可以声明自己的隔离级别、超时和只读设置,而不会继承外部事务的特征。

理解PROPAGATION_NESTED

PROPAGATION_NESTED使用一个具有多个保存点的物理事务,它可以回滚到这些保存点。这样的部分回滚允许内部事务范围触发其范围的回滚,而外部事务能够继续物理事务,尽管已经回滚了一些操作。该设置通常映射到JDBC保存点,因此它只适用于JDBC资源事务。参见Spring的DataSourceTransactionManager。

建议事务操作

假设您希望同时执行事务操作和一些基本的分析建议。如何在<tx:annotation-driven/>的上下文中实现这一点?
当您调用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;
    }
}

通知的排序通过有序接口进行控制。有关通知订购的详细信息,请参阅[通知顺序](https://docs.spring.io/spring/docs/5.2.5.RELEASE/spring-framework-reference/core.html#aop-ataspectj-advice-ordering)

下面的配置创建了一个fooService bean,它按照所需的顺序应用了分析和事务方面:

<?xml version="1.0" encoding="UTF-8"?>
<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"/>

    <!-- this is the aspect -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- execute before the transactional advice (hence the lower order number) -->
        <property name="order" value="1"/>
    </bean>

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

    <aop:config>
            <!-- this advice will execute around the transactional advice -->
            <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声明方法:

<?xml version="1.0" encoding="UTF-8"?>
<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"/>

    <!-- the profiling advice -->
    <bean id="profiler" class="x.y.SimpleProfiler">
        <!-- execute before the transactional advice (hence the lower order number) -->
        <property name="order" value="1"/>
    </bean>

    <aop:config>
        <aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>
        <!-- will execute after the profiling advice (c.f. the order attribute) -->

        <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" order="2"/>
        <!-- order value is higher than the profiling aspect -->

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

    <!-- other <bean/> definitions such as a DataSource and a PlatformTransactionManager here -->

</beans>

前面的配置的结果是一个fooService bean,它按照这个顺序应用了分析和事务方面。如果您希望分析建议在事务建议之后执行,在事务建议之后执行,在事务建议之前执行,那么您可以交换分析方面bean的order属性的值,使其高于事务建议的order值。

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

您还可以通过AspectJ方面在Spring容器之外使用Spring框架的@Transactional支持。为此,首先使用@Transactional注释注释您的类(以及可选的类方法),然后使用org.springframework.transaction.aspectj链接(weave)您的应用程序。在spring-aspects.jar文件中定义的注释transactionaspect。您还必须使用事务管理器配置方面。您可以使用Spring框架的IoC容器来处理依赖注入方面。配置事务管理方面的最简单方法是使用<tx:annotation-driven/>元素,并将mode属性指定为aspectj,如使用@Transactional中所述。因为我们在这里关注的是在Spring容器之外运行的应用程序,所以我们将向您展示如何通过编程实现这一点。

在继续之前,您可能希望分别使用@Transactional和AOP进行阅读。

// 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开发指南),或者使用加载时间编织。在Spring框架中使用AspectJ进行加载时编织,以讨论与AspectJ连接的加载时间编织。

编程式事务管理

Spring框架提供了两种编程式事务管理方法,使用:

  • TransactionTemplate
  • 直接使用PlatformTransactionManager实现

Spring团队通常推荐用于编程式事务管理的TransactionTemplate。第二个方法类似于使用JTA UserTransaction API,尽管异常处理不那么麻烦。

使用TransactionTemplate

TransactionTemplate采用与其他Spring模板相同的方法,如JdbcTemplate。它使用一个回调方法(免费应用程序代码,从需要做模板采集和发布事务资源),并结果在意图驱动的代码中,在这一点上,您的代码只关注您想要做的事情。

作为下面展示的例子,使用TransactionTemplate绝对会对Spring的事务框架和api进行结合。编程式事务管理是否适合您的开发需求,这是您必须做的决定。

必须在事务上下文中执行并显式使用TransactionTemplate的应用程序代码类似于下面的示例。作为一个应用程序开发人员,您可以编写一个TransactionCallback实现(通常表示为一个匿名内部类),其中包含您需要在事务上下文中执行的代码。然后,您可以将自定义TransactionCallback的一个实例传递给TransactionTemplate上公开的execute(…)方法。下面的例子演示了如何做到这一点:

public class SimpleService implements Service {

    // single TransactionTemplate shared amongst all methods in this instance
    private final TransactionTemplate transactionTemplate;

    // use constructor-injection to supply the PlatformTransactionManager
    public SimpleService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);
    }

    public Object someServiceMethod() {
        return transactionTemplate.execute(new TransactionCallback() {
            // the code in this method executes in a transactional context
            public Object doInTransaction(TransactionStatus status) {
                updateOperation1();
                return resultOfUpdateOperation2();
            }
        });
    }
}

如果没有返回值,可以使用方便的TransactionCallbackWithoutResult类和一个匿名类,如下所示:

transactionTemplate.execute(new TransactionCallbackWithoutResult() {
    protected void doInTransactionWithoutResult(TransactionStatus status) {
        updateOperation1();
        updateOperation2();
    }
});

回调中的代码可以通过在提供的TransactionStatus对象上调用setRollbackOnly()方法来滚回事务,如下:

transactionTemplate.execute(new TransactionCallbackWithoutResult() {

    protected void doInTransactionWithoutResult(TransactionStatus status) {
        try {
            updateOperation1();
            updateOperation2();
        } catch (SomeBusinessException ex) {
            status.setRollbackOnly();
        }
    }
});

指定事务设置

您可以在TransactionTemplate上以编程方式或在配置中指定事务设置(例如传播模式、隔离级别、超时等)。默认情况下,TransactionTemplate实例具有默认的事务设置。下面的示例显示了对特定TransactionTemplate的事务设置的程序化自定义:

public class SimpleService implements Service {

    private final TransactionTemplate transactionTemplate;

    public SimpleService(PlatformTransactionManager transactionManager) {
        this.transactionTemplate = new TransactionTemplate(transactionManager);

        // the transaction settings can be set here explicitly if so desired
        this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
        this.transactionTemplate.setTimeout(30); // 30 seconds
        // and so forth...
    }
}

下面的例子通过使用Spring XML配置定义了带有一些自定义事务设置的TransactionTemplate:

<bean id="sharedTransactionTemplate"
        class="org.springframework.transaction.support.TransactionTemplate">
    <property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
    <property name="timeout" value="30"/>
</bean>

然后,您可以将sharedTransactionTemplate注入到所需的许多服务中。

最后,TransactionTemplate类的实例是线程安全的,在这种情况下,实例不维护任何会话状态。然而,TransactionTemplate实例维护配置状态。因此,虽然许多类可能共享一个TransactionTemplate实例,但是如果一个类需要使用具有不同设置的TransactionTemplate(例如,不同的隔离级别),则需要创建两个不同的TransactionTemplate实例。

使用PlatformTransactionManager

您还可以使用org.springframe.transaction.PlatformTransactionManager直接管理您的事务。为此,通过一个bean引用将您使用的PlatformTransactionManager的实现传递给bean。然后,通过使用TransactionDefinition和TransactionStatus对象,您可以启动事务、回滚和提交。下面的例子演示了如何做到这一点:

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can be done only programmatically
def.setName("SomeTxName");
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);

TransactionStatus status = txManager.getTransaction(def);
try {
    // execute your business logic here
}
catch (MyException ex) {
    txManager.rollback(status);
    throw ex;
}
txManager.commit(status);

在编程式和声明式事务管理之间进行选择

只有在有少量的事务操作时,编程式事务管理通常是一个好主意。例如,如果您有一个web应用程序,只需要对某些更新操作进行事务,您可能不想使用Spring或任何其他技术来设置事务代理。在这种情况下,使用TransactionTemplate可能是一个很好的方法。能够显式地设置事务名称也是可以通过使用编程方法进行事务管理的方法。

另一方面,如果您的应用程序有许多事务操作,那么声明性事务管理通常是值得的。它将事务管理置于业务逻辑之外,并且易于配置。当使用Spring框架而不是EJB CMT时,声明性事务管理的配置成本将大大降低。

Transaction-bound事件

从Spring 4.2开始,事件的侦听器可以绑定到事务的某个阶段。典型的例子是在事务成功完成时处理事件。这样,当当前事务的结果实际上对侦听器很重要时,就可以更灵活地使用事件。

您可以使用@EventListener注释注册一个常规的事件监听器。如果需要将其绑定到事务,请使用@TransactionalEventListener。当您这样做时,默认情况下侦听器被绑定到事务的提交阶段。

下一个例子展示了这个概念。假设组件发布了一个订单创建的事件,并且我们希望定义一个侦听器,该侦听器应该只在发布该事件的事务成功提交后才处理该事件。下面的例子设置了这样一个事件监听器:

@Component
public class MyComponent {

    @TransactionalEventListener
    public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
        // ...
    }
}

@TransactionalEventListener注释公开了一个phase属性,该属性允许您自定义应该将侦听器绑定到的事务的阶段。有效的阶段是BEFORE_COMMIT、AFTER_COMMIT(默认)、AFTER_ROLLBACK和AFTER_COMPLETION,它们聚合事务完成(无论是提交还是回滚)。

如果没有事务运行,则不会调用侦听器,因为我们不能尊重所需的语义。但是,您可以通过将注释的返回执行属性设置为true来覆盖该行为。

应用服务器特定的集成

Spring的事务抽象通常是应用服务器不可知论者。此外,Spring的JtaTransactionManager类(可以选择执行JTA UserTransaction和TransactionManager对象的JNDI查找)自动检测后一种对象的位置,而应用服务器则不同。访问JTA TransactionManager允许增强的事务语义——特别是支持事务暂停。详见JtaTransactionManager javadoc。

Spring的JtaTransactionManager是在Java EE应用服务器上运行的标准选择,并在所有公共服务器上工作。高级功能,如事务暂停,在许多服务器上(包括GlassFish、JBoss和Geronimo)都在没有任何特殊配置的情况下工作。然而,为了完全支持的事务暂停和进一步的高级集成,Spring包括WebLogic服务器和WebSphere的特殊适配器。下面将讨论这些适配器。

对于标准场景,包括WebLogic Server和WebSphere,考虑使用方便的< tx:jta-transaction-manager / >配置元素。在配置时,该元素会自动检测底层服务器,并选择平台上可用的最佳事务管理器。这意味着您不需要显式地配置服务器特定的适配器类(如下部分所讨论的)。相反,它们是自动选择的,标准的JtaTransactionManager是默认的后备。

IBM WebSphere

在WebSphere 6.1.0.9及以上版本中,推荐使用的Spring JTA事务管理器是WebSphereUowTransactionManager。这个特殊适配器使用IBM的UOWManager API,该API在WebSphere Application Server 6.1.0.9及更高版本中可用。有了这个适配器,IBM正式支持spring驱动的事务挂起(由PROPAGATION_REQUIRES_NEW启动的挂起和恢复)。

Oracle WebLogic Server

在WebLogic服务器中,您通常使用WebLogicJtaTransactionManager而不是stock JtaTransactionManager类。这个特殊的weblogic特定的JtaTransactionManager的子类支持Spring在weblogor管理的事务环境中的事务定义的全部能力,超越了标准的JTA语义。特性包括事务名称、每个事务隔离级别,以及所有情况下的事务的适当恢复。

解决常见问题的方法

本节描述了一些常见问题的解决方案。

为特定数据源使用错误的事务管理器

根据您对事务技术和需求的选择,使用正确的PlatformTransactionManager实现。如果使用得当,Spring框架仅仅提供了一个简单且可移植的抽象。如果您使用全局事务,您必须使用用于所有事务操作的org.springframework.transaction.jta.JtaTransactionManager类(或其特定于应用程序服务器的子类)。否则,事务基础结构将尝试在诸如容器数据源实例之类的资源上执行本地事务。这样的本地事务没有意义,好的应用程序服务器会将它们视为错误。

进一步的资源

有关Spring框架的事务支持的更多信息,请参见:

  • 在Spring的分布式事务中,带XA和不带XA是一个JavaWorld的演示,Spring的David Syer引导您通过7种模式来在Spring应用程序中进行分布式事务,其中3种是XA和4种不是的。
  • Java事务设计策略InfoQ的一本书,它为Java中的事务提供了一个良好的描述。它还包括了如何配置和使用Spring框架和EJB3的事务的并行示例。

DAO支持

使用JDBC数据访问

对象关系映射(ORM)数据访问

使用对象XML映射器组织XML

附录

spring-framework-5.0.2.release-中文注释版是Spring Framework的一个版本,它是一个Java开发框架,用于构建企业级应用程序。 Spring Framework被设计为一个轻量级的、模块化的开发框架,它提供了一系列的功能和组件,使开发人员可以更加方便地构建可扩展和可维护的应用程序。这个版本中的中文注释提供了对框架内部工作原理和代码片段的解释,使开发人员可以更加容易地理解和使用Spring框架。 Spring Framework的核心特点包括依赖注入(DI)和面向切面编程(AOP)。依赖注入是指通过配置文件或注释方式将对象的依赖关系注入到目标对象中,降低了对象之间的耦合度。面向切面编程则是一种编程范式,用于处理与业务逻辑无关的横切关注点,如日志、事务等。 除了依赖注入和面向切面编程外,Spring Framework还提供了许多其他功能,如企业级服务(如事务管理、远程访问和消息传递)、Web应用程序开发(如MVC框架和REST服务)、数据访问(如对数据库的支持)和测试支持等。通过这些功能,开发人员可以更高效地开发和管理应用程序。 总的来说,spring-framework-5.0.2.release-中文注释版是一个功能强大且易于使用的框架,它在企业级应用程序开发中发挥着重要作用。通过使用这个版本,开发人员可以更容易地理解和使用Spring框架,并构建出高质量和可扩展的应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值