代码之家  ›  专栏  ›  技术社区  ›  Shervin Asgari

Hibernate/Spring-JUnit失败,出现Transactional(REQUIRES_NEW)

  •  1
  • Shervin Asgari  · 技术社区  · 8 年前

    我们最近决定从 @Transactional @Transactional(propagation = Propagation.REQUIRES_NEW)

    并添加了 <tx:annotation-driven proxy-target-class="true"/> 在里面 应用程序上下文.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)
    

    为什么它会失败,因为我们运行的事务需要新的?

    如果我把它改回 @事务性的 然后一切正常,但我们想运行一个新的事务

    编辑:

    这是部分代码。我创建了Utfyling。

    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 UtfyllingVersjon

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

    当它进入createUtfyllingVersjon时,Utfyling是分离的,所以我必须使用merge。 这在jetty本地运行代码时有效,但在运行JUnit测试时失败。

    这是我的测试上下文。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>
    
    2 回复  |  直到 8 年前
        1
  •  2
  •   Gab    8 年前

    我的猜测:

    春天 @Transactional 默认传播级别为REQUIERED,所需规范为:“支持当前事务,如果不存在则创建新事务。”

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

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

        2
  •  2
  •   Gergely Bacso    8 年前

    这里有两个不同的问题:

    为什么会失败?

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

    Referential integrity constraint violation
    

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

    • 移除FK约束
    • 不要在单独的事务中执行属于一起的插入(可能只使用REQUIRE,而不是REQUIRE_NEW)

    为什么测试和主代码的结果不同?

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