• [ ] > # Data Access

参考文档的这一部分涉及数据访问以及数据访问层与业务或服务层之间的交互。

Spring的全面的事务管理支持已经被详细介绍了,接下来是对Spring框架集成的各种数据访问框架和技术的全面介绍。

1.事务管理

1.1. Spring框架事务管理介绍

全面的事务支持是使用Spring框架的最有说服力的理由之一。 Spring框架为事务管理提供了一致的抽象,提供以下好处:

  • 跨越不同事务API(如Java事务API(JTA),JDBC,Hibernate和Java持久性API(JPA))的一致编程模型。
  • 支持声明式事务管理。
  • 用于编程式事务管理的简单API比复杂事务API(如JTA)要简单。
  • 与Spring的数据访问抽象极佳整合。

以下部分描述了Spring框架的事务增值和技术。 (本章还包括讨论最佳实践,应用程序服务器集成和常见问题的解决方案。)

  • Spring框架事务支持模型的优点描述了为什么要使用Spring框架的事务抽象而不是EJB容器管理事务(CMT),或者选择通过Hibernate等专有API驱动本地事务。
  • 了解Spring Framework事务抽象概述了核心类,并介绍了如何从各种来源配置和获取DataSource实例。
  • 使资源与事务同步描述应用程序代码如何确保正确地创建,重用和清理资源。
  • 声明式事务管理描述了对声明式事务管理的支持。
  • 编程式事务管理包括对程序化(即明确编码的)事务管理的支持。
  • 事务绑定事件描述了如何在事务中使用应用程序事件。

1.2. Spring框架的事务支持模型的优点

传统上,Java EE开发人员对事务管理有两种选择:全局或本地事务,两者都有很大的局限性。 接下来的两节将回顾全局和本地事务管理,然后讨论Spring框架的事务管理支持如何解决全局和本地事务模型的局限性。

1.2.1. 全局事务

全局事务使您能够处理多个事务资源,通常是关系数据库和消息队列。 应用程序服务器通过JTA管理全局事务,这是一个繁琐的API使用(部分归因于它的异常模型)。 此外,JTA UserTransaction通常需要来自JNDI,这意味着您还需要使用JNDI才能使用JTA。 显然,使用全局事务将限制应用程序代码的任何潜在的重用,因为JTA通常只在应用程序服务器环境中可用。

以前,使用全局事务的首选方式是通过EJB CMT(容器管理事务):CMT是一种声明式事务管理(区别于程序化事务管理)。 EJB CMT消除了与事务相关的JNDI查找的需要,当然,使用EJB本身也需要使用JNDI。 它消除了大部分但不是全部需要编写Java代码来控制事务。 重大的缺点是CMT与JTA和应用程序服务器环境相关联。 此外,只有在选择在EJB中实现业务逻辑时,或者至少在事务性EJB Facade后面才可用。 一般来说,EJB的负面影响非常大,所以这不是一个有吸引力的命题,特别是在面向声明式事务管理的引人注目的选择方面。

1.2.2. 本地事务

本地事务是与资源相关的,比如与JDBC连接相关的事务。 本地规则可能更容易使用,但是有多个事务资源。 在全局JTA事务中运行。 由于应用服务器不涉及事务管理,因此无法保证跨多个资源的正确性。 (值得注意的是,大多数应用程序使用单个事务资源。)另一个缺点是其他事务对编程模型是侵入式的。

1.2.3. Spring框架的一致编程模型

Spring解决了全局和本地事务的缺点。 它使应用程序开发人员能够在任何环境中使用一致的编程模型。 您只需编写一次代码,并且可以从不同环境中的不同事务管理策略中受益。 Spring Framework提供了声明式和编程式事务管理。 大多数用户更喜欢声明式事务管理,这在大多数情况下是推荐的。

通过编程式事务管理,开发人员可以使用Spring Framework事务抽象,它可以在任何基础事务基础结构上运行。 使用首选的声明模型,开发人员通常会写很少或没有与事务管理相关的代码,因此不依赖于Spring Framework事务API或任何其他事务API。

你需要一个应用程序服务器进行事务管理吗?

