Java Hibernate/Spring-JUnit无法处理事务(需要新的)

Java Hibernate/Spring-JUnit无法处理事务(需要新的),java,spring,hibernate,jpa,spring-transactions,Java,Spring,Hibernate,Jpa,Spring Transactions,我们最近决定将一些方法从@Transactional更改为@Transactional(传播=传播。需要\u NEW) 并在applicationContext.xml中添加了 运行应用程序时一切正常,但我们的测试失败,出现以下异常: 2016-03-15 20:44:02 [main] DEBUG org.hibernate.SQL - insert into utfylling_versjon (opprettet, utfylling_id, id) values

我们最近决定将一些方法从
@Transactional
更改为
@Transactional(传播=传播。需要\u NEW)

并在applicationContext.xml中添加了

运行应用程序时一切正常,但我们的测试失败,出现以下异常:

2016-03-15 20:44:02 [main] DEBUG org.hibernate.SQL - 
insert 
into
    utfylling_versjon
    (opprettet, utfylling_id, id) 
values
    (?, ?, ?)

2016-03-15 20:44:02 [main] TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [1] as [TIMESTAMP] - [Tue Mar 15 20:44:02 CET 2016]
2016-03-15 20:44:02 [main] TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - [1216]
2016-03-15 20:44:02 [main] TRACE o.h.type.descriptor.sql.BasicBinder - binding parameter [3] as [BIGINT] - [1217]
2016-03-15 20:44:02 [main] WARN  o.h.e.jdbc.spi.SqlExceptionHelper - SQL Error: 23506, SQLState: 23506
2016-03-15 20:44:02 [main] ERROR o.h.e.jdbc.spi.SqlExceptionHelper - Referential integrity constraint violation: "FK_JA5LSNJNODJIEEC22M3HU3YIS: PUBLIC.UTFYLLING_VERSJON FOREIGN KEY(UTFYLLING_ID) REFERENCES PUBLIC.UTFYLLING(ID) (1216)"; SQL statement:
insert into utfylling_versjon (opprettet, utfylling_id, id) values (?, ?, ?) [23506-191]
2016-03-15 20:44:02 [main] INFO  o.h.e.j.b.internal.AbstractBatchImpl - HHH000010: On release of batch it still contained JDBC statements
2016-03-15 20:44:02 [main] INFO  o.s.t.c.t.TransactionContext - Rolled back transaction for test context [DefaultTestContext@41289e88 testClass = RisikoServiceTest, testInstance = no.sb1.forsikring.seopp.kjerne.fip.RisikoServiceTest@7e8783b0, testMethod = sjekkAtFaktaBlirSatt@RisikoServiceTest, testException = org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["FK_JA5LSNJNODJIEEC22M3HU3YIS: PUBLIC.UTFYLLING_VERSJON FOREIGN KEY(UTFYLLING_ID) REFERENCES PUBLIC.UTFYLLING(ID) (1216)"; SQL statement:
insert into utfylling_versjon (opprettet, utfylling_id, id) values (?, ?, ?) [23506-191]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement, mergedContextConfiguration = [MergedContextConfiguration@d0e4972 testClass = RisikoServiceTest, locations = '{classpath:test-context.xml}', classes = '{}', contextInitializerClasses = '[]', activeProfiles = '{}', propertySourceLocations = '{}', propertySourceProperties = '{}', contextLoader = 'org.springframework.test.context.support.DelegatingSmartContextLoader', parent = [null]]].

    org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint ["FK_JA5LSNJNODJIEEC22M3HU3YIS: PUBLIC.UTFYLLING_VERSJON FOREIGN KEY(UTFYLLING_ID) REFERENCES PUBLIC.UTFYLLING(ID) (1216)"; SQL statement:
    insert into utfylling_versjon (opprettet, utfylling_id, id) values (?, ?, ?) [23506-191]]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:255)
        at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:221)
        at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521)
更具体

Caused by: org.h2.jdbc.JdbcSQLException: Referential integrity constraint violation: "FK_JA5LSNJNODJIEEC22M3HU3YIS: PUBLIC.UTFYLLING_VERSJON FOREIGN KEY(UTFYLLING_ID) REFERENCES PUBLIC.UTFYLLING(ID) (1216)"; SQL statement:
insert into utfylling_versjon (opprettet, utfylling_id, id) values (?, ?, ?) [23506-191]
    at org.h2.message.DbException.getJdbcSQLException(DbException.java:345)
    at org.h2.message.DbException.get(DbException.java:179)
为什么它会失败,因为我们运行一个需要新代码的事务

如果我将其更改回
@Transactional
,则一切正常,但我们希望在新事务中运行

编辑:

这是代码的一部分。我创造了乌特弗林

Utfylling utfylling = someService.createUtfylling();
//Perform some operations
someService.createUtfyllingVersjon(utfylling);

@Transactional
    public Utfylling createUtfylling() {
        Utfylling utfylling = new Utfylling()
        //some setters
        entityManager.persist(utfylling);
        return utfylling;
    }
然后我调用create utfylingversjon

