quarkus: Cannot use Liquibase extension with hibernate-reactive

Describe the bug It seems it is not possible to use hibernate-reactive while providing JDBC properties, and those are required buy Liquibase extension, effectively, Liquibase cannot be used with hibernate reactive

Expected behavior Should be able to use JDBC properties alongside reactive properties.

Actual behavior using JDBC properties while using hibernate-reactive leads to build exception.

Caused by: java.lang.IllegalStateException: Booting an Hibernate Reactive serviceregistry on a non-reactive RecordedState!
	at io.quarkus.hibernate.reactive.runtime.boot.registry.PreconfiguredReactiveServiceRegistryBuilder.checkIsReactive(PreconfiguredReactiveServiceRegistryBuilder.java:76)
	at io.quarkus.hibernate.reactive.runtime.boot.registry.PreconfiguredReactiveServiceRegistryBuilder.<init>(PreconfiguredReactiveServiceRegistryBuilder.java:66)
	at io.quarkus.hibernate.reactive.runtime.FastBootHibernateReactivePersistenceProvider.rewireMetadataAndExtractServiceRegistry(FastBootHibernateReactivePersistenceProvider.java:177)
	at io.quarkus.hibernate.reactive.runtime.FastBootHibernateReactivePersistenceProvider.getEntityManagerFactoryBuilderOrNull(FastBootHibernateReactivePersistenceProvider.java:156)
	at io.quarkus.hibernate.reactive.runtime.FastBootHibernateReactivePersistenceProvider.createEntityManagerFactory(FastBootHibernateReactivePersistenceProvider.java:82)
	... 86 more

Configuration

#need to limit memory taken by graalvm to create the native image , or it won't build
quarkus.native.native-image-xmx=4G


# Liquibase minimal config properties
quarkus.liquibase.migrate-at-start=true
quarkus.liquibase.default-schema-name=public
quarkus.liquibase.change-log=db/changeLog.xml

#Database
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=postgres
quarkus.datasource.password=postgres
#quarkus.datasource.jdbc=false
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost/postgres

# Reactive config
quarkus.datasource.reactive.url=vertx-reactive:postgresql://localhost/postgres

#hibernate
quarkus.hibernate-orm.log.sql=true

Environment (please complete the following information):

  • Output of uname -a or ver:
  • Output of java -version:
  • GraalVM version (if different from Java):
  • Quarkus version or git rev:
  • Build tool (Gradle 6.5.1)

Additional context

plugins {
    java
    id("io.quarkus")
}

repositories {
    mavenLocal()
    mavenCentral()
}

val quarkusPlatformGroupId: String by project
val quarkusPlatformArtifactId: String by project
val quarkusPlatformVersion: String by project

dependencies {
    implementation(enforcedPlatform("${quarkusPlatformGroupId}:${quarkusPlatformArtifactId}:${quarkusPlatformVersion}"))
    implementation("io.quarkus:quarkus-resteasy-reactive-jackson")
    implementation("io.quarkus:quarkus-hibernate-reactive-panache")
    implementation("io.quarkus:quarkus-smallrye-openapi")
    implementation("io.quarkus:quarkus-smallrye-jwt")
    implementation("io.quarkus:quarkus-liquibase")
    implementation("io.quarkus:quarkus-mailer")
    implementation("io.quarkus:quarkus-reactive-pg-client")
    implementation("io.quarkus:quarkus-smallrye-metrics")
    implementation("io.quarkus:quarkus-arc")
    implementation("io.quarkus:quarkus-reactive-pg-client")
    testImplementation("io.quarkus:quarkus-jdbc-postgresql")
    testImplementation("io.quarkus:quarkus-junit5")
    testImplementation("io.rest-assured:rest-assured")
    testImplementation("org.testcontainers:postgresql:1.15.1")
}

group = "com.qu"
version = "1.0.0-SNAPSHOT"

java {
    sourceCompatibility = JavaVersion.VERSION_11
    targetCompatibility = JavaVersion.VERSION_11
}

tasks.withType<JavaCompile> {
    options.encoding = "UTF-8"
    options.compilerArgs.add("-parameters")
}

About this issue

  • Original URL
  • State: open
  • Created 3 years ago
  • Reactions: 3
  • Comments: 36 (10 by maintainers)

Most upvoted comments