Spring框架的事务管理支持改变了传统的规则,即企业Java应用程序何时需要应用程序服务器。

特别是,您不需要应用程序服务器只是通过EJB进行声明式事务。 事实上,即使你的应用程序服务器具有强大的JTA功能,你也可以决定Spring框架的声明性事务提供比EJB CMT更多的功能和更高效的编程模型。

通常情况下,只有当您的应用程序需要处理跨多个资源的事务时,您才需要应用程序服务器的JTA功能,这对于许多应用程序来说不是必需的 许多高端应用程序使用单个高度可伸缩的数据库(如Oracle RAC)。 独立事务管理器,如Atomikos Transactions和JOTM是其他选项。 当然,您可能还需要其他应用程序服务器功能,例如Java消息服务(JMS)和Java EE连接器体系结构(JCA)。

Spring框架可以让您选择何时将应用程序扩展到满载的应用程序服务器。 过去,使用EJB CMT或JTA的唯一替代方法是用本地事务(如JDBC连接上的事务)编写代码,如果需要代码在全局容器管理事务中运行,则会面临很大的重复劳动。 使用Spring框架,只需要修改配置文件中的部分bean定义,而不是代码。

1.3. 理解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)一样定义。 单就这个好处而言,即使在使用JTA的时候,Spring Framework事务也是一个有价值的抽象。 事务代码可以比直接使用JTA更容易测试。

再一次按照Spring的理念,任何PlatformTransactionManager接口的方法都可以抛出的TransactionException没有被检查(也就是说,它扩展了java.lang.RuntimeException类)。 事务基础设施故障几乎总是致命的。 在应用程序代码实际上可以从事务故障中恢复的罕见情况下,应用程序开发人员仍然可以选择捕获和处理TransactionException。 重点是开发商不必被迫这样做。

getTransaction(..)方法根据TransactionDefinition参数返回一个TransactionStatus对象。 返回的TransactionStatus可能代表一个新的事务,或者如果当前调用栈中存在匹配的事务,则可以代表一个已经存在的事务。 后一种情况的含义是,与Java EE事务上下文一样,TransactionStatus与一个执行线程相关联。

TransactionDefinition接口指定:

  • 隔离:这种事务与其他事务的隔离程度。 例如,这个事务可以看到来自其他事务的未提交的写入?

  • 传播:通常,在事务范围内执行的所有代码都将在该事务中运行。 但是,如果在事务上下文已经存在的情况下执行事务方法,则可以选择指定行为。 例如,代码可以继续在现有的事务中运行(常见的情况)。 或者现有事务可以被暂停并创建新的事务。 Spring提供了EJB CMT所熟悉的所有事务传播选项。 要了解Spring中事务传播的语义,请参阅事务传播。

  • 超时:在超时之前该事务运行多久,并由基础事务基础结构自动回滚。

  • 只读状态:当您的代码读取但不修改数据时,可以使用只读事务。 在某些情况下,只读事务可以是一个有用的优化,例如当您使用Hibernate时。

这些设置反映了标准的事务概念。 如有必要,请参阅讨论事务隔离级别和其他核心事务概念的资源。 理解这些概念对于使用Spring框架或任何事务管理解决方案都是至关重要的。

TransactionStatus接口为事务代码提供了一种简单的方法来控制事务执行和查询事务状态。 这些概念应该很熟悉,因为它们对于所有的事务处理API都是通用的:

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数据源

<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 bean定义将会有一个对DataSource定义的引用。 它看起来像这样:

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

如果您在Java EE容器中使用JTA,那么您将通过JNDI获得的容器DataSource与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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/jee
        http://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 bean的上述定义使用jee命名空间中的<jndi-lookup />标签。 有关更多信息,请参阅JEE架构。

您也可以轻松地使用Hibernate本地事务,如以下示例所示。 在这种情况下,您需要定义一个Hibernate LocalSessionFactoryBean,您的应用程序代码将用它来获取Hibernate Session实例。

DataSource bean定义将类似于前面显示的本地JDBC示例,因此在以下示例中不会显示。