@Transactional(propagation = Propagation.REQUIRES_NEW)
    public void createUtfyllingVersjon(Utfylling utfylling) {
 UtfyllingVersjon utfyllingVersjon = new UtfyllingVersjon(utfylling);
            entityManager.persist(utfyllingVersjon);
//some more setters
utfylling.getUtfyllingVersjoner().add(utfyllingVersjon);
            entityManager.persist(utfyllingVersjon);
            entityManager.merge(utfylling);
}
当UTFyling进入CreateUTFylingVersjon时,它是分离的,所以我必须使用merge。 当在jetty中本地运行代码时,这是可行的,但当运行JUnit测试时,它会失败

这是我的test-context.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.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd">

    <!-- enable the configuration of transactional behavior based on annotations -->
    <tx:annotation-driven proxy-target-class="true"/>

    <context:annotation-config />
    <context:component-scan base-package="foo.bar"/>

    <bean id="dozerMapper" class="org.dozer.DozerBeanMapper" />

    <bean id="h2DataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close">
        <property name="driverClassName" value="org.h2.Driver"/>
        <property name="url" value="jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;"/>
        <property name="username" value="sa"/>
        <property name="password" value=""/>
    </bean>

    <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="persistenceXmlLocation" value="META-INF/persistence-test.xml"/>
        <property name="packagesToScan" value="foo.bar" />
        <property name="dataSource" ref="h2DataSource"/>
        <property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
        <property name="jpaDialect" ref="jpaDialect"/>
        <property name="jpaProperties">
            <props>
                <prop key="hibernate.show_sql">false</prop>
                <prop key="hibernate.hbm2ddl.auto">create</prop>
            </props>
        </property>
    </bean>

    <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
        <property name="database" value="H2"/>
        <property name="databasePlatform" value="org.hibernate.dialect.H2Dialect"/>
        <property name="generateDdl" value="true"/>
        <property name="showSql" value="true"/>
    </bean>

    <bean id="jpaDialect" class="org.springframework.orm.jpa.vendor.HibernateJpaDialect"/>

    <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="transactionManager" />
    </bean>

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

</beans>

假的
创造

这里有两个不同的问题:

为什么失败?

这相当简单:在您的代码中,您实际上执行了两次插入。当您尝试执行第二次插入时,您将收到:

Referential integrity constraint violation
这是合乎逻辑的,因为您刚刚更改了代码以在单独的事务中执行第二次插入此新事务不会“看到”上一个事务插入的记录(只有已提交的和事务内插入的记录才会在任何给定事务中可见),因此外键约束阻止您插入第二行。为什么?因为如果出于任何原因回滚第一个事务,第二次插入可能会导致完整性冲突。所以DB的行为完全是它应该做的。为了避免这种情况发生,您需要以某种方式更改代码:

  • 或者删除FK约束
  • 不要执行属于单独事务的插入(可能只使用REQUIRE,而不是REQUIRE\u NEW)
为什么测试和主代码的结果不同?


这有点难。我唯一的假设是主代码自动提交第一个事务,从而使第一个插入对第二个事务可见。当您的测试保持第一个事务挂起(可能在最后回滚)时,这种方式会导致上述问题。

这里有两个不同的问题:

为什么失败?

这相当简单:在您的代码中,您实际上执行了两次插入。当您尝试执行第二次插入时,您将收到:

Referential integrity constraint violation
这是合乎逻辑的,因为您刚刚更改了代码以在单独的事务中执行第二次插入此新事务不会“看到”上一个事务插入的记录(只有已提交的和事务内插入的记录才会在任何给定事务中可见),因此外键约束阻止您插入第二行。为什么?因为如果出于任何原因回滚第一个事务,第二次插入可能会导致完整性冲突。所以DB的行为完全是它应该做的。为了避免这种情况发生,您需要以某种方式更改代码:

  • 或者删除FK约束
  • 不要执行属于单独事务的插入(可能只使用REQUIRE,而不是REQUIRE\u NEW)
为什么测试和主代码的结果不同?

这有点难。我唯一的假设是主代码自动提交第一个事务,从而使第一个插入对第二个事务可见。虽然您的测试保持第一个事务挂起(可能在最后回滚),但这种方式会导致上述问题。

我猜:

Spring
@Transactional
需要默认的传播级别,需要的规范是:“支持当前事务,如果不存在,则创建一个新事务。”

单元测试本身在事务中运行,createUtfylling加入现有事务,然后CreateUtfylingVersion暂停它,打开它自己的事务,该事务没有看到挂起的更改,并触发外键异常

在应用程序运行时,如果没有封闭事务,CreateUtfylingVersion会立即创建自己的新事务(因此以下调用可以看到更新)

我猜:

Spring
@Transactional
需要默认的传播级别,需要的规范是:“支持当前事务,如果不存在,则创建一个新事务。”

单元测试本身在事务中运行,createUtfylling加入现有事务,然后CreateUtfylingVersion暂停它,打开它自己的事务,该事务没有看到挂起的更改,并触发外键异常


在应用程序运行时,您没有封闭的事务,CreateUtfylingVersion会立即创建自己的新事务(因此更新对于以下调用是可见的)

我认为这个问题无法从您提供的详细信息中得到答案,因为可能不仅仅是传播行为的变化导致了问题,还包括您在现有事务中所做的工作,您在新创建的一个类中做了什么,以及它们之间的关系。但是,与作为单元测试运行它相比,当我在本地运行它时,为什么它在同一个数据库上工作?您在JUnit类上有一个默认属性值为
defaultRollback=true
@TransactionConfiguration
注释吗?没有,我相信我们没有每个te上的任何回滚