Current workaround is based on the workaround from @gwenneg on this issue #10716:


        @ConfigProperty(name = "custom.liquibase.migrate")
	boolean runMigration;
	@ConfigProperty(name = "quarkus.datasource.jdbc.url")
	String datasourceUrl;
	@ConfigProperty(name = "quarkus.datasource.username")
	String datasourceUsername;
	@ConfigProperty(name = "quarkus.datasource.password")
	String datasourcePassword;
        @ConfigProperty(name = "quarkus.liquibase.change-log")
	String changeLogLocation;

	public void runLiquibaseMigration(@Observes StartupEvent event) throws LiquibaseException {
		if(runMigration)
		{
			Liquibase liquibase = null;
			try {
				ResourceAccessor resourceAccessor = new ClassLoaderResourceAccessor(Thread.currentThread().getContextClassLoader());
				DatabaseConnection conn = DatabaseFactory.getInstance().openConnection(datasourceUrl, datasourceUsername, datasourcePassword, null, resourceAccessor);
				liquibase = new Liquibase(changeLogLocation, resourceAccessor, conn);
				liquibase.update(new Contexts(), new LabelExpression());
			} catch (Exception e) {
				logger.error("Liquibase Migration Exception: " + ExceptionUtil.generateStackTrace(e));
			}
			finally {
				if(liquibase!=null)
				{
					liquibase.close();
				}
			}
		}
	}

application.properties:


quarkus.datasource.db-kind=postgresql
quarkus.datasource.username={username}
quarkus.datasource.password={password}
quarkus.datasource.jdbc=false
quarkus.datasource.jdbc.url=jdbc:postgresql://{db-url}
quarkus.datasource.reactive.url=vertx-reactive:postgresql://{db-url}
quarkus.hibernate-orm.log.sql=true
quarkus.liquibase.change-log=db/changeLog.xml
custom.liquibase.migrate=true

pom.xml dependencies tag:

    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-hibernate-reactive</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-reactive-jackson</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-resteasy-reactive</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-reactive-pg-client</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-arc</artifactId>
    </dependency>
    <dependency>
      <groupId>io.quarkus</groupId>
      <artifactId>quarkus-hibernate-validator</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-liquibase</artifactId>
    </dependency>
    <dependency>
        <groupId>io.quarkus</groupId>
        <artifactId>quarkus-jdbc-postgresql</artifactId>
    </dependency>

There’s definitely something broken and I think the main culprit is that we should split Hibernate ORM/Hibernate Reactive differently and have:

  • a common extension that doesn’t enable any features by itself and just provide common code
  • hibernate-orm that depends on the common extension
  • hibernate-reactive that depends on the common extension instead of having the hibernate-reactive extension depending on hibernate-orm.

This way, you could have a JDBC driver around without automatically triggering Hibernate ORM. That’s the main problem you all have here.

We also might need separate config roots or a way to define if a PU is reactive or not.

But given @Sanne is currently working on integrating ORM 6 and that it will probably be a big change, this work would need to be done after ORM 6 has landed.

Maybe a warning on the Liquibase and Flyway guides that they do not work with Hibernate Reactive by default will be a good idea.

@BlackSharkCZE Several workarounds have been suggested or linked in this discussion. These workarounds make it possible to use Liquibase alongside with Hibernate Reactive in most projects.

Do you have a blocking problem that can’t be solved with one of these? If so, don’t hesitate to give us more details. The community might be able to help you fix it.

Are there any plans to introduce liquibase and flyway as reactive variant? I know the problems can be solved using a workaround, but reactive being the default now, a workaround feels a bit meh… It would be so cool to get rid of the jdbc dependency, the manual configuration and the warning in the console 2022-07-20 10:03:52,811 WARN [io.quarkus.agroal.deployment.AgroalProcessor] (build-33) The Agroal dependency is present but no JDBC datasources have been defined..

@BlackSharkCZE Several workarounds have been suggested or linked in this discussion. These workarounds make it possible to use Liquibase alongside with Hibernate Reactive in most projects.

Do you have a blocking problem that can’t be solved with one of these? If so, don’t hesitate to give us more details. The community might be able to help you fix it.

IIRC, @Sanne has mentioned elsewhere that this issue will be addressed, but likely after Hibernate 6 is out and integrated into Quarkus.

