我正試圖爲一個使用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(讀的地方,它會導致異常早,如果有不與線程關聯事務試了一下),但這不會改變它的行爲。
感謝您的回覆,但我很確定這不是問題。當我打印出每種方法的入口/出口時,我會看到測試按順序運行。 (正如你所建議的那樣,我改變了第二次測試以使用不同的唯一值,但仍然以相同的方式失敗。) – user3207820
謝謝你的迴應。我會質疑前提:
return new Foo[] { fooDAO.createFoo(val), fooDAO.createFoo(val) };
在同一個事務中執行2次插入。這不會發生在你的DAO?如:entityManager.persist(s); entityManager.persist(s);
。我會通過調試器運行代碼來測試前提。數據庫只會拒絕第二次插入。所以你的前提完全在2次插入中發生在單個事務中。因此,我會驗證一個事務執行兩個插入。祝你好運。 – lorinpa對不起:)打字錯誤。我的評論應該是這樣寫的:「所以你的前提完全依賴於單次交易中發生的2次插入。」前提是事務管理器,而不是數據庫,回滾最初的插入。 – lorinpa