4

I write integration tests annotated with @SpringBootTest. To run tests I insert data to the H2 database from the data.sql in test resources.

I have a situation when the firstly tests run successfully, and after a number of times, I have an error, which tells me, that H2 cannot insert data in a table because that table is not found. The error comes with different raws every next launch of tests. In the same log, I see that this table was created and other insertions performed successfully before the exception happend. I see that 4 of my 5 tests are green and one is red. It can be a different test at different times.

A reboot of an IDE makes tests run successfully again 3-4 times. After that, the error returns.

I tried to use @DirtiesContext() with my test class and with test methods, but it didn't solve the problem.

I have a guess that the source of the problem may be in the way I initialize databases. For both databases I use a reference for one data.sql file. I didn't manage to find a way to separate them into different *.sql files.

The second guess is that insertions into databases begin before tables are created. I'm checking this theory now by moving all insertions into the test code. But I'm not sure that it'll help as I sow log that tables was created before insertion started.

I use "mvn clean" before every test run.

I would be very grateful for your recommendations in solving this problem.

My entities:

@Entity
@Table(name = "entity1", schema = "schema1")
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class SomeEntityClass extends GenericEntity<Long> { ...}


@MappedSuperclass
public abstract class GenericEntity<ID extends Serializable> implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column

    protected ID id;

//getter, setter
}

In my data.sql file initialization looks like that:

create schema if not exists schema1;
create schema if not exists schema2; 

drop table if exists schema1.table1;
create table schema1.table1
(....structure of the table...)
 
drop table if exists schema2.table2;
create table schema2.table2
(....structure of the table...)
 
INSERT INTO schema1.table1
(...)
VALUES (...)
 
... many insertions
 
 
INSERT INTO schema2.table2
(...)
VALUES (...)
 
... many insertions

This is H2 configuration in application.properties in test/resources:

# The first database
spring.datasource.url=jdbc:h2:mem:database01;INIT=RUNSCRIPT FROM 'src/test/resources/data.sql'
spring.datasource.username=user1
spring.datasource.password=abc
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.generate-ddl=true

# The second database
spring.datasource.security.url=\
  jdbc:h2:mem:dbo_security_db:database02;INIT=RUNSCRIPT FROM 'src/test/resources/data.sql'
spring.datasource.security.username=user1
spring.datasource.security.password=abc
spring.datasource.security.driver-class-name=org.h2.Driver

spring.jpa.hibernate.ddl-auto=update
spring.jpa.properties.hibernate.format_sql=true

Log with error:

08-09-2021 09:00:08.729 DEBUG [o.s.jdbc.datasource.init.ScriptUtils] - 0 returned as update count for SQL: create table schema1.table1( ….)
08-09-2021 09:00:08.730 DEBUG [o.s.jdbc.datasource.init.ScriptUtils] - 1 returned as update count for SQL: INSERT INTO schema1.table1(...) VALUES ()
///...other insertions into this table performed well
08-09-2021 09:00:08.735 WARN  [o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext] - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dataSourceScriptDatabaseInitializer' defined in class path resource [org/springframework/boot/autoconfigure/sql/init/DataSourceInitializationConfiguration.class]: Invocation of init method failed; nested exception is org.springframework.jdbc.datasource.init.ScriptStatementFailedException: Failed to execute SQL script statement #36 of URL [file:/home/nikiforov-java/Documents/.../target/test-classes/data.sql]: INSERT INTO schema1.table1(...) VALUES (...); nested exception is org.h2.jdbc.JdbcSQLSyntaxErrorException: Table "TABLE1" not found; SQL statement: ...

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
08-09-2021 09:00:08.782 ERROR [o.s.boot.SpringApplication] - Application run failed

nikiforov.java
  • 229
  • 4
  • 12
  • Try removing `spring.jpa.generate-ddl=true`. With that in place your are telling your JPA provider to create the schema while simultaneously creating it yourself via data.sql so it possible yo get some conflicts. Alternately, you can have your JPA provider create the schema and just have the insert statement sin your script. See further: https://www.baeldung.com/spring-boot-data-sql-and-schema-sql – Alan Hay Sep 08 '21 at 10:00
  • @AlanHay, thank you for your advice, I didn't know about that. But, I'm afraid it didn't solve the problem. I forgot to mention that I have 'spring.jpa.hibernate.ddl-auto=update' in my properties as well. I tried to remove 'spring.jpa.generate-ddl=true' , and both 'spring.jpa.generate-ddl=true' and 'spring.jpa.hibernate.ddl-auto=update', but had the same issue. – nikiforov.java Sep 08 '21 at 10:40
  • I would suggest enabling SQL logging then to get a better idea of what is going on. See the following which I always set-up on projects to get a better insight into what is going on https://stackoverflow.com/questions/1710476/how-to-print-a-query-string-with-parameter-values-when-using-hibernate/19299769#19299769. As you are using Spring Boot maybe easier to set-up using this: https://github.com/candrews/log4jdbc-spring-boot-starter – Alan Hay Sep 08 '21 at 12:18
  • @AlanHay, thanks. I configured a logger according to your suggestions. By now I partly solved the problem by moving all insertions into a method annotated with `@BeforeAll`. As this method uses a Jpa repository I put `@TestInstance(TestInstance.Lifecycle.PER_CLASS) ` in my test class declaration, so I could use `@BeforeAll` with non-static methods and use JPA repositories inside the method. Now If I run only tests in a test class, annotated with `@SpringBootTest` and if I run "mvn clean" after every test run I have no problems. – nikiforov.java Sep 09 '21 at 10:08
  • However, If I run all the tests in the project, including Jpa tests which use H2 database as well, sometimes I have an exception because H2 cannot create a table, as this table already exists. Even though I see in log before that the table was dropped, because in my data.sql I have the following script: `drop table if exists schema1.table1; create table schema1.table1` – nikiforov.java Sep 09 '21 at 10:08
  • It looks like tests from different classes run in parallel and situations happen when test1 creates a table right after test2 dropped this table and before test2 created this table after dropping. Is this situation possible? Is there a way to solve the problem? Maybe I just must not use the same data.sql for integration tests and JPA tests at the same time? Thanks. – nikiforov.java Sep 09 '21 at 10:09

2 Answers2

6

Add this property to application.properties:

spring.jpa.defer-datasource-initialization=true

According to Spring Boot 2.5 RELEASE notes:

By default, data.sql scripts are now run before Hibernate is initialized. This aligns the behavior of basic script-based initialization with that of Flyway and Liquibase. If you want to use data.sql to populate a schema created by Hibernate, set spring.jpa.defer-datasource-initialization to true.

Rômulo Pereira
  • 331
  • 4
  • 6
0

After adding:

spring.jpa.defer-datasource-initialization=true

to your application.properties file, if you still have the same:

"Exception "org.h2.jdbc.JdbcSQLSyntaxErrorException:Table "NAME" not found exception, double check all your column names in your entity. Be sure you are not using reserved keywords, and if you are, add an alias using: @Column(name = "<YOUR NOT RESERVED KEYWORD COLUMN NAME>")