Вставка JPA последовательности Oracle и Junit с H2

#java #junit #spring-data-jpa #eclipselink #h2

#java #junit #spring-data-jpa #eclipselink #h2

Вопрос:

У меня есть проект Spring MvC, использующий JPA и Oracle в качестве базы данных, с этим объектом:

 @Entity
@Table(name = "AUTORISATION_TAURU")
@NoArgsConstructor
@AllArgsConstructor
@Data
@Builder
@EqualsAndHashCode(of = {"autorisationTaurusId"})
public class AutorisationTauru implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SEQ_TAURU")
@SequenceGenerator(sequenceName = "SEQ_AUTORISATION_TAURUS", allocationSize = 1, name = "SEQ_TAURU")
    @Column(name = "AUTORISATION_TAURUS_ID")
    private Long autorisationTaurusId;
..
}
  

в моем файле конфигурации xml у меня есть это;

 <bean id="dataSource"
        class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="org.h2.Driver" />
    <property name="url" value="jdbc:h2:mem:~/test2;DB_CLOSE_DELAY=-1;MODE=Oracle;INIT=RUNSCRIPT FROM 'classpath:create_db.sql';
                    RUNSCRIPT FROM 'classpath:create_db2.sql';
                    RUNSCRIPT FROM 'classpath:create_func.sql'" />
    <property name="username" value="sa" />
    <property name="password" value="" />
</bean>



  <bean id="jpaVendorAdapter"
              class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
   
        <property name="database" value="H2" />
       <bean id="entityManagerFactory"
              class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
        <property name="dataSource" ref="dataSource" />
        <property name="persistenceUnitName" value="bonanza-entities" />
        <property name="packagesToScan">
            <array>
              <value>com.bonanza.model</value>           
            </array>
        </property>
        <property name="jpaVendorAdapter" ref="jpaVendorAdapter" />
        <property name="jpaProperties">
            <props>
                <prop key="eclipselink.target-database">org.eclipse.persistence.platform.database.OraclePlatform</prop>
                
            </props>
        </property>
    </bean>
  

Я создал таблицу, в которую я выполняю ВСТАВКУ, используя параметр AUTO_INCREMENT:

 CREATE TABLE IF NOT EXISTS AUTORISATION_TAURU
(
  AUTORISATION_TAURUS_ID NUMBER ,
  

но когда я запускаю свои локальные тесты, я получаю эту ошибку:

 Local Exception Stack: 
Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.7.7.v20200504-69f2c2b80d): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.h2.jdbc.JdbcSQLSyntaxErrorException: 
Syntax error in SQL statement "SELECT SEQ_AUTORISATION_TAURUS.NEXTVAL FROM[*] DUAL"; expected "identifier"; SQL statement:
SELECT SEQ_AUTORISATION_TAURUS.NEXTVAL FROM DUAL [42001-200]
Error Code: 42001
Call: SELECT SEQ_AUTORISATION_TAURUS.NEXTVAL FROM DUAL
Query: ValueReadQuery(sql="SELECT SEQ_AUTORISATION_TAURUS.NEXTVAL FROM DUAL")
  

Если я добавлю создание продолжения:

 CREATE SEQUENCE SEQ_AUTORISATION_TAURUS
MINVALUE 1 MAXVALUE 9223372036854775807
START WITH 1 INCREMENT BY 1 CACHE 8 NOCYCLE;
  

Я получил эту ошибку при запуске теста:

     ... 43 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jpaMappingContext': Invocation of init method failed; nested exception is javax.persistence.PersistenceException: Exception [EclipseLink-28019] (Eclipse Persistence Services - 2.7.7.v20200504-69f2c2b80d): org.eclipse.persistence.exceptions.EntityManagerSetupException
Exception Description: Deployment of PersistenceUnit [bonanza-entities] failed. Close all factories for this PersistenceUnit.
Internal Exception: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.7.7.v20200504-69f2c2b80d): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: org.h2.jdbc.JdbcSQLSyntaxErrorException: Sequence "SEQ_AUTORISATION_TAURUS" already exists; SQL statement:
  

Комментарии:

1. H2 1.4.200 выдает неверное сообщение об ошибке, когда .NEXTVAL синтаксис Oracle используется для несуществующей последовательности. Сначала вам нужно создать эту последовательность с CREATE SEQUENCE SEQ_AUTORISATION_TAURUS помощью или каким-либо другим способом. Вы также не должны использовать AUTO_INCREMENT предложение в стиле MySQL в режиме совместимости с Oracle в H2 и не должны пытаться использовать столбцы идентификаторов и отдельные последовательности для одного и того же столбца вместе, вам нужно выбрать тот или иной подход.

Ответ №1:

Вероятно, проблема с вашей настройкой заключается в том, что ваши тесты создают в несколько раз контекст приложения Spring.

Каждый раз, когда запускается новый тест, он воссоздает dataSource компонент и, кроме того, пытается запустить сценарии инициализации базы данных H2.

В обычном случае первый тест создаст папку базы данных H2 и связанные с ней материалы, следующий будет использовать ее повторно.

В зависимости от содержимого этих скриптов он будет работать в большинстве случаев, но не всегда, как в вашем случае.

Чтобы избежать этой проблемы, у вас есть несколько вариантов.

С одной стороны, в этом конкретном случае вы можете включить предложение IF NOT EXISTS в свой код создания последовательности:

 CREATE SEQUENCE IF NOT EXISTS SEQ_AUTORISATION_TAURUS...
  

В общем случае вы можете изменить свой скрипт, чтобы учесть этот факт и создать, если не существует, разные элементы H2 или сначала DROP , а затем CREATE все, что вам нужно.

С другой стороны, Spring Test также предлагает вам @DirtiesContext аннотацию для аналогичной цели:

Тестовая аннотация, которая указывает, что ApplicationContext связанный с тестом является грязным и поэтому должен быть закрыт и удален из кэша контекста.

И:

@DirtiesContext может использоваться в качестве аннотации на уровне класса и метода в пределах одного и того же класса или иерархии классов. В таких сценариях ApplicationContext они будут помечены как грязные до или после любого такого аннотированного метода, а также до или после текущего тестового класса, в зависимости от настроенного methodMode() и classMode() .

Как вы можете видеть, вам нужно только аннотировать свой класс или методы тестирования с помощью этой аннотации, и Spring соответствующим образом воссоздаст контекст:

 @DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
  

Пожалуйста, имейте в виду, что этот подход повлияет на производительность ваших тестов, поскольку необходимо воссоздать контекст приложения Spring, хотя, с другой стороны, он всегда обеспечит вам чистое и детерминированное — в соответствии с вашими сценариями инициализации — состояние базы данных.