Spring事务(1)

15.5. Spring TestContext Framework

15.5.7. Transaction management


1、要想启用事务,必须在ApplicationContext中配置一个PlatformTransactionManager的bean,它通过@ContextConfiguration加载。另外,必须在需要测试的类或者方法上加上@Transaction

一般我们会在applicationContext.xml中进行类似的配置:

<!-- 配置事务管理器-->
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

而DataSourceTransactionManager实现了PlatformTransactionManager接口:

 

2、Spring管理和应用管理的事务通常将参与测试的事务管理,而当Spring管理或应用程序管理配置的事务传播类型不是REQUIRED或SUPPORTS的话,就需要小心一些。

 

3、

@Transaction放在测试方法上,即方法会运行事务中,默认情况下,当测试完成后事务将自动回滚;

@Transaction加在测试类上,那么该测试类的每个方法都会运行在事务中。若没有加该注解,则不能在事务中运行,或者有注解,但事务传播类型配置为NOT_SUPPORTED,那也会没有事务。

 

4、AbstractTransactionalJUnit4SpringContextTests和AbstractTransactionalTestNGSpringContextTests是预先配置为类级别事务的支持。

下面的示例演示了一个基于Hibernate的userRepository编写集成测试的一个常见的场景。注意在createUser()方法执行以后,没有必要清理数据库,是因为任何对数据库的改变都将由TransactionalTestExecutionListener自动进行回滚

example:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = TestConfig.class)
@Transactional
public class HibernateUserRepositoryTests {

    @Autowired
    HibernateUserRepository repository;

    @Autowired
    SessionFactory sessionFactory;

    JdbcTemplate jdbcTemplate;

    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Test
    public void createUser() {
        // track initial state in test database:
        final int count = countRowsInTable("user");

        User user = new User(...);
        repository.save(user);

        // Manual flush is required to avoid false positive in test
        sessionFactory.getCurrentSession().flush();
        assertNumUsers(count + 1);
    }

    protected int countRowsInTable(String tableName) {
        return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
    }

    protected void assertNumUsers(int expected) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
    }
}

 

5、第3条说过默认情况下,测试事务会在测试完成后自动回滚。不过事务的提交和回滚也可以通过@Commit和@RollBack注解配置。

 

6、编程式事务管理:

自Spring4.1以后,可以通过TestTransaction类中的静态方法以编程地方式与测试管理事务进行交互。例如,TestTransaction可与测试方法中使用,在方法之前和方法之后开始或结束当前测试管理事务,又或者配置测试管理事务进行回滚或提交。 无论什么时候TransactionalTestexecutionListener启用,对于TestTransaction都支持。下面的例子演示了一些TestTransaction的特点。具体看TestTransaction javadoc。

@ContextConfiguration(classes = TestConfig.class)
public class ProgrammaticTransactionManagementTests extends
        AbstractTransactionalJUnit4SpringContextTests {

    @Test
    public void transactionalTest() {
        // assert initial state in test database:
        assertNumUsers(2);

        deleteFromTables("user");

        // changes to the database will be committed!
        TestTransaction.flagForCommit();
        TestTransaction.end();
        assertFalse(TestTransaction.isActive());
        assertNumUsers(0);

        TestTransaction.start();
        // perform other actions against the database that will
        // be automatically rolled back after the test completes...
    }

    protected void assertNumUsers(int expected) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
    }
}

 

6、执行事务之外的代码

有时候,你需要在事务上下文外执行某些代码, 例如,验证初始的数据库状态测试执行之前或者测试执行后的事务提交(行为)是否正常(如果测试是配置提交事务)。这里可以使用TransactionalTestExecutionListener类的注解@BeforeTransaction和@AfterTransaction简单注释在测试类中的任何void方法,或者在一个测试接口的任何void默认方法上使用这些注释中的一个。同时TransactionalTestExecutionListener类会确保在适当的时间,在事务方法之前或者之后执行这些方法。

 

和junit的贴士:

任何前置的方法(如使用JUnit 4的@Before注解的方法)和后置的方法(如使用JUnit 4的@After注解的方法)都在事务内执行。另外,由@BeforeTransaction或@AfterTransaction注解的方法,如果测试方法未配置事务,那这些方法自然不会被测试方法执行。

 

7、配置事务管理器

在测试中,TransactionalTestExecutionListener希望在Spring ApplicationContext中定义一个PlatformTransactionManager的bean。如果在测试的ApplicationContext内,有多个PlatformTransactionManager实例,限定符可以通过@Transactional("myTxMgr")或@Transactional(transactionManager ="myTxMgr"来声明,或者通过带有@Configuration的实现TransactionManagementConfigurer。在测试的ApplicationContext中,retrieveTransactionManager()用于查找一个事务管理器,具体的算法明细可以查看javadoc

 

8、所有的事务相关的注解示范
下面的基于JUnit 4的示例展示了一个假想的集成测试场景,重点阐述所有和事务相关的注解。这个例子并不意味着最好的实践,而是想说明如何使用这些注解。查看注解支持部分可以看到更多相关信息和配置的例子。对于@sql事务管理还有另外一个例子,即声明式SQL脚本在默认事务回滚语义下执行,可以使用@sql(暂时搁置)。

 

@RunWith(SpringRunner.class)
@ContextConfiguration
@Transactional(transactionManager = "txMgr")
@Commit
public class FictitiousTransactionalTest {

    @BeforeTransaction
    void verifyInitialDatabaseState() {
        // logic to verify the initial state before a transaction is started
    }

    @Before
    public void setUpTestDataWithinTransaction() {
        // set up test data within the transaction
    }

    @Test
    // overrides the class-level @Commit setting
    @Rollback
    public void modifyDatabaseWithinTransaction() {
        // logic which uses the test data and modifies database state
    }

    @After
    public void tearDownWithinTransaction() {
        // execute "tear down" logic within the transaction
    }

    @AfterTransaction
    void verifyFinalDatabaseState() {
        // logic to verify the final state after transaction has rolled back
    }

}

另外:

当你测试操控一个Hibernate会话状态或JPA持久化上下文的应用程序代码时,必须确保在执行该代码的测试方法中flush work的基本单元。未能刷flush work的基本单元可以产生错误判断:测试可能会通过,但相同的代码在现场,生产环境下会抛出一个异常。在下面基于Hibernate的测试案例,一种方法演示了这种错误判断,另一个方法正确展示了刷新会话的结果。注意,这适用于保持work在一个存储器单元的任何ORM框架。

// ...

@Autowired
SessionFactory sessionFactory;

@Transactional
@Test // no expected exception!
public void falsePositive() {
    updateEntityInHibernateSession();
    // False positive: an exception will be thrown once the Hibernate
    // Session is finally flushed (i.e., in production code)
}

@Transactional
@Test(expected = ...)
public void updateWithSessionFlush() {
    updateEntityInHibernateSession();
    // Manual flush is required to avoid false positive in test
    sessionFactory.getCurrentSession().flush();
}

// ...
Or for JPA:
// ...

@PersistenceContext
EntityManager entityManager;

@Transactional
@Test // no expected exception!
public void falsePositive() {
    updateEntityInJpaPersistenceContext();
    // False positive: an exception will be thrown once the JPA
    // EntityManager is finally flushed (i.e., in production code)
}

@Transactional
@Test(expected = ...)
public void updateWithEntityManagerFlush() {
    updateEntityInJpaPersistenceContext();
    // Manual flush is required to avoid false positive in test
    entityManager.flush();
}

// ...

 

 

17. Transaction Management

 

17.1. Introduction to Spring Framework transaction management

综合事务支持也是使用Spring框架最有说服力的理由。 Spring框架提供的事务管理一个统一的抽象,提供了以下好处:

1、跨不同的事务API,如Java事务API(JTA),JDBC,Hibernate,Java持久性API(JPA)和Java数据对象(JDO)一致的编程模型。
2、支持声明式事务管理。
3、对于编程式事务管理而言比复杂事务API(如JTA)更简单的事务API。
4、Spring的数据访问抽象完美集成。

 

下面的部分描述了Spring框架另外的价值和技术。 (这一章还包括了常见问题最佳实践的讨论,应用服务器集成和解决方案。)

1、Spring的事务支持模型的优点介绍了为什么要使用Spring框架的事务抽象,而不是EJB容器管理事务(CMT),或者选择通过专有的API来驱动本地事务,比如Hibernate。

2、了解Spring框架事务抽象概括的核心类,并介绍如何配置,并从各种来源取得DataSource实例。

3、事务同步资源描述应用程序代码如何确保资源的创建,重复使用,并妥善清理。

4、声明式事务管理介绍了声明式事务管理的支持。
5、编程式事务管理涵盖了对编程事务管理的支持(即显式编码)。
6、必然事件事务描述你怎么在一个事务中使用应用程序事件。

 

17.2 Advantages of the Spring Framework’s transaction support model

传统上,Java EE的开发者有两个事务管理的选择:全局或本地事务,两者都具有深刻局限性。全局和本地事务管理在接下来的两节回顾,其次是关于Spring框架的事务管理支持如何解决全局和本地事务模型的局限性的讨论。

1、全局事务

全局事务意味着可以使用多个事务资源,通常是关系数据库和消息队列应用服务器通过JTA,JTA是一个管理全局事务的复杂API(部分是因为它的异常模型)。此外,JTA的UserTransaction通常需要从JNDI获得,这意味着你想使用JTA就必须先使用JNDI。显然使用全局事务会限制应用程序代码任何潜在的复用,因为JTA通常只在应用服务器环境中使用。 ---贬低JTA

 

以前,使用全局事务的首选方式是通过EJB的CMT(容器管理事务):CMT是声明式事务管理的一种形式(区别于编程式事务管理)。EJB CMT不需要任何和事务相关的查找JNDI的需求,虽然在使用EJB过程中还是需要使用JNDI。它不需要在所有地方编写Java代码来控制事务。显著的缺点是CMT绑定在JTA和应用服务器环境。此外,如果一个人选择使用EJB实现业务逻辑,或者至少处于一个事务化EJB门面(模式),那么它是唯一可用的。通常EJB的缺点很大以致于它并不是很有吸引力,尤其是在面对声明式事务管理作为更好替代的情况下。 ---贬低EJB

 

2、本地事务

本地事务是资源专用的,如用JDBC连接相关联的事务。本地事务更容易使用,但有显著缺点:他们不能跨越多个事务性资源工作。例如,管理使用JDBC连接事务的代码不能用于全局的JTA事务中。由于应用程序服务器未参与到事务管理,所以它不能帮助并确保跨越多个资源的正确性。(值得注意的是,多数应用使用单一事务性的资源)另一个缺点是本地事务是侵入性的编程模型。

 

3、Spring统一的编程模型

Spring解决全局和局部事务的缺点。它使应用开发者在任何环境下使用一致的编程模型。写一次你的代码,它可以在不同环境下的不同事务管理策略中受益。Spring框架提供声明式和编程式事务管理。大多数用户喜欢声明式事务管理,在大多数情况下推荐使用。编程式事务管理,开发者用Spring框架事务抽象开发,它可以运行在任何底层事务基础之上。使用推荐的声明式模型,开发者通常书写和事务相关的很少或根本没有代码,因此不依赖于Spring框架事务API,或任何其他事务API。

 

另外:事务管理,你需要一个应用程序服务器?
当一个企业Java应用程序需要的应用服务器,Spring的事务管理支持改变了传统的规则。

尤其是你并不需要一个应用服务器仅仅是为了通过EJB实现声明性事务的时候。事实上,即使你的应用服务器具有强大的JTA功能,你也可以选择Spring框架的声明式事务,它比EJB CMT提供了更大的功率和更高效的编程模型。

通常情况下,如果你的应用程序需要跨越多个资源处理事务,那么需要一个应用服务器的JTA功能,然而这对于很多应用程序又并非是必要的。许多高端应用使用单一的,高度可扩展的数据库(如Oracle RAC)代替。独立的事务管理器,如Atomikos Transections和JOTM也是另外的选择。当然,你可能需要其他的应用服务器功能,如Java消息服务(JMS)和Java EE连接器体系结构(JCA)。
Spring框架给你一个选择,即何时扩展应用程序到一个完全加载的应用程序服务器。那些仅使用EJB CMT或JTA编写本地事务代码的日子已经一去不复返了,像那些JDBC连接代码,如果你需要那样的代码运行在全局,容器管理的事务中,也会面临着繁重的改写。 Spring框架,仅仅包含一些bean的定义在配置文件中,而不是你的代码,需要改变。

 

17.3 Understanding the Spring Framework transaction abstraction

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是一个接口,它可以容易地被模拟和桩化(概念http://www.cnblogs.com/happyframework/p/3595547.html)。它不依赖于查找策略如JNDI。PlatformTransactionManager实现定义像Spring框架的IoC容器内任何其他对象(或bean)。这个好处使得Spring框架的事务有了很有价值的抽象,甚至当你使用JTA的时候也是如此。相比直接使用JTA,事务代码更容易进行测试。

 

另外在spring内,TransactionException可由任何PlatformTransactionManager的接口方法抛出,它属于unchecked异常(http://czj4451.iteye.com/blog/1851825)(它继承自java.lang.RuntimeException)。底层的事务失败几乎都是致命的。在极少数情况下,应用程序代码可以从事务处理中恢复,不过应用开发者依然可以捕获并处理TransactionException。最关键的一点是,开发人员不会被强迫这样做。


getTransaction(..)方法返回一个TransactionStatus对象,依赖于TransactionDefinition的参数。返回的TransactionStatus对象可能代表一个新的事务,也可能代表一个现有的事务,前提是在当前调用堆栈中存在一个匹配的事务。在后一种情况下的含义是,在Java EE的事务上下文中,一个TransactionStatus也和执行的线程相关联。

 

TransactionDefinition接口指定(important)

隔离:在某种程度上将本次事务从其他事务的工作中分离出来例如,这个事务能否看到其他事务未提交的写数据?

传播通常情况下,在事务作用域中执行的代码都将在一个事务中运行。然而,在一个事件中你有指定行为的选项,即当一个事务上下文已经存在,执行(另外)一个事务方法。例如,代码可以继续在现有的事务(一般情况)运行;或暂停现有的事务和创建一个新的事务。 Spring提供所有在EJB CMT中熟悉的事务传播选项,需要在spring中阅读有关事务传播的语义,请参见17.5.7,“事务传播”。
超时事务在超时前运行多长时间,并被基础事务机制自动回滚。
只读状态:只读事务用于代码读取,但不修改数据。只读事务在某些情况下是一个有用的优化,例如当你使用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 DataSource:

<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定义将引用这个数据源。它看起来就像这样:

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

如果你在Java EE容器使用JTA,然后使用容器数据源,通过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
        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不需要知道数据源,或任何其他特定的资源,因为它使用容器的全局事务管理。

贴士:

数据源bean的上述定义使用从JEE命名空间中的<JNDI的查找/>标记。有关基于Schema的配置的详细信息,请参见第41章基于XML-Schema configuration,更多关于<JEE/>标签的信息,请参阅标题为Section41.2.3“the jee schema”。

 

也可以很容易使用Hibernate本地事务,下面会展示例子。在这种情况下,你需要定义一个Hibernate的LocalSessionFactoryBean,应用程序代码将使用它获得Hibernate的Session实例。

Datasource bean定义类似于前面本地JDBC例子,因此不会在下面的例子展示。

贴士:

如果Datasource,由任何non-JTA事务管理器使用,通过JNDI查找和并由Java EE容器管理,那么它应该是非事务,因为是由Spring框架管理事务(先这么说),而不是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事务,那么你只需要使用相同的JtaTransactionManager一如之前JDBC的 JTA 示例。 
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

贴士:

如果你使用JTA,那么你的事务管理器定义看起来是一样的,无论你用什么数据访问技术,无论是JDBC,Hibernate JPA或任何其他支持技术。而这基于一个事实,即JTA事务是全局事务,可以支持任何事务性资源。

 

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

17.4 Synchronizing resources with transactions

现在应该很清楚如何创建不同的事务管理,以及它们是如何链接到需要被同步到事务的资源(例如DataSource TransactionManager对象和JDBC DataSource,HibernateTransactionManager与一个Hibernate的SessionFactory,等等)。本节介绍应用程序代码怎样直接或间接地使用一种持久化API,如JDBC,Hibernate的,或JDO,确保这些资源的创建,重复使用,并妥善清理。本节还讨论事务同步通过相关的PlatformTransactionManager是如何触发的(可选)。

 

17.4.1 High-level synchronization approach

一般首选的方法是使用Spring最高级别基于模板的持久化整合的API,或使用本地的ORM API和transaction-aware工厂bean,或代理来管理本地资源工厂。这些transaction-aware解决方案在内部处理资源的创建和重用,清理,对资源的事务同步以及异常映射。用户的数据访问代码不具备处理这些任务,但可以完全集中在非持久化逻辑里。一般情况下,使用本地ORM API,或通过使用JdbcTemplate采取JDBC访问的模板方法。这些解决方案在这份参考文档后面的章节中详细介绍。 

 

17.4.2 Low-level synchronization approach

类:DataSourceUtils(针对JDBC),EntityManagerFactoryUtils(在JPA),SessionFactoryUtils中(Hibernate 中),PersistenceManagerFactoryUtils(对于JDO),等等在一个较低水平的存在。当你想要应用程序代码直接处理本地持久化API的资源类型,使用这些类确保获得合适的Spring框架管理实例,事务被(任选)同步,发生在这一过程中的异常可以正确映射到一个一致的API。

 

例如,使用JDBC时,不用传统的JDBC方式,调用getConnection()方法获取数据源,改为使用Spring的org.springframework.jdbc.datasource.DataSourceUtils,像这样:

Connection conn = DataSourceUtils.getConnection(dataSource);

如果现有的事务已经将其同步链接,则返回该实例。否则该方法调用触发创建一个新的连接,可以(可选)同步到任何现有的事务,并且在那个同一事务里可以后续复用。正如前面提到的任何SQLException都包含在一个Spring框架的CannotGetJdbcConnectionException,它是unchecked DataAccessException的Spring框架层次结构中的一种。这种方法比简单地从SQLException中获得更多信息,并且确保跨越数据库移植,甚至跨越不同持久化技术。

 

这种方法工作可以没有Spring事务管理(事务同步是可选的),那样对于事务管理,无论是否使用Spring的事务管理,都可以使用它。

 

当然,一旦你使用了Spring的JDBC支持,JPA支持或Hibernate支持,你通常不喜欢使用DataSourceUtils或是别的辅助类了,因为你会更乐意通过Spring的抽象不是直接使用相关的API。例如,如果你使用Spring的JdbcTemplate或jdbc.object包来简化使用JDBC,正确的连接检索发生在幕后,你将不需要写任何专门的代码。

 

17.4.3 TransactionAwareDataSourceProxy

 

在最底层存在TransactionAwareDataSourceProxy类。这是一个对目标DataSource的代理,它包装了目标DataSource,增加了对Spring事务管理能力。在这一方面,类似于由一个Java EE服务器提供的事务性JNDI数据源。

 

几乎很少需要或期望使用这个类,除非现有的代码必须被调用并传递一个标准的JDBC DataSource接口实现。在那种情况下,这段代码是可用的,但会参与Spring的事务管理。最好是用上面提到更高层次的抽象来写新代码。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值