春天@Transactional

时间:2014-02-13 23:42:18

标签: spring junit transactions

我正在尝试使用Spring @Transactional注释为系统组装一个简单的极简主义JUnit测试,但没有取得多大成功。

我正在为具有唯一约束的列创建具有相同值的两个实例。如果两个实例创建恰好在不同的事务中,我希望第一个将提交,第二个将抛出异常,导致一行 - 我看到发生了这种情况。如果两个插入发生在同一个Transaction中,我希望两者都作为一个原子单元回滚,我没有看到。我确定某处存在配置问题,但我没有太多运气确定它。

对于测试,我有一个bean(ContextTestHelperImpl / ContextTestHelper),其中包含创建一个或两个实例的方法。每个方法都有一个Propagation.REQUIRES_NEW注释:

@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, 
        rollbackFor = {ContextDuplicationException.class})
public Foo createOneFoo(int val) throws ContextDuplicationException {
    try {
        return fooDAO.createFoo(val);
    } catch (Throwable th) {
        throw new ContextDuplicationException(th);
    }
}

@Override
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW, 
        rollbackFor = {ContextDuplicationException.class})
public Foo[] createTwoFoos(int val) throws ContextDuplicationException {
    try {
        return new Foo[] { fooDAO.createFoo(val), fooDAO.createFoo(val) };
    } catch (Throwable th) {
        throw new ContextDuplicationException(th);
    }
}

我有一个JUnit测试(ContextTest),它调用第一个方法两次:

public void dupTestTwoTransactions() {
    contextTestHelper.deleteAllFoo();
    Assert.assertEquals(0, contextTestHelper.getCountOfFoo());

    boolean hadException = false;
    try {
        contextTestHelper.createOneFoo(DUPLICATE_VALUE);
    } catch (ContextDuplicationException th) {
        hadException = true;
        System.out.println("[ContextTest][dupTestTwoTransactions] UNEXPECTED ERROR: " + th);
        th.printStackTrace();
    }
    Assert.assertEquals(hadException, false);
    Assert.assertEquals(1, contextTestHelper.getCountOfFoo());

    try {
      contextTestHelper.createOneFoo(DUPLICATE_VALUE);
    } catch (ContextDuplicationException th) {
        hadException = true;
    }
    Assert.assertEquals(hadException, true);
    Assert.assertEquals(1, contextTestHelper.getCountOfFoo());
}

这可以按预期工作。第一个调用不会抛出异常;第二个电话呢。最后,Foo表中有一行。

我在同一个类中进行了第二次JUnit测试,调用后一种方法一次:

@Test
public void dupTestOneTransaction() {
    contextTestHelper.deleteAllFoo();
    Assert.assertEquals(0, contextTestHelper.getCountOfFoo());

    boolean hadException = false;
    try {
      contextTestHelper.createTwoFoos(DUPLICATE_VALUE);
    } catch (ContextDuplicationException th) {
        hadException = true;
    }
    Assert.assertEquals(hadException, true);
    Assert.assertEquals(0, contextTestHelper.getCountOfFoo());
}

第二次测试在最终断言时失败--Foo实例的计数为1,而我期望为0。

我有一些shenanigans正在进行数据源设置,因为我们试图在JBoss下运行代码时使用JNDI查找。因此,JUnit需要在幕后设置JNDI查找(ContextTest.java):

@BeforeClass
public static void setUpClass() throws NamingException {
    ApplicationContext context = new ClassPathXmlApplicationContext("classpath:spring-test.xml");
    DataSource testDataSource = (DataSource) context.getBean("testDataSource");
    SimpleNamingContextBuilder builder = new SimpleNamingContextBuilder();
    builder.bind("java:comp/env/jdbc/dataSource", testDataSource);
    builder.activate();
}

这是我的spring-test.xml文件,它在NetBeans中设置在Test Packages的默认包中:

<?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:tx="http://www.springframework.org/schema/tx"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<context:property-placeholder location="user-specific.properties"/>

<bean id="testDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName"><value>${db.driver.class}</value></property>
    <property name="url"><value>${db.url}</value></property>
    <property name="username"><value>${db.user}</value></property>
    <property name="password"><value>${db.password}</value></property>
</bean>
</beans>

自第一次测试以来,我显然能够连接到数据库,所以我认为这里没有任何特殊问题。

这是applicationContext.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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.1.xsd
       http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.1.xsd
       http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.1.xsd
       http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.1.xsd
       http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-3.2.xsd"
        default-autowire="byName" >

    <context:annotation-config />
    <context:component-scan base-package="com.xyzzy" />
    <tx:annotation-driven transaction-manager="transactionManager" />

    <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location">
           <value>classpath:user-specific.properties</value>
        </property>
    </bean>

    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/dataSource"/>

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    <bean id="fooDAO" class="com.xyzzy.FooDAOImpl" />

    <bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource"><ref local="dataSource"/></property>
        <property name="packagesToScan" value="com.xyzzy" />
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">${db.dialect}</prop>
                <prop key="hibernate.show_sql">${db.show_sql}</prop>
                <prop key="hibernate.hbm2ddl.auto">${db.hbm2ddl.auto}</prop>
            </props>
        </property>
    </bean>

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="jpaVendorAdapter">
            <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
                <property name="databasePlatform" value="${db.dialect}"/>
                <property name="generateDdl" value="true"/>
                <property name="showSql" value="true"/>

            </bean>
        </property>
        <property name="packagesToScan" value="com.xyzzy" />
    </bean> 
</beans>

所有非单独测试类(Foo,FooDAO,FooDAOImpl)都在com.xyzzy包中,所有测试类(ContextTest,ContextTestHelper,ContextTestHelperImpl,ContextDuplicationException)都在com.xyzzy中。测试

Foo,FooDAO或FooDAOImpl上没有@Transactional注释。 ContextTestHelperImpl是唯一指定事务边界的。

有关如何解决这个问题的建议,以便它的行为应该如此? (我选择的dataSource类或transactionManager有什么问题吗?我在applicationContext.xml中的一些设置是不一致还是多余?)

更新:

DAO实施类:

@Repository("FooDAO")
public class FooDAOImpl implements FooDAO {
    private HibernateTemplate hibernateTemplate;

    @Autowired
    public void setSessionFactory(SessionFactory sessionFactory) {
        this.hibernateTemplate = new HibernateTemplate(sessionFactory);
    }

    @Override
    public Foo createFoo(int val) {
        Foo foo = new Foo();
        foo.setVal(val);
        saveFoo(foo);
        return foo;
    }

    void saveFoo(Foo foo) {
        hibernateTemplate.save(foo);
    }

    [... and many similar methods ...]

我还尝试使用Propagation.REQUIRED(如果没有与该线程关联的事务,请在某处读取它将导致异常),但这不会改变其行为。

1 个答案:

答案 0 :(得分:0)

我相信你在课堂上有2个考试。您假设这些测试按顺序运行。您期望精确记录计数为1或0.测试并行运行,因此与您的预期结果相冲突。

更改逻辑,以便每个测试使用它自己的唯一值(对于插入)。而不是基于select all(无标准)比较记录计数。从带有条件的数据库中选择(添加where子句)。

总而言之,测试运行器并行运行测试。您需要将其记入帐户并隔离测试用例。在每个测试用例中使用唯一值。

希望有所帮助。

相关问题