如果由任何非JTA事务管理器使用的DataSource通过JNDI查找并由Java EE容器管理,那么它应该是非事务性的,因为Spring Framework(而不是Java EE容器)将管理事务。

这种情况下的txManager bean是HibernateTransactionManager类型的。 与DataSourceTransactionManager需要对DataSource的引用相同,HibernateTransactionManager需要对SessionFactory的引用。

<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事务,那么您应该简单地使用与以前JTA JDBC示例相同的JtaTransactionManager。

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

如果您使用JTA,那么无论您使用什么数据访问技术,无论是JDBC,Hibernate JPA还是任何其他支持的技术,您的事务管理器定义都将保持不变。 这是由于JTA事务是全局事务,它可以征用任何事务资源。

在所有这些情况下,应用程序代码不需要改变。 您可以通过更改配置来更改事务的管理方式,即使这种更改意味着从本地事务转移到全局事务,反之亦然。

1.4. 资源与事务同步

现在应该清楚如何创建不同的事务管理器,以及它们如何链接到需要同步到事务的相关资源(例如DataSourceTransactionManager到JDBC数据源,HibernateTransactionManager到Hibernate SessionFactory等等)。 本节介绍应用程序代码如何直接或间接使用持久化API(如JDBC,Hibernate或JPA)确保正确创建,重用和清理这些资源。 本节还讨论了如何通过相关PlatformTransactionManager触发(可选)事务同步。

1.4.1.高级同步方法

首选的方法是使用Spring最高级别的基于模板的持久集成API,或者使用本地ORM API和事务感知工厂bean或代理来管理本地资源工厂。 这些事务感知型解决方案在内部处理资源创建和重用,清理,资源的可选事务同步以及异常映射。 因此,用户数据访问代码不必解决这些任务,但可以纯粹专注于非模板化的持久性逻辑。 通常,使用本机ORM API或使用JdbcTemplate采取模板方法进行JDBC访问。 这些解决方案在本参考文档的后续章节中详细介绍。

1.4.2. 低级同步方法

诸如DataSourceUtils(用于JDBC),EntityManagerFactoryUtils(用于JPA),SessionFactoryUtils(用于Hibernate)等的类存在于较低级别。 当您希望应用程序代码直接处理本地持久化API的资源类型时,您可以使用这些类来确保获得正确的Spring Framework管理的实例,事务(可选)同步,并且进程中发生的异常是 正确地映射到一致的API。

例如,对于JDBC而言,不是调用DataSource的getConnection()方法的传统JDBC方法,而是使用Spring的org.springframework.jdbc.datasource.DataSourceUtils类,如下所示:

Connection conn = DataSourceUtils.getConnection(dataSource);

如果现有的事务已经有一个同步(链接)到它的连接,则返回该实例。 否则,方法调用将触发创建一个新的连接,该连接(可选)与任何现有事务同步,并可用于随后在同一事务中重新使用。 如前所述,任何SQLException都被封装在Spring框架CannotGetJdbcConnectionException中,这是Spring框架未经检查的DataAccessExceptions的层次结构之一。 这种方法为您提供了比从SQLException容易获得的信息更多的信息,并确保跨数据库的可移植性,即使是跨不同的持久性技术。

这种方法也可以在没有Spring事务管理的情况下工作(事务同步是可选的),因此无论您是否使用Spring进行事务管理,都可以使用它。

当然,一旦你使用了Spring的JDBC支持,JPA支持或者Hibernate支持,你通常不会使用DataSourceUtils或其他的帮助类,因为通过Spring抽象,比直接使用相关的API更快乐。 例如,如果使用Spring JdbcTemplate或jdbc.object包来简化JDBC的使用,则在后台执行正确的连接检索,而不需要编写任何特殊代码。

1.4.3. TransactionAwareDataSourceProxy

在最低级别存在TransactionAwareDataSourceProxy类。 这是一个目标DataSource的代理,它封装了目标DataSource以增加对Spring管理的事务的了解。 在这方面,它类似于由Java EE服务器提供的事务性JNDI数据源。

