2016-03-15 5 views
0

Недавно мы решили изменить некоторые методы из @Transactional в @Transactional(propagation = Propagation.REQUIRES_NEW)Hibernate/Spring - 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 то все работает нормально, но мы хотим запустить в новой транзакции

EDIT:

Вот часть кода. Я создаю 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

@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 отсоединяется, когда речь заходит внутрь createUtfyllingVersjon, так что я должен использовать слияние. Это работает при запуске кода локально в причале, но при запуске теста 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 создает свой собственную новую транзакция поручены немедленно (и поэтому обновление открыто для следующих вызовов)

+0

Спасибо. Я удалил Transactional из тестов junit и работал –

+0

Так или иначе, pb не находится в вашем модульном тесте, а в вашем дизайне сервисного уровня. Вы не можете позволить себе этот код; Необходимо либо потребовать либо оба вызова, либо require_new. Это может быть декомпозиция службы pb больше, чем область транзакции. – Gab

+0

Я понимаю. Я просмотрю код и убедитесь, что каждая транзакция действительно отдельная и не разделяет ее –

2

Есть два различных вопроса:

Почему терпят неудачу?

Это довольно просто: в вашем коде вы делаете по существу две вставки. При попытке выполнить вторую вставку, вы получите:

Referential integrity constraint violation 

что логично, так как вы только что изменили свой код, чтобы выполнить вторую вставку в отдельной транзакции. Эта новая транзакция не «видит» запись, вставленную предыдущим (только зафиксированные и встроенные в транзакции вставки будут видны в любой заданной транзакции), поэтому ограничение внешнего ключа не позволит вам вставить вторую строку. Зачем? Потому что, если первая транзакция будет отменена по какой-либо причине, вторая вставка может привести к нарушению целостности. Таким образом, DB действует точно так, как должно. Для того, чтобы избежать этого происходит вам нужно изменить свой код каким-то образом:

  • либо удалить ограничение FK
  • не выполнять вставки, которые принадлежат вместе в отдельных операциях (возможно использовать только ТРЕБУЮТ, не REQUIRE_NEW)

Почему у вас разные результаты теста и основного кода?

Это немного сложнее. Моя единственная гипотеза заключается в том, что ваш основной код автоматически выполняет первую транзакцию, поэтому первая вставка видима для второй транзакции. В то время как ваш тест удерживает первую транзакцию, зависающую (возможно, ее нужно отбросить в конце), и таким образом приводит к описанной выше проблеме.

+0

Но мы хотим, чтобы utfyllingversion хранилась в отдельной транзакции. Как я могу достичь этого, не получив ограничения? Удаление ограничения не является опцией :) –

Смежные вопросы