Spring 3.1、H2、junit 4和hibernate 3.2下事务无法工作

4

我有以下测试...

@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。为什么测试中的回滚不起作用呢?

祝好 Prabin

6个回答

3

我曾经遇到过同样的问题,但最终成功解决了,尽管我没有使用Hibernate(这并不重要)。

使其工作的关键是扩展正确的Spring单元测试类,即AbstractTransactionalJUnit4SpringContextTests。请注意类名中的“Transactional”。因此,一个有效的事务性单元测试类的框架如下:

@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>

使用这个设置,每个测试方法所做的修改都会被正确地回滚。

祝好,Ola


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();
}

稍后,在回滚事务之后,它将释放连接。这样做可以使Hibernate还原连接上的原始autocommit设置(以便其他可能会被分配相同连接的人使用原始设置启动)。但是,在事务期间设置自动提交会触发显式提交,请参阅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 );
    }
}

这通常不应该是问题,因为据我所知,在回滚之后事务应该已经结束了。但更重要的是,如果我在回滚后发出提交指令,并且在回滚和提交之间没有任何更改,那么根据提交的 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使用的DataSource匹配。

所以最终我做了这个:

<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配置的,即使用带有指定“dataSource”的Spring的LocalSessionFactoryBean,则会自动检测到DataSource:在这种情况下,仍然可以显式地指定DataSource,但是您不需要这样做。

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

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

事务由HibernateTransactionManager启动。用于事务同步的数据源从SessionFactory中获取(因此再次提醒:使用来自SessionFactoy的缓存SessionFactory与数据源实例的应用程序上下文)。当直接在测试(或生产)代码中使用DataSource时,您将使用属于该点活动的app context的实例。此实例与用于事务同步的实例(从SF中提取)不匹配。这导致问题,因为所获得的连接将无法正确地参与事务。

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

结论:只要您在HibernateTransactionManager或JtaTransactionManager中与某种Spring支持工厂bean一起使用SF或EM,即使混合使用普通JDBC和Hibernate,您也应该没问题。在后一种情况下,请不要忘记通过DataSourceUtils获取连接(或使用JDBCTemplate)。


1
尝试这样做: 删除 org.springframework.jdbc.datasource.DataSourceTransactionManager。
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

将其替换为org.springframework.orm.jpa.JpaTransactionManager。
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

或者你可以注入一个 EntityManagerFactory。
<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

你需要一个EntityManagerFactory,就像以下这样
<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>

0

谢谢Ryan

测试代码大致如下。

@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>

0

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


0

以上方法都对我没用!

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

问题在于,使用任何一个带有 [SpringJUnit4ClassRunner.class] 注释的单元测试类都会导致 Spring 库设计的自动回滚功能 请查看 ***org.springframework.test.context.transaction.TransactionalTestExecutionListener*** >> ***isDefaultRollback***

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接