除非现有的代码必须被调用并通过一个标准的JDBC DataSource接口实现,否则几乎不需要使用这个类。 在这种情况下,这个代码有可能是可用的,但参与Spring管理事务。 最好使用上面提到的更高级别的抽象来编写新的代码。

1.5. 声明式事务管理

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

Spring框架的声明式事务管理是通过Spring面向方面编程(AOP)实现的,尽管由于事务方面的代码随Spring Framework的发行版一起提供,并且可能以样板模式使用,AOP概念通常不需要被理解 有效使用这段代码。

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

  • 与JTA绑定的EJB CMT不同,Spring框架的声明式事务管理适用于任何环境。 它可以使用JDBC,JPA或Hibernate通过简单地调整配置文件来处理JTA事务或本地事务。

  • 您可以将Spring Framework声明式事务管理应用于任何类,而不仅仅是诸如EJB的特殊类。

  • Spring框架提供了声明式的回滚规则,这是一个没有EJB相同的特性。 提供了对回滚规则的编程和声明性支持。

  • Spring框架使您能够通过使用AOP来自定义事务行为。 例如,您可以在事务回滚的情况下插入自定义行为。 您还可以添加任意的建议,以及事务建议。 使用EJB CMT,除了使用setRollbackOnly()之外,不能影响容器的事务管理。

  • Spring框架不支持跨远程调用传播事务上下文,高端应用程序服务器也如此。 如果您需要此功能,我们建议您使用EJB。 但是,在使用这种功能之前要仔细考虑,因为通常情况下,不希望事务跨越远程调用。

TransactionProxyFactoryBean在哪里?

Spring 2.0及更高版本的声明式事务配置与以前的Spring版本有很大不同。 主要区别在于不再需要配置TransactionProxyFactoryBean bean。

Spring 2.0之前的配置风格仍然是100%有效的配置; 把新的<tx:tags />想象成简单地为你定义TransactionProxyFactoryBean bean。

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

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

1.5.1. Understanding the Spring Framework’s declarative transaction implementation

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

仅仅通过@Transactional注解告诉你注解类,将@EnableTransactionManagement添加到你的配置,然后期望你明白它是如何工作是不够的。 本节将解释在发生事务相关问题时Spring Framework的声明式事务基础架构的内部工作原理。

关于Spring框架的声明式事务支持最重要的概念是通过AOP代理来支持这种支持,并且事务性的建议是由元数据(目前是基于XML或基于注解的)来驱动的。 AOP与事务性元数据的结合产生了AOP代理,该代理使用TransactionInterceptor和适当的PlatformTransactionManager实现来驱动方法调用周围的事务。

AOP部分介绍了Spring AOP。

从概念上讲,调用事务代理的方法如下所示...

1.5.2. 声明式事务实现的示例

考虑以下接口及其附带的实现。 本示例使用Foo和Bar类作为占位符,以便您可以专注于事务使用,而不必关注特定的域模型。 就这个例子而言,DefaultFooService类在每个实现的方法的主体中抛出UnsupportedOperationException实例是好事实; 它允许您查看创建的事务,然后回滚以响应UnsupportedOperationException实例。

// 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);

}
// an implementation of the above interface

package x.y.service;

public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        throw new UnsupportedOperationException();
    }

    public Foo getFoo(String fooName, String barName) {
        throw new UnsupportedOperationException();
    }

    public void insertFoo(Foo foo) {
        throw new UnsupportedOperationException();
    }

    public void updateFoo(Foo foo) {
        throw new UnsupportedOperationException();
    }

}

假设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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://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 bean,事务性的。 要应用的事务语义被封装在<tx:advice />定义中。 <tx:advice />定义读为“...所有以'get'开头的方法都是在只读事务的上下文中执行的,而所有其他方法都是使用默认的事务语义执行的。 将<tx:advice />标记的transaction-manager属性设置为将要驱动事务的PlatformTransactionManager bean的名称,在这种情况下为txManager bean。

如果您要连接的PlatformTransactionManager的bean名称具有名称transactionManager,则可以省略事务性建议(<tx:advice />)中的transaction-manager属性。 如果您要连接的PlatformTransactionManager bean具有其他名称,则必须显式使用transaction-manager属性,如上例所示。

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

