2014-02-13 92 views
1

我正试图为一个使用Spring @Transactional注释的系统组织一个简单的骨架极简主义JUnit测试,并且没有太大的成功。Spring @Transactional

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

对于测试,我有一个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()); 
} 

本次测试失败在最后的断言 - 美孚实例数为1,而我期望为0.

我有一些关于数据源设置的恶作剧,因为我们试图在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(); 
} 

这里是一个在NetBeans设置在测试包默认包我的春天的test.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: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> 

所有的未完全换测试类(美孚,FooDAO,FooDAOImpl)是在包com.xyzzy,所有的测试者的(ContextTest ,ContextTestHelper,ContextTestHelperImpl,ContextDuplicationException)都在com.xyzzy.test中。

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

对于如何解决这个问题的任何建议,以便它的行为应该如此? (有没有问题,我选择的dataSource类,或transactionManager?我的applicationContext.xml中的一些设置不一致或冗余?)

UPDATE:

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(读的地方,它会导致异常早,如果有不与线程关联事务试了一下),但这不会改变它的行为。

回答

0

我相信你有一个班级的2个测试。您假定这些测试是按顺序运行的。您正在等待1或0的确切记录计数。测试并行运行,因此与您的预期结果相冲突。

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

总之,测试运行器并行运行测试。您需要考虑并分离测试用例。在每个测试用例中使用唯一值。

希望有所帮助。

+0

感谢您的回复,但我很确定这不是问题。当我打印出每种方法的入口/出口时,我会看到测试按顺序运行。 (正如你所建议的那样,我改变了第二次测试以使用不同的唯一值,但仍然以相同的方式失败。) – user3207820

+0

谢谢你的回应。我会质疑前提:return new Foo[] { fooDAO.createFoo(val), fooDAO.createFoo(val) };在同一个事务中执行2次插入。这不会发生在你的DAO?如:entityManager.persist(s); entityManager.persist(s);。我会通过调试器运行代码来测试前提。数据库只会拒绝第二次插入。所以你的前提完全在2次插入中发生在单个事务中。因此,我会验证一个事务执行两个插入。祝你好运。 – lorinpa

+0

对不起:)打字错误。我的评论应该是这样写的:“所以你的前提完全依赖于单次交易中发生的2次插入。”前提是事务管理器,而不是数据库,回滚最初的插入。 – lorinpa