事务不适用于 Spring 3.1 – H2 – junit 4 – hibernate 3.2

Posted

技术标签:

【中文标题】事务不适用于 Spring 3.1 – H2 – junit 4 – hibernate 3.2【英文标题】:transactions not working with Spring 3.1 – H2 – junit 4– hibernate 3.2 【发布时间】:2013-01-29 03:05:16 【问题描述】:

我有以下测试..

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

干杯 普拉宾

【问题讨论】:

【参考方案1】:

我遇到了同样的问题,但我终于解决了,尽管我不使用 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>

使用此设置可以正确回滚每种测试方法的修改。

问候,奥拉

【讨论】:

【参考方案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不仅用于数据源的标识,还用于databasename:

<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 团队可以更好地配置它)

注意:通过脚本 (

【讨论】:

【参考方案3】:

试试这个:

移除 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>

【讨论】:

【参考方案4】:

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

【讨论】:

【参考方案5】:

谢谢瑞恩

测试代码是这样的。

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

【讨论】:

【参考方案6】:

以上都不适合我!

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

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

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

【讨论】:

以上是关于事务不适用于 Spring 3.1 – H2 – junit 4 – hibernate 3.2的主要内容,如果未能解决你的问题,请参考以下文章