在<aop:pointcut />元素中定义的表达式是一个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", 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)

1.5.3. 回滚声明式事务

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

向Spring框架的事务基础架构表明事务的工作将被回滚的推荐方法是从事务上下文中正在执行的代码中引发一个异常。 Spring框架的事务基础架构代码会捕获任何未处理的异常,因为它会唤起调用堆栈,并确定是否标记事务以进行回滚。

在其默认配置中,Spring Framework的事务基础架构代码仅在运行时标记为回滚事务,未经检查的异常; 也就是说,抛出的异常是RuntimeException的一个实例或子类。 (错误也将 - 默认 - 导致回滚)。 检查从事务性方法抛出的异常不会导致在默认配置中回滚。

您可以精确地配置哪些Exception类型标记回滚事务,包括已检查的异常。 以下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框架的事务基础结构,即使面对未处理的InstrumentNotFoundException,也要提交伴随事务。

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

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

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

您也可以以编程方式指示所需的回滚。 虽然非常简单,但这个过程非常有创意,并且将代码紧密结合到Spring框架的事务基础架构上:

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

如果可能,强烈建议您使用声明式方法来回滚。 如果您绝对需要,可以使用程序化回滚,但是它的使用会在实现一个干净的基于POJO的体系结构时发生。

1.5.4. 为不同的bean配置不同的事务语义

考虑一下你有多个服务层对象的场景,并且你想对它们中的每一个应用完全不同的事务配置。 您可以通过定义具有不同切入点和advice-ref属性值的不同<aop:advisor />元素来执行此操作。

作为比较的一点,首先假定所有的服务层类都是在一个根x.y.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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://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>

以下示例显示如何使用完全不同的事务设置配置两个不同的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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://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>

1.5.5. <tx:advice/> 设置

本部分总结了可以使用<tx:advice />标签指定的各种事务设置。 默认的<tx:advice />设置是:

  • (Propagation)传播设置是必需的(REQUIRED)。
  • 隔离级别是DEFAULT。
  • 事务是读/写的。
  • 事务超时默认为基础事务系统的默认超时,如果超时不受支持,则默认为无。
  • 任何RuntimeException触发回滚,并且任何检查的异常不会。
  • 您可以更改这些默认设置; 嵌套在<tx:advice />和<tx:attributes />标签中的<tx:method />标签的各种属性总结如下:

表1.<tx:method> 设置

属性 是否必须? 默认 描述
Attribute Required? Default Description
name Yes 要与事务属性相关联的方法名称。 通配符(*)字符可用于将相同的事务属性设置与多种方法相关联; 例如,get *,handle *,on * Event等等。
propagation No REQUIRED 事务传播行为。
isolation No DEFAULT 事务隔离级别
timeout No -1 事物超时值(以秒为单位)。
read-only No false 当前事务是否只读?
rollback-for No 触发回滚的异常;逗号分隔。 例如,com.foo.MyBusinessException,ServletException。
no-rollback-for No 不触发回滚的异常(s);逗号分隔。 例如,com.foo.MyBusinessException,ServletException。

1.5.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 {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo foo);
}

当上面的POJO在Spring IoC容器中定义为一个bean时,只需添加一行XML配置就可以使bean实例成为事务性的:

<!-- 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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://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>

如果您要连接的PlatformTransactionManager的Bean名称具有名称transactionManager,则可以省略<tx:annotation-driven />标记中的transaction-manager属性。 如果要依赖注入的PlatformTransactionManager bean具有任何其他名称,则必须显式使用transaction-manager属性,如上例所示。


如果您使用的是基于Java的配置,则@EnableTransactionManagement批注提供等效的支持。 只需将注解添加到@Configuration类即可。 查看javadocs的全部细节。


方法可见性和@Transactional

在使用代理时,您应该将@Transactional注解仅应用于具有公开可见性的方法。 如果使用@Transactional注解标注protected,private或package-visible方法,则不会引发错误,但注解的方法不会显示已配置的事务设置。 如果您需要注解非公共方法,请考虑使用AspectJ(请参见下文)。

