base data: Using JDK 17, I'm trying to update from Spring Boot 2.7.11 -> 3.1.2. We're using a Postgres (version: 42.6.0) in production, that's why we're using h2 (version 2.1.214) with MODE=PostgreSQL, but no explicit dialect setting.
The application consists of multiple modules, one which provides access to the persistence layer. It defines multiple entities, for which the database is setup using Liquibase (version: 4.23.0). The tests override this setting, having Liquibase setup the DB, before Hibernate drops the tables and re-creates them (create-drop).
One of our integration test looks as follows:
package my.service;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ExtendWith(SpringExtension.class)
@SpringBootTest
@ActiveProfiles("test")
class MyInternalServiceTest {
@Autowired
private MyInternalService myInternalService;
@Test
public void aVeryWellDesignedTest() {
/* something */
}
@ComponentScan(basePackages = "my")
@EnableJpaRepositories(basePackages = "my.persistence")
@EntityScan(basePackages = "my.persistence")
@SpringBootApplication
@Profile("test")
@PropertySource("classpath:application-test.yml")
static class TestConfiguration {}
}
application-test.yml:
spring:
datasource:
driver-class-name: org.h2.Driver
username: sa
password:
url: jdbc:h2:mem:tests;DB_CLOSE_DELAY=-1;MODE=PostgreSQL;DB_CLOSE_ON_EXIT=FALSE
jpa:
hibernate:
ddl-auto: create-drop
main:
allow-bean-definition-overriding: true
With Spring 2.7.11 the test boots up, the DB gets created using Liquibase, re-created using Hibernate and the test then does its job - all good.
If I now change to Spring Boot 3.1.2, Hibernate somehow cannot properly detect the target DB. It seems to use statements, that are not suitable to our H2/Postgres environment. E.g., it tries to create columns using type TINYINT for enumeration attributes or BIGINT for Long. It also cannot delete tables, that are referenced by foreign keys (it cannot remove those FKs) and sequences are also not created - or cannot be used, because it uses the wrong statement to obtain the next value. For the table creation statement, I get for example:
UPDATE SUMMARY
Run: 73
Previously run: 0
Filtered out: 0
-------------------------------
Total change sets: 73
Liquibase: Die Update-Operation war erfolgreich.
2023-08-13T09:21:50.571+02:00 WARN 10865 --- [main] o.h.t.s.i.ExceptionHandlerLoggedImpl : GenerationTarget encountered exception accepting command : Error executing DDL "create table inouthistory (active boolean, geoeventsource integer, geofencestatus tinyint check (geofencestatus between 0 and 5), geotimestamp timestamp(6), id bigint not null, lastupdatetime timestamp(6), vehicleid bigint, primary key (id))" via JDBC [Unbekannter Datentyp: "TINYINT"
Unknown data type: "TINYINT";]
Note: "Liquibase: Die Update-Operation war erfolgreich." means something like "Liquibase: The Update-Operation finished successfully."
This gets resolved though, when I change @SpringBootTest
to @org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest
: the tables can be dropped and re-created by Hibernate (the test fails during the application context setup though).
To me, this seems to be a bug, since @DataJpaTest is supposed to be used for repository tests only - not for ITs. This may also be the reason, why I'm having trouble booting the application context. But the Spring Boot guys rejected this idea (https://github.com/spring-projects/spring-framework/issues/31043) - so I've probably missed something here (but I definitely liked how quickly a response came in).
This issue might be the cause for Spring Boot 3.1.2: Integration tests try to add @Configuration classes from other ITs to app context - but that's guessing, not knowing.
Please do not hesitate to ask for anything, if it might help you understand/resolve the issue.
Thanks. kniffte
we finally found a working solution. It seems like the tests found multiple TestConfiguration classes (basically, each Test class has its own).
We therefore created class
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.FilterType;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@ComponentScan(basePackages = "com.exxeta.dfstp.geofence",
excludeFilters = @ComponentScan.Filter(type = FilterType.REGEX, pattern = {
".*TestConfiguration"
}))
public @interface TestComponentScan {}
which is now used by our test class
package my.service;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.PropertySource;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.test.context.ActiveProfiles;
import org.springframework.test.context.junit.jupiter.SpringExtension;
@ActiveProfiles("test")
@Import({ MyInternalServiceTest.TestConfiguration.class })
@SpringBootTest
class MyInternalServiceTest {
@Autowired
private MyInternalService myInternalService;
@Test
public void aVeryWellDesignedTest() {
/* something */
}
@TestComponentScan
@SpringBootApplication
@PropertySource("classpath:application-test.yml")
static class TestConfiguration {}
}