Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Liquibase extension #6334

Merged
merged 3 commits into from
Mar 6, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/ci-actions.yml
Original file line number Diff line number Diff line change
Expand Up @@ -329,6 +329,7 @@ jobs:
flyway
hibernate-orm-panache
reactive-pg-client
liquibase
- category: Data4
neo4j: "true"
timeout: 30
Expand Down
5 changes: 5 additions & 0 deletions bom/deployment/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -541,6 +541,11 @@
<artifactId>quarkus-flyway-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-liquibase-deployment</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-kogito-deployment</artifactId>
Expand Down
33 changes: 33 additions & 0 deletions bom/runtime/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,9 @@
<jgit.version>5.6.1.202002131546-r</jgit.version>
<flyway.version>6.3.0</flyway.version>
<yasson.version>1.0.6</yasson.version>
<liquibase.version>3.8.7</liquibase.version>
<snakeyaml.version>1.25</snakeyaml.version>
<osgi.version>6.0.0</osgi.version>
<neo4j-java-driver.version>4.0.0</neo4j-java-driver.version>
<mongo-client.version>3.12.0</mongo-client.version>
<mongo-reactivestreams-client.version>1.13.0</mongo-reactivestreams-client.version>
Expand Down Expand Up @@ -397,6 +400,11 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-flyway</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-liquibase</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
Expand Down Expand Up @@ -2715,6 +2723,31 @@
<artifactId>flyway-core</artifactId>
<version>${flyway.version}</version>
</dependency>
<dependency>
<groupId>org.liquibase</groupId>
<artifactId>liquibase-core</artifactId>
<version>${liquibase.version}</version>
<exclusions>
<exclusion>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
</exclusion>
<exclusion>
<groupId>javax.xml.bind</groupId>
<artifactId>jaxb-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<version>${osgi.version}</version>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this strictly needed or can we use an exclusion instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gsmet cool, thanks

I believe that I add this dependency for native build because of this class:
https://github.com/liquibase/liquibase/blob/9626494719cf3f6e698bcdd76ee97b9eee1ff717/liquibase-core/src/main/java/liquibase/servicelocator/ServiceLocator.java#L7

The OSGI is used in the static block of the class

    static {
        try {
            Class<?> scanner = Class.forName("Liquibase.ServiceLocator.ClrServiceLocator, Liquibase");
            instance = (ServiceLocator) scanner.newInstance();
        } catch (Exception e) {
            try {
                if (OSGiUtil.isLiquibaseLoadedAsOSGiBundle()) {
                    Bundle liquibaseBundle = FrameworkUtil.getBundle(ServiceLocator.class);
                    instance = new ServiceLocator(new OSGiPackageScanClassResolver(liquibaseBundle), 
                            new OSGiResourceAccessor(liquibaseBundle));
                } else {
                    instance = new ServiceLocator();
                }
            } catch (Throwable e1) {
                LogService.getLog(ServiceLocator.class).severe(LogType.LOG, "Cannot build ServiceLocator", e1);
            }
        }
    }

We are not using this in the quarkus at all.
I have no idea how it is possible to substitute the static block without replacing the whole class for the native build.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@andrejpetras we would need to move that code to a static method and substitute that method. Maybe you can prepare another PR for Liquibase?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh wait silly me, we would still need the dependency anyway so let's leave it at that.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gsmet I check the 4.0.x branch of the liquibase and the class does not have osgi packages anymore. We can check the liquibase extension again after liquibase release 4.0.0 version. (This will happen in few months or more ...) But this change should not affect the liquibase extension API for developer. We can change it later in background and clean up the dependencies and also fix the native resources with a regex. We can have new task for it and I can take it to synchronize the liquibase changes in the extension.

</dependency>