您可以将@Transactional注解放置在类的接口定义,接口上的方法,类定义或公共方法之前。 然而,仅仅存在@Transactional注解不足以激活事务行为。 @Transactional注解仅仅是一些元数据,可以被一些具有@ Transactional-aware的运行时基础结构使用,并且可以使用元数据来配置具有事务行为的合适的Bean。 在前面的示例中,<tx:annotation-driven />元素用于切换事务行为。

Spring建议您只使用@Transactional注解来注解具体类(以及具体类的方法),而不是注解接口。 您当然可以将@Transactional注解放在一个接口(或一个接口方法)上,但是这个工作只有在您使用基于接口的代理时才会如您所愿。 Java注解没有从接口继承的事实意味着,如果您使用的是基于类的代理(proxy-target-class =“true”)或基于交织的方面(mode =“aspectj”),那么事务设置是 没有被代理和编织基础架构认可,并且该对象不会被封装在一个事务代理中,这将是非常糟糕的。


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

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

表2.注解驱动的事务设置

XML Attribute Annotation Attribute Default Description
transaction-manager N/A (SeeTransactionManagementConfigurerjavadocs) transactionManager 要使用的事务管理器的名称。 只有在事务管理器的名称不是transactionManager的情况下才需要,如上例所示。
mode mode proxy 默认模式“代理”处理带注解的bean,使用Spring的AOP框架进行代理(遵循代理语义,如上所述,仅适用于通过代理进入的方法调用)。 替代模式“aspectj”用Spring的AspectJ事务方面编织受影响的类,修改目标类字节代码以应用于任何类型的方法调用。 AspectJ编织要求classpath中的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接口的代理。 (请参阅[aop-proxying]了解不同代理类型的讨论。)


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

评估方法的事务设置时,派生最多的位置优先。 在下面的例子中,DefaultFooService类在类级别注解了只读事务的设置,但同一个类中updateFoo(Foo)方法的@Transactional注解优先于定义的事务设置 在班级一级。

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

    public Foo getFoo(String fooName) {
        // do something
    }

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

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

  • 传播设置是PROPAGATION_REQUIRED。
  • 事务是读/写(read/write)的
  • 事务超时默认为基础事务系统的默认超时,如果超时不受支持,则默认为无。
  • 任何RuntimeException触发回滚,并且任何检查的异常不会。

这些默认设置可以改变; 下表汇总了@Transactional注解的各种属性:

表3.@Transactional设置

Property Type Description
value String 指定要使用的事务管理器的可选限定符。
propagation enum:Propagation 可选的传播设置。
isolation enum:Isolation 可选的隔离级别。
readOnly boolean Read/write vs. read-only 事务
timeout int (in seconds granularity) 事务超时
rollbackFor Array ofClassobjects, which must be derived fromThrowable. 可选的回滚的异常类的可选数组。
rollbackForClassName Array of class names. Classes must be derived fromThrowable. 可选的回滚的异常类名称的可选数组。
noRollbackFor Array ofClassobjects, which must be derived fromThrowable. 可选的不需要回滚的异常类数组。
noRollbackForClassName Array ofStringclass names, which must be derived fromThrowable. 可选的数组名称的异常类,必须回滚。

目前,您不能显式控制事务名称,其中'name'表示将在事务监视器(如果适用)(如WebLogic的事务监视器)中显示的事务名称以及日志记录输出。 对于声明式事务,事务名称始终是完全合格的类名+“。” +事务建议的类的方法名称。 例如,如果BusinessService类的handlePayment(..)方法启动了一个事务,则事务的名称将是:com.foo.BusinessService.handlePayment。

多事务管理器与 @Transactional

大多数Spring应用程序只需要一个事务管理器,但可能会出现在单个应用程序中需要多个独立事务管理器的情况。 @Transactional注解的value属性可以用来指定要使用的PlatformTransactionManager的标识。 这可以是事务管理器bean的bean名称或限定符值。 例如,使用限定符符号表示以下Java代码

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上的两个方法将在单独的事务管理器下运行,并由“order”和“account”限定符进行区分。 如果没有找到专门定义的PlatformTransactionManager bean,那么仍然会使用默认的<tx:annotation-driven>目标bean名称transactionManager。

