我正试图为一个使用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