3

我有以下测试..

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"/schedule-agents-config-context.xml"})
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
@Transactional
public class H2TransactionNotWorkingTest extends SubmitAgentIntegratedTestBase {
    private static final int FIVE_SUBMISSIONS = 5;

    @Autowired
    private ApplicationSubmissionInfoDao submissionDao;

    private FakeApplicationSubmissionInfoRepository fakeRepo;

    @Before
    public void setUp() {
        fakeRepo = fakeRepoThatNeverFails(submissionDao, null);
        submitApplication(FIVE_SUBMISSIONS, fakeRepo);
    }

    @Test
    @Rollback(true)
    public void shouldSaveSubmissionInfoWhenFailureInDatabase() {
        assertThat(fakeRepo.retrieveAll(), hasSize(FIVE_SUBMISSIONS));

    }

    @Test
    @Rollback(true)
    public void shouldSaveSubmissionInfoWhenFailureInXmlService() {
        assertThat(fakeRepo.retrieveAll().size(), equalTo(FIVE_SUBMISSIONS));
    }
}

...以及以下配置...

   <jdbc:embedded-database id="dataSource" type="H2">
        <jdbc:script location="classpath:/db/h2-schema.sql" />
    </jdbc:embedded-database>

    <tx:annotation-driven transaction-manager="transactionManager"/>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <bean id="transactionalSessionFactory"
          class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.H2Dialect</prop>
                <prop key="hibernate.show_sql">false</prop>
                <prop key="hibernate.cache.use_second_level_cache">false</prop>
                <prop key="hibernate.cache.use_query_cache">false</prop>
            </props>
        </property>
        <property name="namingStrategy">
            <bean class="org.hibernate.cfg.ImprovedNamingStrategy"/>
        </property>
        <property name="configurationClass" value="org.hibernate.cfg.AnnotationConfiguration"/>
        <property name="packagesToScan" value="au.com.mycomp.life.snapp"/>
    </bean>

    <bean id="regionDependentProperties" class="org.springframework.core.io.ClassPathResource">
        <constructor-arg value="region-dependent-service-test.properties"/>
    </bean

>

我还在 sql 脚本中将自动提交设置为 false

SET AUTOCOMMIT FALSE;

代码中没有 REQUIRES_NEW 。为什么回滚在测试中不起作用?

干杯普拉宾

4

6 回答 6

3

我遇到了同样的问题,但我终于解决了,尽管我不使用 Hibernate(应该不重要)。

使其工作的关键项目是扩展正确的 Spring 单元测试类,即AbstractTransactionalJUnit4SpringContextTests. 注意类名中的“事务”。因此,一个工作事务单元测试类的框架如下所示:

@ContextConfiguration(locations = {"classpath:/com/.../testContext.xml"})
public class Test extends AbstractTransactionalJUnit4SpringContextTests {

    @Test
    @Transactional
    public void test() {
    }
}

关联的 XML 上下文文件包含以下项目:

<jdbc:embedded-database id="dataSource" type="H2" />

<tx:annotation-driven transaction-manager="transactionManager"/>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

使用此设置,每种测试方法的修改都会正确回滚。

问候, 奥拉

于 2014-05-20T11:46:59.417 回答
2

我遇到了类似的问题,我也在使用 TestNG + Spring 测试支持和 Hibernate。发生的情况是 Hibernate 在事务开始之前禁用连接上的自动提交,并且它会记住原始的自动提交设置:

org.hibernate.engine.transaction.internal.jdbc#JdbcTransaction:

@Override
protected void doBegin() {
    try {
        if ( managedConnection != null ) {
            throw new TransactionException( "Already have an associated managed connection" );
        }
        managedConnection = transactionCoordinator().getJdbcCoordinator().getLogicalConnection().getConnection();
        wasInitiallyAutoCommit = managedConnection.getAutoCommit();
        LOG.debugv( "initial autocommit status: {0}", wasInitiallyAutoCommit );
        if ( wasInitiallyAutoCommit ) {
            LOG.debug( "disabling autocommit" );
            managedConnection.setAutoCommit( false );
        }
    }
    catch( SQLException e ) {
        throw new TransactionException( "JDBC begin transaction failed: ", e );
    }

    isDriver = transactionCoordinator().takeOwnership();
}

稍后,在回滚事务后,它将释放连接。这样做休眠也将恢复连接上的原始自动提交设置(以便可能分发相同连接的其他人从原始设置开始)。但是,在事务期间设置自动提交会触发显式提交,请参阅JavaDoc

