Hibernate fails to validate database schema, which is created by flyway migration. (database migration script is correct) Is this because of in-memory H2 database? It almost looks like hibernate connects to different database than flyway.
Steps to reproduce:
Create spring-boot project with h2, flyway and jpa (hibernate).
application.properties:
spring.datasource.url=jdbc:h2:mem:flyway_db
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.H2Dialect
spring.flyway.baseline-on-migrate=true
spring.flyway.url=jdbc:h2:mem:flyway_db
Example entity:
@Entity
@Table(name = "test_entity")
public class TestEntity {
public int getId() {
return id;
}
public void setId(final int id) {
this.id = id;
}
public String getDesc() {
return description;
}
public void setDesc(final String desc) {
this.description = desc;
}
@Id
private int id;
private String description;
}
V1__init.sql script:
CREATE TABLE test_entity
(
id INTEGER,
description VARCHAR(255),
PRIMARY KEY(id)
);
Expect application run fail: full log:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.6.6)
2022-05-20 12:53:54.920 INFO 10104 --- [ main] flyway_hibernate_h2.test.Application : Starting Application using Java 11.0.15 on cfe484487b08d045fbee329048f51788 with PID 10104 (/home/gitrow/eclipse-workspace-new/spring.flyway_hibernate_h2.test/target/classes started by gitrow in /home/gitrow/eclipse-workspace-new/spring.flyway_hibernate_h2.test)
2022-05-20 12:53:54.926 INFO 10104 --- [ main] flyway_hibernate_h2.test.Application : No active profile set, falling back to 1 default profile: "default"
2022-05-20 12:53:55.594 INFO 10104 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data JPA repositories in DEFAULT mode.
2022-05-20 12:53:55.613 INFO 10104 --- [ main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 4 ms. Found 0 JPA repository interfaces.
2022-05-20 12:53:56.163 INFO 10104 --- [ main] o.f.c.internal.license.VersionPrinter : Flyway Community Edition 8.0.5 by Redgate
2022-05-20 12:53:56.164 INFO 10104 --- [ main] o.f.c.i.database.base.BaseDatabaseType : Database: jdbc:h2:mem:flyway_db (H2 1.4)
2022-05-20 12:53:56.241 INFO 10104 --- [ main] o.f.core.internal.command.DbValidate : Successfully validated 1 migration (execution time 00:00.027s)
2022-05-20 12:53:56.263 INFO 10104 --- [ main] o.f.c.i.s.JdbcTableSchemaHistory : Creating Schema History table "PUBLIC"."flyway_schema_history" ...
2022-05-20 12:53:56.337 INFO 10104 --- [ main] o.f.core.internal.command.DbMigrate : Current version of schema "PUBLIC": << Empty Schema >>
2022-05-20 12:53:56.344 INFO 10104 --- [ main] o.f.core.internal.command.DbMigrate : Migrating schema "PUBLIC" to version "1 - init"
2022-05-20 12:53:56.381 INFO 10104 --- [ main] o.f.core.internal.command.DbMigrate : Successfully applied 1 migration to schema "PUBLIC", now at version v1 (execution time 00:00.054s)
2022-05-20 12:53:56.517 INFO 10104 --- [ main] o.hibernate.jpa.internal.util.LogHelper : HHH000204: Processing PersistenceUnitInfo [name: default]
2022-05-20 12:53:56.558 INFO 10104 --- [ main] org.hibernate.Version : HHH000412: Hibernate ORM core version 5.6.7.Final
2022-05-20 12:53:56.736 INFO 10104 --- [ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.1.2.Final}
2022-05-20 12:53:56.832 INFO 10104 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2022-05-20 12:53:56.854 INFO 10104 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2022-05-20 12:53:56.874 INFO 10104 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2022-05-20 12:53:57.385 ERROR 10104 --- [ main] j.LocalContainerEntityManagerFactoryBean : Failed to initialize JPA EntityManagerFactory: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [test_entity]
2022-05-20 12:53:57.386 WARN 10104 --- [ main] s.c.a.AnnotationConfigApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [test_entity]
2022-05-20 12:53:57.386 INFO 10104 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown initiated...
2022-05-20 12:53:57.393 INFO 10104 --- [ main] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Shutdown completed.
2022-05-20 12:53:57.402 INFO 10104 --- [ main] ConditionEvaluationReportLoggingListener :
Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2022-05-20 12:53:57.427 ERROR 10104 --- [ main] o.s.boot.SpringApplication : Application run failed
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'entityManagerFactory' defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaConfiguration.class]: Invocation of init method failed; nested exception is javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [test_entity]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1804) ~[spring-beans-5.3.18.jar:5.3.18]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:620) ~[spring-beans-5.3.18.jar:5.3.18]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:542) ~[spring-beans-5.3.18.jar:5.3.18]
at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:335) ~[spring-beans-5.3.18.jar:5.3.18]
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234) ~[spring-beans-5.3.18.jar:5.3.18]
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:333) ~[spring-beans-5.3.18.jar:5.3.18]
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:208) ~[spring-beans-5.3.18.jar:5.3.18]
at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1154) ~[spring-context-5.3.18.jar:5.3.18]
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:908) ~[spring-context-5.3.18.jar:5.3.18]
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:583) ~[spring-context-5.3.18.jar:5.3.18]
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:740) ~[spring-boot-2.6.6.jar:2.6.6]
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:415) ~[spring-boot-2.6.6.jar:2.6.6]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:303) ~[spring-boot-2.6.6.jar:2.6.6]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1312) ~[spring-boot-2.6.6.jar:2.6.6]
at org.springframework.boot.SpringApplication.run(SpringApplication.java:1301) ~[spring-boot-2.6.6.jar:2.6.6]
at flyway_hibernate_h2.test.Application.main(Application.java:10) ~[classes/:na]
Caused by: javax.persistence.PersistenceException: [PersistenceUnit: default] Unable to build Hibernate SessionFactory; nested exception is org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [test_entity]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:421) ~[spring-orm-5.3.18.jar:5.3.18]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396) ~[spring-orm-5.3.18.jar:5.3.18]
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341) ~[spring-orm-5.3.18.jar:5.3.18]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1863) ~[spring-beans-5.3.18.jar:5.3.18]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1800) ~[spring-beans-5.3.18.jar:5.3.18]
... 15 common frames omitted
Caused by: org.hibernate.tool.schema.spi.SchemaManagementException: Schema-validation: missing table [test_entity]
at org.hibernate.tool.schema.internal.AbstractSchemaValidator.validateTable(AbstractSchemaValidator.java:129) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final]
at org.hibernate.tool.schema.internal.GroupedSchemaValidatorImpl.validateTables(GroupedSchemaValidatorImpl.java:42) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final]
at org.hibernate.tool.schema.internal.AbstractSchemaValidator.performValidation(AbstractSchemaValidator.java:97) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final]
at org.hibernate.tool.schema.internal.AbstractSchemaValidator.doValidation(AbstractSchemaValidator.java:76) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final]
at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.performDatabaseAction(SchemaManagementToolCoordinator.java:204) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final]
at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.process(SchemaManagementToolCoordinator.java:85) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final]
at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:335) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final]
at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:471) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final]
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1498) ~[hibernate-core-5.6.7.Final.jar:5.6.7.Final]
at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:58) ~[spring-orm-5.3.18.jar:5.3.18]
at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) ~[spring-orm-5.3.18.jar:5.3.18]
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409) ~[spring-orm-5.3.18.jar:5.3.18]
... 19 common frames omitted
What I tried: explicitly name in memory database, explicitly name schema for both datasource and flyway.
CodePudding user response:
As noted in the comments below the question, the problem can be solved by removing the property spring.flyway.url
from the properties file.
If the property is not set, then Spring Boot will configure Flyway with either the primary DataSource
or, if it exists, the DataSource
that is annotated with @FlywayDataSource
. The property spring.flyway.url
exists to override this selection process with a concrete connection string. The relevant code for this can be found in the class FlywayAutoConfiguration
.
In the particular case of H2, overriding the connection string leads to the issue that H2 databases do not remain open after a transaction unless the connection string contains the flags that tell it do so. This explains the observation in the question that Flyway runs against a database that is afterwards closed and then Hibernate connects to a clean new instance of H2.
This follows the usual patterns of Spring Boot and its philosophy of auto-configuration. Auto-configuration is intended to work hands-off and being triggered by things merely being on the classpath. Accordingly, for many of its features no property or code is required to enable them.
At the same time, Spring Boot can only use sensible defaults or heuristics on what auto-configuration should configure in which cases. Therefore, Spring Boot comes with extensions points and flags that let the auto-configuration back off easily if it's not doing the right thing. The property spring.flyway.url
is one of these flags.