我正在尝试使用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(如果没有与该线程关联的事务,请在某处读取它将导致异常),但这不会改变其行为。
答案 0 :(得分:0)
我相信你在课堂上有2个考试。您假设这些测试按顺序运行。您期望精确记录计数为1或0.测试并行运行,因此与您的预期结果相冲突。
更改逻辑,以便每个测试使用它自己的唯一值(对于插入)。而不是基于select all(无标准)比较记录计数。从带有条件的数据库中选择(添加where子句)。
总而言之,测试运行器并行运行测试。您需要将其记入帐户并隔离测试用例。在每个测试用例中使用唯一值。
希望有所帮助。