【发布时间】:2014-03-13 03:30:02
【问题描述】:
我正在尝试为使用 Spring @Transactional 注释的系统构建一个简单的极简 JUnit 测试,但并没有取得多大成功。
我正在为具有唯一约束的列创建两个具有相同值的实例。如果两个实例创建碰巧在不同的事务中,我希望第一个将提交,第二个将抛出异常,导致一行 - 我看到这种情况发生了。如果这两个插入发生在同一个事务中,我希望两者都作为一个原子单元回滚,我没有看到。我确定某处存在配置问题,但我没有太多运气识别它。
对于测试,我有一个 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。
我在数据源设置方面有一些恶作剧,因为我们尝试使用 JNDI 查找来查找代码在 JBoss 下运行的时间。因此,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 中设置在测试包的默认包中:
<?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 尝试过(在某个地方读到,如果没有与线程关联的事务,它将在早期导致异常),但这不会改变它的行为。
【问题讨论】:
标签: spring junit transactions