2014-02-13 124 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