2016-03-15 137 views
0

我們最近決定改變從@Transactional一些方法來@Transactional(propagation = Propagation.REQUIRES_NEW)休眠/春 - JUnit的失敗交易(REQUIRES_NEW)

加入<tx:annotation-driven proxy-target-class="true"/>的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 [[email protected] testClass = RisikoServiceTest, testInstance = [email protected], testMethod = [email protected], 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 = [[email protected] 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 utfylling = someService.createUtfylling(); 
//Perform some operations 
someService.createUtfyllingVersjon(utfylling); 

@Transactional 
    public Utfylling createUtfylling() { 
     Utfylling utfylling = new Utfylling() 
     //some setters 
     entityManager.persist(utfylling); 
     return utfylling; 
    } 

然後我打電話創建UtfyllingVersjon

當談到內部createUtfyllingVersjon
@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); 
} 

Utfylling是分離的,所以我必須使用合併。 這適用於在碼頭本地運行代碼,但運行JUnit時測試失敗。

這是我測試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> 
+0

我認爲這個問題不能從您提供的信息回答,因爲它可能而不僅僅是導致問題的傳播行爲的變化,而且還包括您在現有事務中做了什麼,您在新創建的事務中做了什麼以及它們如何相互關聯。 –

+0

但是爲什麼當我在本地運行它作爲單元測試時它在同一個數據庫上工作? –

+0

你有'@ TransactionConfiguration'註釋你的JUnit類的默認屬性值爲'defaultRollback = true'嗎? –

回答

1

我的猜測:

@Transactional默認傳播水平該電源線,需要規範是:「支持當前事務,創建如果不存在新的」

單元測試本身在事務中運行,createUtfylling加入現有的事務,然後createUtfyllingVersion掛起它,打開它自己的事務,它沒有看到掛起的更改並觸發外鍵異常。

在應用程序運行時,你有沒有封閉事務時,createUtfyllingVersion創建它立刻COMMITED自己的新的交易(因此更新是可見的,下面的調用)

+0

謝謝。我從junit測試中刪除了Transactional並且它能夠正常工作 –

+0

無論如何,pb不在您的單元測試中,而是在您的服務層設計中。你不能讓這樣的代碼出現;無論是必須要求還是requires_new。它可能是一個服務分解比一個事務範圍更多的分解。 – Gab

+0

我明白了。我會查看代碼並確保每個事務確實是分開的,並且不共享實體 –

2

這裏有兩個不同的問題:

它爲什麼會失敗?

這很簡單:在你的代碼中你基本上做了兩個插入。當您嘗試進行第二次插入,您將收到:

Referential integrity constraint violation 

這是合乎邏輯的,因爲你只是改變了你的代碼在一個單獨的事務執行第二插入。 這個新的事務不會「看到」前一個插入的記錄(只提交,並且事務內插入在任何給定的事務中都是可見的),所以外鍵約束會阻止您插入第二行。爲什麼?因爲如果第一個事務將因任何原因而被回滾,第二個事務可能導致違反完整性。所以DB的行爲完全按照它應該的。爲了避免這種情況發生,你需要改變你的代碼以某種方式:

  • 要麼刪除FK約束
  • 不執行,在不同的交易屬於一起插入(也許只是使用規定,但不得REQUIRE_NEW)

爲什麼從測試和主代碼中得到不同的結果?

這有點困難。我唯一的假設是你的主代碼自動提交第一個事務,因此第一個插入對第二個事務是可見的。雖然你的測試保持了第一個事務的掛起(可能最終會被回滾),並且這種方式導致了上述問題。

+0

但是我們希望將utfyllingversion存儲在單獨的事務中。我怎樣才能達到這一點,而不會受到限制?刪除約束不是一個選項:) –