<dependency>
<groupId>org.webjars</groupId>
Expand Down
1 change: 1 addition & 0 deletions ci-templates/stages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@ stages:
timeoutInMinutes: 30
modules:
- flyway
- liquibase
- hibernate-orm-panache
- reactive-pg-client
name: data_3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public final class Capabilities extends SimpleBuildItem {
public static final String MONGODB_PANACHE = "io.quarkus.mongodb.panache";
public static final String KOGITO = "io.quarkus.kogito";
public static final String FLYWAY = "io.quarkus.flyway";
public static final String LIQUIBASE = "io.quarkus.liquibase";
public static final String SECURITY = "io.quarkus.security";
public static final String SECURITY_ELYTRON_OAUTH2 = "io.quarkus.elytron.security.oauth2";
public static final String SECURITY_ELYTRON_JDBC = "io.quarkus.elytron.security.jdbc";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public final class FeatureBuildItem extends MultiBuildItem {
public static final String DYNAMODB = "dynamodb";
public static final String ELASTICSEARCH_REST_CLIENT = "elasticsearch-rest-client";
public static final String FLYWAY = "flyway";
public static final String LIQUIBASE = "liquibase";
public static final String HIBERNATE_ORM = "hibernate-orm";
public static final String HIBERNATE_ORM_PANACHE = "hibernate-orm-panache";
public static final String HIBERNATE_VALIDATOR = "hibernate-validator";
Expand Down
218 changes: 218 additions & 0 deletions docs/src/main/asciidoc/liquibase.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
////
This guide is maintained in the main Quarkus repository
and pull requests should be submitted there:
https://github.com/quarkusio/quarkus/tree/master/docs/src/main/asciidoc
////
= Quarkus - Using Liquibase

include::./attributes.adoc[]
:change-log: src/main/resources/db/changeLog.xml
:config-file: application.properties

https://www.liquibase.org/[Liquibase] is an open source tool for database schema change management.

Quarkus provides first class support for using Liquibase as will be explained in this guide.

== Setting up support for Liquibase

To start using Liquibase with your project, you just need to:

* add your changeLog to the `{change-log}` file as you usually do with Liquibase
* activate the `migrate-at-start` option to migrate the schema automatically or inject the `Liquibase` object and run
your migration as you normally do.

In your `pom.xml`, add the following dependencies:

* the Liquibase extension
* your JDBC driver extension (`quarkus-jdbc-postgresql`, `quarkus-jdbc-h2`, `quarkus-jdbc-mariadb`, ...)

[source,xml]
----
<dependencies>
<!-- Liquibase specific dependencies -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-liquibase</artifactId>
</dependency>

<!-- JDBC driver dependencies -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
</dependencies>
----

Liquibase support relies on the Quarkus datasource config.
It can be customized for the default datasource as well as for every <<multiple-datasources,named datasource>>.
First, you need to add the datasource config to the `{config-file}` file
in order to allow Liquibase to manage the schema.

The following is an example for the `{config-file}` file:

[source,properties]
----
# configure your datasource
quarkus.datasource.url=jdbc:postgresql://localhost:5432/mydatabase
quarkus.datasource.driver=org.postgresql.Driver
quarkus.datasource.username=sarah
quarkus.datasource.password=connor

# Liquibase minimal config properties
quarkus.liquibase.migrate-at-start=true

# Liquibase optional config properties
# quarkus.liquibase.change-log=db/changeLog.xml
# quarkus.liquibase.validate-on-migrate=true
# quarkus.liquibase.clean-at-start=false
# quarkus.liquibase.database-change-log-lock-table-name=DATABASECHANGELOGLOCK
# quarkus.liquibase.database-change-log-table-name=DATABASECHANGELOG
# quarkus.liquibase.contexts=Context1,Context2
# quarkus.liquibase.labels=Label1,Label2
# quarkus.liquibase.default-catalog-name=DefaultCatalog
# quarkus.liquibase.default-schema-name=DefaultSchema
# quarkus.liquibase.liquibase-catalog-name=liquibaseCatalog
# quarkus.liquibase.liquibase-schema-name=liquibaseSchema
# quarkus.liquibase.liquibase-tablespace-name=liquibaseSpace
----

Add a changeLog file to the default folder following the Liquibase naming conventions: `{change-log}`
The yaml, json, xml and sql changeLog file formats are also supported.

[source,xml]
----
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd
http://www.liquibase.org/xml/ns/dbchangelog
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">

<changeSet author="quarkus" id="1">
<createTable tableName="quarkus">
<column name="ID" type="VARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="NAME" type="VARCHAR(255)"/>
</createTable>
</changeSet>
</databaseChangeLog>
----

Now you can start your application and Quarkus will run the Liquibase's update method according to your config:

[source,java]
----
import org.quarkus.liquibase.LiquibaseFactory; <1>

@ApplicationScoped
public class MigrationService {
// You can Inject the object if you want to use it manually
@Inject
LiquibaseFactory liquibaseFactory; <2>

public void checkMigration() {
// Get the list of liquibase change set statuses
try (Liquibase liquibase = liquibaseFactory.createLiquibase()) {
List<ChangeSetStatus> status = liquibase.getChangeSetStatuses(liquibaseFactory.createContexts(), liquibaseFactory.createLabels());
}
}
}
----
<1> The Quarkus extension provides a factory to initialize a Liquibase instance
<2> Inject the Quarkus liquibase factory if you want to use the liquibase methods directly

== Multiple datasources

Liquibase can be configured for multiple datasources.
The Liquibase properties are prefixed exactly the same way as the named datasources, for example:

[source,properties]
----
quarkus.datasource.driver=org.h2.Driver
quarkus.datasource.url=jdbc:h2:tcp://localhost/mem:default
quarkus.datasource.username=username-default
quarkus.datasource.min-size=3
quarkus.datasource.max-size=13

quarkus.datasource.users.driver=org.h2.Driver
quarkus.datasource.users.url=jdbc:h2:tcp://localhost/mem:users
quarkus.datasource.users.username=username1
quarkus.datasource.users.min-size=1
quarkus.datasource.users.max-size=11

quarkus.datasource.inventory.driver=org.h2.Driver
quarkus.datasource.inventory.url=jdbc:h2:tcp://localhost/mem:inventory
quarkus.datasource.inventory.username=username2
quarkus.datasource.inventory.min-size=2
quarkus.datasource.inventory.max-size=12

# Liquibase configuration for the default datasource
quarkus.liquibase.schemas=DEFAULT_TEST_SCHEMA
quarkus.liquibase.change-log=db/changeLog.xml
quarkus.liquibase.migrate-at-start=true

# Liquibase configuration for the "users" datasource
quarkus.liquibase.users.schemas=USERS_TEST_SCHEMA
quarkus.liquibase.users.change-log=db/users.xml
quarkus.liquibase.users.migrate-at-start=true

# Liquibase configuration for the "inventory" datasource
quarkus.liquibase.inventory.schemas=INVENTORY_TEST_SCHEMA
quarkus.liquibase.inventory.change-log=db/inventory.xml
quarkus.liquibase.inventory.migrate-at-start=true
----

Notice there's an extra bit in the key.
The syntax is as follows: `quarkus.liquibase.[optional name.][datasource property]`.

NOTE: Without configuration, Liquibase is set up for every datasource using the default settings.

== Using the Liquibase object

In case you are interested in using the `Liquibase` object directly, you can inject it as follows:

NOTE: If you enabled the `quarkus.liquibase.migrate-at-start` property, by the time you use the Liquibase instance,
Quarkus will already have run the migrate operation.

[source,java]
----
import org.quarkus.liquibase.LiquibaseFactory;

@ApplicationScoped
public class MigrationService {
// You can Inject the object if you want to use it manually
@Inject
LiquibaseFactory liquibaseFactory; <1>

@Inject
@LiquibaseDataSource("inventory") <2>
LiquibaseFactory liquibaseFactoryForInventory;

@Inject
@Named("liquibase_users") <3>
LiquibaseFactory liquibaseFactoryForUsers;

public void checkMigration() {
// Use the liquibase instance manually
try (Liquibase liquibase = liquibaseFactory.createLiquibase()) {
liquibase.dropAll(); <4>
liquibase.validate();
liquibase.update(liquibaseFactory.createContexts(), liquibaseFactory.createLabels());
// Get the list of liquibase change set statuses
List<ChangeSetStatus> status = liquibase.getChangeSetStatuses(liquibaseFactory.createContexts(), liquibaseFactory.createLabels()); <5>
}
}
}
----
<1> Inject the LiquibaseFactory object
<2> Inject Liquibase for named datasources using the Quarkus `LiquibaseDataSource` qualifier
<3> Inject Liquibase for named datasources
<4> Use the Liquibase instance directly
<5> List of applied or not applied liquibase ChangeSets

== Configuration Reference

include::{generated-dir}/config/quarkus-liquibase.adoc[opts=optional, leveloffset=+2]
69 changes: 69 additions & 0 deletions extensions/liquibase/deployment/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>quarkus-liquibase-parent</artifactId>
<groupId>io.quarkus</groupId>
<version>999-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>quarkus-liquibase-deployment</artifactId>
<name>Quarkus - Liquibase - Deployment</name>

<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-core-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-arc-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-agroal-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jaxb-deployment</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-liquibase</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-junit5-internal</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-test-h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-h2-deployment</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-extension-processor</artifactId>
<version>${project.version}</version>
</path>
</annotationProcessorPaths>
</configuration>
</plugin>
</plugins>
</build>
</project>
Loading