自定义快捷注解

如果您发现您在许多不同的方法上重复使用@Transactional的相同属性,那么Spring的元注解支持允许您为特定用例定义自定义快捷方式注解。 例如,定义以下注解

@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() { ... }
}

这里我们使用了语法来定义事务管理器限定符,但也可能包含传播行为,回滚规则,超时等。

1.5.7. 事物的传播特性

本节介绍Spring中事务传播的一些语义。 请注意,本节不是事务传播的介绍。 而是详细介绍了Spring中事务传播的一些语义。

在Spring管理的事务中,要注意物理和逻辑事务之间的区别,以及传播设置如何应用于这个区别。

Required

PROPAGATION_REQUIRED

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

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

RequiresNew

PROPAGATION_REQUIRES_NEW

与PROPAGATION_REQUIRED相比,PROPAGATION_REQUIRES_NEW对每个受影响的事务范围使用完全独立的事务。 在这种情况下,底层的物理事务是不同的,因此可以独立地提交或回滚,外部事务不受内部事务的回滚状态的影响。

Nested

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

1.5.8. Advising transactional operations

假设您要执行事务性和一些基本的性能分析建议。 你如何在<tx:annotation-driven />的情况下实现这一点?

当您调用updateFoo(Foo)方法时,您需要查看以下操作:

  • Configured profiling aspect starts up.

  • Transactional advice executes.

  • Method on the advised object executes.

  • Transaction commits.

  • Profiling aspect reports exact duration of the whole transactional method invocation.

他的章节并不关心如何解释AOP的细节(除了适用于事务)。 有关以下AOP配置和AOP的详细介绍,请参阅AOP。

这里是上面讨论的一个简单的剖析方面的代码。 建议的顺序是通过Ordered界面控制的。 有关咨询订购的完整信息,请参阅咨询订购。。

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;
    }
}
<?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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://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>

上述配置的结果是一个fooService bean,它具有按所需顺序应用于分析和事务处理的方面。 您可以用类似的方式配置任何数量的其他方面。

下面的例子和上面的例子一样,但是使用纯粹的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
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://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的订单属性的值,使其高于事务性建议的顺序 值。

您以类似的方式配置其他(aspects)方面

1.5.9. 使用基于AspectJ的@Transactional

通过AspectJ方面,还可以在Spring容器之外使用Spring框架的@Transactional支持。 为此,首先使用@Transactional注解注解您的类(以及您的类的方法),然后将您的应用程序与spring-aspects中定义的org.springframework.transaction.aspectj.AnnotationTransactionAspect链接(编织)。 jar文件。 该方面还必须配置一个事务管理器。 您当然可以使用Spring框架的IoC容器来处理依赖注入的方面。 配置事务管理方面的最简单方法是使用<tx:annotation-driven />元素,并按照使用@Transactional中的描述将aspect属性指定为aspectj。 因为我们将重点放在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进行加载时织入的讨论。

1.6. 编程式事务管理

Spring Framework提供了两种程序化事务管理方式:

  • 使用 TransactionTemplate.
  • 直接使用PlatformTransactionManager实现。

Spring团队通常建议使用TransactionTemplate进行编程事务管理。 第二种方法类似于使用JTA UserTransaction API,虽然异常处理不太麻烦。

1.6.1. 使用 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) {
        Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
        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 (SomeBusinessExeption ex) {
            status.setRollbackOnly();
        }
    }
});
指定事务设置

您可以在TransactionTemplate上以编程方式或在配置中指定事务设置,例如传播模式,隔离级别,超时等等。 TransactionTemplate实例默认具有默认的事务设置。 以下示例显示了针对特定TransactionTemplate的事务设置的编程定制:

public class SimpleService implements Service {

    private final TransactionTemplate transactionTemplate;