在下面的代码中,您可以看到这种情况。发出回滚,最后在 releaseManagedConnection 中释放连接。这里将重新设置自动提交,从而触发提交:

org.hibernate.engine.transaction.internal.jdbc#JdbcTransaction:

    @Override
protected void doRollback() throws TransactionException {
    try {
        managedConnection.rollback();
        LOG.debug( "rolled JDBC Connection" );
    }
    catch( SQLException e ) {
        throw new TransactionException( "unable to rollback against JDBC connection", e );
    }
    finally {
        releaseManagedConnection();
    }
}


private void releaseManagedConnection() {
    try {
        if ( wasInitiallyAutoCommit ) {
            LOG.debug( "re-enabling autocommit" );
            managedConnection.setAutoCommit( true );
        }
        managedConnection = null;
    }
    catch ( Exception e ) {
        LOG.debug( "Could not toggle autocommit", e );
    }
}

这通常不应该是一个问题,因为 afaik 事务应该在回滚后结束。但更重要的是,如果我在回滚后发出提交,如果回滚和提交之间没有更改,则不应该提交任何更改,来自提交时的 javadoc:

使自上次提交/回滚以来所做的所有更改永久化,并释放此 Connection 对象当前持有的所有数据库锁。仅当禁用自动提交模式时才应使用此方法。

在这种情况下,回滚和提交之间没有变化,因为提交(通过重新设置自动提交间接触发)仅在几条语句之后发生。

一种解决方法似乎是禁用自动提交。这将避免恢复自动提交(因为它一开始没有启用),从而防止提交发生。您可以通过操作嵌入式数据源 bean 的 id 来做到这一点。id 不仅用于数据源的标识,还用于数据库名称:

<jdbc:embedded-database id="dataSource;AUTOCOMMIT=OFF" type="H2"/>

这将创建一个名为“dataSource”的数据库。额外的参数将由 H2 解释。Spring 还将创建一个名为“dataSource;AUTOCOMMIT=OFF”的 bean。如果您依赖 bean 名称进行注入,您可以创建一个别名以使其更清晰:

<alias name="dataSource;AUTOCOMMIT=OFF" alias="dataSource"/>

(没有一种更简洁的方法来操作嵌入式数据库命名空间配置,我希望 Spring 团队能够更好地配置它)

注意:通过脚本 (<jdbc:script location="...") 禁用自动提交可能不起作用,因为不能保证相同的连接将被重新用于您的测试。

注意:这不是真正的修复,而只是一种解决方法。发生回滚后仍然存在导致数据提交的错误。

- - 编辑 - -

经过搜索,我发现了真正的问题。如果您正在使用 HibernateTransactionManager(正如我所做的那样)并且您通过 SessionFactory(Hibernate)和直接通过 DataSource(普通 JDBC)使用您的数据库,您应该将 SessionFactory 和 DataSource 都传递给 HibernateTransactionManager。来自 Javadoc:

注意:为了能够为纯 JDBC 代码注册 DataSource 的 Connection,此实例 > 需要了解 DataSource (setDataSource(javax.sql.DataSource))。给定的 >DataSource 显然应该与给定的 SessionFactory 使用的相匹配。

所以最终我这样做了:

<bean id="transactionManager" class="org.springframework.orm.hibernate4.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
    <property name="dataSource" ref="dataSource" />
</bean>

一切都对我有用。

注意:同样适用于 JpaTransactionManager!如果您既使用 EntityManager 又使用 DataSource 执行原始 JDBC 访问,您应该在 EMF 旁边单独提供 DataSource。也不要忘记使用 DataSourecUtils 获取连接(或内部使用 DataSourceUtils 获取连接的 JDBCTemplate)

- - 编辑 - -

好吧,虽然上面确实解决了我的问题,但这毕竟不是真正的原因:) 在正常情况下,使用 Spring 的 LocalSessionFactoryBean 时,设置数据源将没有任何效果,因为它已经为你完成了

如果 SessionFactory 配置了 LocalDataSourceConnectionProvider,即 Spring 的 LocalSessionFactoryBean 配置了指定的“dataSource”,则将自动检测 DataSource:您仍然可以显式指定 DataSource,但在这种情况下不需要。

就我而言,问题在于我们创建了一个扩展 LocalSessionFactoryBean 的缓存工厂 bean。我们仅在测试期间使用它以避免多次启动 SessionFactory。如前所述,如果资源键不同,Spring 测试支持会启动多个应用程序上下文。这种缓存机制完全减轻了开销并确保仅加载 1 个 SF。