https://github.com/quarkusio/quarkus/issues/10716#issuecomment-1022118053

Sure, booting Hibernate Reactive on your own is of course possible.

But it’s not integrated into Spring in any way, nor is there any integration with Flyway or Liquibase that could be used to claim that the same feature exists in Spring and not Quarkus.

My whole point is that Quarkus is at the bleeding edge of innovation and this highly requested feature will definitely land at some point, it’s just a matter of priorities

To be completely fair, there is no Hibernate Reactive in Spring…

IIRC, @Sanne has mentioned elsewhere that this issue will be addressed, but likely after Hibernate 6 is out and integrated into Quarkus.

Any progress on this issue? Feels pretty odd coming from Spring and experiencing such issues from the very beginning 😢

DatabaseFactory

Just as a hint that may help:

  • Liquibase will try to guess the database type from the url , but the url you provided is not supported : jdbc:otel:postgresql , that is why it fails.
  • to solve this, I guess you just have to provide both the url’s to your application , each on different property.
  • use quarkus.datasource.jdbc.url=jdbc:otel:postgresql://localhost:5432/telemetry for the application , and another property for getting connections for liquibase : com.myapp.liquibase.datasource.jdbc.url=jdbc:postgresql://localhost:5432/telemetry

then in you LiquibaseRunner :

@ConfigProperty(name = "com.myapp.liquibase.datasource.jdbc.url")
    String datasourceUrl;

Hi, this is my solution…

build.gradle.kts

    //for application runtime   
    implementation("io.quarkus:quarkus-hibernate-reactive-panache")
    implementation("io.quarkus:quarkus-reactive-mysql-client")

    //only for liquibase migration
    implementation("io.quarkus:quarkus-jdbc-mysql")
    implementation("io.quarkus:quarkus-liquibase")

application.properties (I used default dev configs: https://quarkus.io/guides/databases-dev-services)

quarkus.devservices.enabled=true
quarkus.datasource.devservices.enabled=true

quarkus.datasource.db-kind=mysql
quarkus.datasource.jdbc=false

quarkus.hibernate-orm.database.generation=none

quarkus.liquibase.custom-migrate=true
quarkus.liquibase.migrate=false
quarkus.liquibase.change-log=db/changeLog.xml

a LiquibaseSetup.kt class

@ApplicationScoped
class LiquibaseSetup {

    @ConfigProperty(name = "quarkus.liquibase.custom-migrate")
    var runMigration = false

    @ConfigProperty(name = "quarkus.datasource.reactive.url")
    lateinit var datasourceUrl: String

    @ConfigProperty(name = "quarkus.datasource.username")
    lateinit var datasourceUsername: String

    @ConfigProperty(name = "quarkus.datasource.password")
    lateinit var datasourcePassword: String

    @ConfigProperty(name = "quarkus.liquibase.change-log")
    lateinit var changeLogLocation: String

    fun runLiquibaseMigration(@Observes event: StartupEvent) {
        if (runMigration) {
            var liquibase: Liquibase? = null
            try {
                Log.info("liquibase setup to $datasourceUrl")
                val resourceAccessor: ResourceAccessor = ClassLoaderResourceAccessor(Thread.currentThread().contextClassLoader)
                val conn: DatabaseConnection = DatabaseFactory.getInstance().openConnection(
                        datasourceUrl.replace("vertx-reactive", "jdbc"),
                        datasourceUsername,
                        datasourcePassword,
                        null,
                        resourceAccessor
                    )
                liquibase = Liquibase(changeLogLocation, resourceAccessor, conn)
                liquibase.update(Contexts(), LabelExpression())
            } catch (e: Exception) {
                Log.error("Liquibase Migration Exception: ", e)
            } finally {
                liquibase?.close()
            }
        }
    }
}

Agreed. Sorry people, will have to be patient as the switch to Jakarta takes priority and some “projects” are better not run in parallel.

In the meantime I would suggest embrace strict separations of concerns: don’t include Flyway in the same application as Hibernate Reactive.

I do appreciate that such a solution is not always very practical, but I do believe it’s the better design as you wouldn’t ship Flyway in production and won’t need 100s of copies of flyway to run when scaling production up/down. This way you can benefit from both Flyway and Hibernate Reactive today, and using the robust and well tested existing processes.

Totally understandable 💯, thanks for the effort!