    public SimpleService(PlatformTransactionManager transactionManager) {
        Assert.notNull(transactionManager, "The 'transactionManager' argument must not be null.");
        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,其中包含一些自定义事务设置。 然后,可以将sharedTransactionTemplate注入到所需的尽可能多的服务中。

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

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

1.6.2. 使用 PlatformTransactionManager

您也可以直接使用org.springframework.transaction.PlatformTransactionManager来管理您的事务。 只需通过bean引用将您正在使用的PlatformTransactionManager的实现传递给您的bean。 然后,使用TransactionDefinition和TransactionStatus对象,您可以启动事务,回滚和提交。

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
// explicitly setting the transaction name is something that can only be done 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);

1.7. 在编程式事务和声明式事务之间选择

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

另一方面,如果您的应用程序有大量的事务操作,则声明式事务管理通常是值得的。 它使事务管理不受业务逻辑的影响,并且不难配置。 当使用Spring框架而不是EJB CMT时,声明式事务管理的配置成本大大降低。

1.8.事务绑定事件

从Spring 4.2开始,事件的监听者可以被绑定到事务的一个阶段。 典型的例子是在事务成功完成时处理事件:当事务的结果对于聆听者实际上重要时,这允许事件被更灵活地使用。

注册常规事件侦听器是通过@EventListener注解完成的。 如果您需要使用@TransactionalEventListener将其绑定到事务。 当您这样做时,默认情况下,侦听器将被绑定到事务的提交阶段。

我们举一个例子来说明这个概念。 假定一个组件发布一个订单创建的事件,并且我们想要定义一个监听器,只有当它成功提交的事务被发布后,它才能处理这个事件:

@Component
public class MyComponent {

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

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

如果没有事务正在运行,则根本不会调用监听器,因为我们无法遵守所需的语义。 但是可以通过将注解的fallbackExecution属性设置为true来覆盖该行为。

1.9. 应用程序服务器专用集成

Spring的事务抽象通常是应用程序服务器不可知的。 此外,Spring的JtaTransactionManager类可以选择性地为JTA UserTransaction和TransactionManager对象执行JNDI查找,并自动检测后者对象的位置,而后者因应用程序服务器而异。 有权访问JTA TransactionManager允许增强事务语义,特别是支持事务挂起。 有关详细信息,请参阅JtaTransactionManager javadocs。

Spring的JtaTransactionManager是在Java EE应用程序服务器上运行的标准选择,并且可以在所有通用服务器上运行。 诸如事务挂起之类的高级功能也可以在许多服务器上运行,包括GlassFish,JBoss和Geronimo,不需要任何特殊的配置。 但是,为了完全支持事务挂起和进一步的高级集成,Spring为WebLogic Server和WebSphere提供了特殊的适配器。 这些适配器将在下面的章节中讨论。

对于标准方案(包括WebLogic Server和WebSphere),请考虑使用方便的<tx:jta-transaction-manager />配置元素。 配置后,此元素将自动检测基础服务器,并选择可用于该平台的最佳事务管理器。 这意味着您不必显式配置特定于服务器的适配器类(如以下各节所述); 相反,它们是自动选择的,标准JtaTransactionManager作为默认回退。

1.9.1. IBM WebSphere

在WebSphere 6.1.0.9及更高版本上,建议使用的Spring JTA事务管理器是WebSphereUowTransactionManager。 这个特殊的适配器利用IBM的UOWManager API,它在WebSphere Application Server 6.1.0.9及更高版本中可用。 使用此适配器,IBM正式支持Spring驱动的事务挂起(由PROPAGATION_REQUIRES_NEW启动挂起/恢复)。

1.9.2. Oracle WebLogic Server

在WebLogic Server 9.0或更高版本上,通常使用WebLogicJtaTransactionManager而不是股票JtaTransactionManager类。 正常的JtaTransactionManager的这个特殊的WebLogic特定的子类在WebLogic管理的事务环境中支持Spring的事务定义的全部功能,超越了标准的JTA语义:功能包括事务名称,每个事务的隔离级别以及在所有情况下正确地恢复事务。

1.10. 解决常见的问题

1.10.1. 对特定的数据源使用错误的事务管理器

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

1.11. 更多的资源

有关Spring框架的事务支持的更多信息:

results matching ""

    No results matching ""