这意味着为不同的启动应用程序上下文返回相同的 SessionFactory。此外,传递给 SF 的数据源将是来自启动 SF 的第一个上下文的数据源。这一切都很好,但是 DataSource 本身对于每个新的应用程序上下文都是一个新的“对象”。这会产生差异:

事务由 HibernateTransactionManager 启动。用于事务同步的数据源是从 SessionFactory 中获得的(同样:缓存的 SessionFactory 和 DataSource 实例来自最初加载 SessionFactory 的应用程序上下文)。在您的测试(或生产代码)中直接使用 DataSource 时,您将使用属于此时处于活动状态的应用程序上下文的实例。此实例与用于事务同步的实例不匹配(从 SF 中提取)。这会导致问题,因为获得的连接将无法正确参与事务。

通过在事务管理器上显式设置数据源,这似乎得到了解决,因为后初始化不会从 SF 获取数据源,而是使用注入的数据源。对我来说合适的方法是调整缓存机制,每次从缓存返回 SF 时,将缓存 SF 中的数据源替换为当前 appcontext 中的数据源。

结论:您可以忽略我的帖子:) 只要您将 HibernateTransactionManager 或 JtaTransactionManager 与某种用于 SF 或 EM 的 Spring 支持工厂 bean 结合使用,即使将 vanilla JDBC 与 Hibernate 混合使用,您也应该没问题。在后一种情况下,不要忘记通过 DataSourceUtils 获取连接(或使用 JDBCTemplate)。

于 2013-09-19T11:10:15.923 回答
1

Try this:

remove the org.springframework.jdbc.datasource.DataSourceTransactionManager

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

and replace it with the org.springframework.orm.jpa.JpaTransactionManager

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

or you inject an EntityManagerFactory instead ...

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

you need an EntityManagerFactory then, like the following

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="jpaVendorAdapter">
        <bean id="jpaAdapter"
            class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
            <property name="showSql" value="true" />
            <property name="generateDdl" value="true" />
        </bean>
    </property>
</bean>
于 2013-06-25T14:05:53.213 回答
0

你还没有展示出拼图的所有部分。在这一点上,我的猜测是您的 ApplicationSubmissionInfoDao 是事务性的并且是自行提交的,尽管我认为如果一切配置正确,这将与测试事务冲突。要获得更多答案,请提出更完整的问题。最好的办法是发布SSCCE

于 2013-01-29T03:58:53.347 回答
0

谢谢瑞恩

测试代码是这样的。

@Test
@Rollback(true)
public void shouldHave5ApplicationSubmissionInfo() {
    for (int i = 0; i < 5; i++) {
        hibernateTemplate.saveOrUpdate(new ApplicationSubmissionInfoBuilder()
                .with(NOT_PROCESSED)
                .build());
    }
    assertThat(repo.retrieveAll(), hasSize(5));
}

@Test
@Rollback(true)
public void shouldHave5ApplicationSubmissionInfoAgainButHas10() {
    for (int i = 0; i < 5; i++) {
        hibernateTemplate.saveOrUpdate(new ApplicationSubmissionInfoBuilder()
                .with(NOT_PROCESSED)
                .build());
    }
    assertThat(repo.retrieveAll(), hasSize(5));
}

我发现使用 jdbc:embedded-database 定义的嵌入式数据库没有事务支持。当我使用 commons DBCP 定义数据源并将默认自动提交设置为 false 时,它​​起作用了。

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" scope="singleton">
    <property name="driverClassName" value="org.h2.Driver" />
    <property name="url" value="jdbc:h2:~/snappDb"/>
    <property name="username" value="sa"/>
    <property name="password" value=""/>
    <property name="defaultAutoCommit" value="false" />
    <property name="connectionInitSqls" value=""/>
</bean>
于 2013-01-30T01:42:37.110 回答
0

以上都不适合我!

但是,我使用的堆栈是 [ spring-test 4.2.6.RELEASEspring-core 4.2.6.RELEASEspring-data 1.10.1.RELEASE ]

问题是,使用带有 [SpringJUnit4ClassRunner.class] 注释的任何单元测试类将通过 spring 库设计检查导致自动回滚功能***org.springframework.test.context.transaction.TransactionalTestExecutionListener*** >> ***isDefaultRollback***

为了克服这种行为,只需用注释单元测试类 @Rollback(value = false)

于 2016-06-01T14:18:19.860 回答