How can I configure Maven Liquibase plugin in Spring Boot?

11,980

Solution 1

application.properties settings are very fast to have an up and running application but not the best solution in terms of flexibility

My advice is to configure a datasource using @Configuration, example here

And then configure liquibase passing datasource defined above as follows

@Configuration
public class LiquibaseConfigurer {

    @Autowired
    @Qualifier("primaryDataSource")
    private DataSource oltpDataSource;

    @Bean
    @DependsOn
    public SpringLiquibase liquibase() {
        SpringLiquibase liquibase = new SpringLiquibase();
        liquibase.setChangeLog("classpath:liquibase/liquibase-changelog.xml");
        liquibase.setDataSource(oltpDataSource);
        return liquibase;
    }
}

In this case you just need liquibase-core dependency as follows

    <dependency>
        <groupId>org.liquibase</groupId>
        <artifactId>liquibase-core</artifactId>
    </dependency>

A simpler alternative is to configure liquibase outside the application with no maven plugin.

Download library, or install it with some package manager, and launch a command line with all settings

liquibase --driver=org.h2.Driver \
     --classpath=/path/to/h2/driver.jar \
     --changeLogFile=/db/changelog/db.changelog-master.yaml \
     --url="jdbc:h2:file:./target/glossary-test" \
     --username=sa \
     --password=sa \
     --logLevel=debug \
     migrate

Anyway the problem you have now is because you've written this:

url=${spring.datasource.url}

I don't know where did you find this syntax but try to replicate connections url and replace with the following

url=jdbc:h2:file:./target/test

do the same for other settings

Solution 2

Liquibase maven plugin supports configuration injection through pom.xml.

So you can use properties-maven-plugin to include your properties from application.properties (or use yaml-properties-maven-plugin if you are using application.yml), and then inject them into the liquibase configuration:

Example:

<plugin>
    <groupId>it.ozimov</groupId>
    <artifactId>yaml-properties-maven-plugin</artifactId>
    <version>1.1.3</version>
    <executions>
                    <execution>
                            <phase>initialize</phase>
                            <goals>
                                    <goal>read-project-properties</goal>
                            </goals>
                            <configuration>
                                    <files>
                                            <file>src/main/resources/application.yml</file>
                                    </files>
                            </configuration>
                    </execution>
     </executions>
</plugin>

Now you can inject these properties in liquibase configuration:

<plugin>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-maven-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <changeLogFile>src/main/resources/db/changelog/db.changelog-master.yaml</changeLogFile>
                <driver>${spring.datasource.driverClassName}</driver>
                <url>${spring.datasource.url}</url>
                <username>${spring.datasource.username}</username>
                <password>${spring.datasource.password}</password>
                <promptOnNonLocalDatabase>false</promptOnNonLocalDatabase>
                <databaseChangeLogTableName>DATABASECHANGELOG</databaseChangeLogTableName>
                <databaseChangeLogLockTableName>DATABASECHANGELOGLOCK</databaseChangeLogLockTableName>
            </configuration>
            <dependencies>
                <dependency>
                    <groupId>javax.xml.bind</groupId>
                    <artifactId>jaxb-api</artifactId>
                    <version>2.3.0</version>
                </dependency>
            </dependencies>
</plugin>

I also needed to set the logicalFilePath to ensure that the changelog path inferred by spring boot integration and the maven plugin where the same.

Solution 3

It is a very common occurrence in many projects.

When you use multiple plug-ins/library, each expect certain properties from environment config where key names are defined in their native nomenclature.

There is no standardization for this issue.

In order to avoid providing same values to multiple properties, which is error prone, you are recommended to use references.

# Keys needed for liquibase maven plugin
url=${spring.datasource.url}

UPDATE

I noticed you are encountering exception when running liquibase maven plugin which is of course runs outside of spring context. The solution I provided earlier works within spring context, that is when you have application spun up.

For given scenario, use maven filter resource files feature. So your command will change to

mvn liquibase:generateChangeLog resources:resources

And your setup will look like below:

src/main/filters/filter.properties

db.url=jdbc:h2:file:./target/glossary-test
db.username=sa
db.password=sa
db.driver=org.h2.Driver
db.lb.changeLogFile=classpath:/db/changelog/db.changelog-master.yaml

application.properties

[email protected]@
[email protected]@
[email protected]@
[email protected]@
[email protected]@
[email protected]@
[email protected]@
[email protected]@
[email protected]@

pom.xml

<build>
......
    <plugins
        ......
        <plugin>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-maven-plugin</artifactId>
            <version>3.6.3</version>
            <configuration>
                <propertyFile>target/classes/application.properties</propertyFile>
            </configuration>
        </plugin>

    </plugins>

    <filters>
        <filter>src/main/filters/filter.properties</filter>
    </filters>
    <resources>
        <resource>
            <directory>src/main/resources</directory>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

Please refer to my github project for complete working solution. Look at filter.properties file where you have common properties defined, and then same is referred in application.properties file.

NOTE: Since this is a spring project, you can't use ${propertyName} for maven filter file as its reserved property placeholder syntax for spring, but then use @propertyName@. For non-spring project ${propertyName} will work out of the box.

Share:
11,980
Pitto
Author by

Pitto

I am a case sensitive type of person / indoor enthusiast. print(''.join([chr(x) for x in [100,110,97,115,116,114,105,64,103,109,97,105,108,46,99,111,109]]))

Updated on June 12, 2022

Comments

  • Pitto
    Pitto almost 2 years

    I am learning Liquibase and Spring Boot so I've created a simple project with Spring Initializr.

    In the POM.xml file I've added:

        <plugin>
            <groupId>org.liquibase</groupId>
            <artifactId>liquibase-maven-plugin</artifactId>
            <version>3.4.1</version>
            <configuration>
                <propertyFile>src/main/resources/application.properties</propertyFile>
            </configuration>
        </plugin>
    

    I've specified as property file the application.properties so all the configuration of my application can happen in a single file.

    When I run any liquibase-maven-plugin task from IntelliJ I get different errors, here's an example running the changeLogSync task:

    [ERROR] Failed to execute goal org.liquibase:liquibase-maven-plugin:3.4.1:changelogSync (default-cli) on project simpleTest: The changeLogFile must be specified
    

    If I add the right keys in the application.properties I am able to make it work.

    For example I've found that liquibase-maven-plugin will not read the spring.datasource.url property but it will only read the url property.

    For this reason my application.properties will have to be something similar:

    environment                         = JUnit
    spring.datasource.url               = jdbc:h2:file:./target/test
    spring.datasource.driver-class-name = org.h2.Driver
    spring.datasource.username          = sa
    spring.datasource.password          = sa
    spring.liquibase.change-log         = classpath:/db/changelog/db.changelog-master.yaml
    spring.h2.console.enabled           = true
    spring.h2.console.path              = /h2-console
    
    
    # Keys needed for liquibase maven plugin
    url                                 = jdbc:h2:file:./target/test
    username                            = sa
    password                            = sa
    

    If I follow this pattern I'll end up having several keys with slightly different names but with the same values in my application.properties and this solution is clearly very ugly and inefficient.

    What is an efficient and maintainable way to configure and use Liquibase Maven Plugin in Spring Boot?

    Edit after the answer received from Amith Kumar:

    environment=JUnit
    spring.datasource.url=jdbc:h2:file:./target/glossary-test
    spring.datasource.driver-class-name=org.h2.Driver
    spring.datasource.username=sa
    spring.datasource.password=sa
    spring.liquibase.change-log=classpath:/db/changelog/db.changelog-master.yaml
    spring.h2.console.enabled=true
    spring.h2.console.path=/h2-console
    url=${spring.datasource.url}
    changeLogFile=${spring.liquibase.change-log}
    username=${spring.datasource.username}
    password=${spring.datasource.password}
    

    Error after the edit:

    [ERROR] Failed to execute goal org.liquibase:liquibase-maven-plugin:3.4.1:dropAll (default-cli) on project test: Error setting up or running Liquibase: liquibase.exception.DatabaseException: java.lang.RuntimeException: Cannot find database driver: Driver class was not specified and could not be determined from the url (${spring.datasource.url}) -> [Help 1]
    
  • Pitto
    Pitto about 5 years
    Hello! I've tried your suggestion and I get: liquibase.exception.DatabaseException: java.lang.RuntimeException: Cannot find database driver: Driver class was not specified and could not be determined from the url (${spring.datasource.url})
  • Amith Kumar
    Amith Kumar about 5 years
    Hi, I am hoping when you are defining properties in your file, you are not having spaces in between. Its just shown here for easy readability. Your actual file should like below: spring.datasource.url=jdbc:h2:file:./target/test <br\># Keys needed for liquibase maven plugin <br\>url=${spring.datasource.url} NOTE: <br/> is to depict new line, as its not allowed in SO comments
  • Pitto
    Pitto about 5 years
    Hi @Amith Kumar, I've tried your suggestion but I get the same error. I've updated the question with new configuratino and error data.
  • Amith Kumar
    Amith Kumar about 5 years
    Updated my solution with sample project reference and more details, this should work for you.
  • Pitto
    Pitto about 5 years
    The ${} notation was suggested in the other answer I received from Amith. While I think it is working as a general idea it doesn't work when using the Maven Liquibase Plugin. When you start one of its tasks in console it prints "'spring.datasource.url' in properties file is not being used by this task.". Thanks for the flexible solution and the repo link :)
  • ValerioMC
    ValerioMC about 5 years
    Yes exactly what i meant to say (placeholder is not working there)
  • Amith Kumar
    Amith Kumar about 5 years
    Just so you know, spring boot manages liquibase for you out of the box. You don't explicitly need DataSource, SpringLiquibase bean unless you have multiple datasources in your project or you are deviating from spring default config. Please refer spring documentation & sample spring git project from pivotal.
  • ValerioMC
    ValerioMC about 5 years
    i had also some problem with hikaricp, url and datasource. unfortunately i don't remember details now. I general the "problem" with this kind of settings is that they fit for very basic scenario or a prototype application.
  • rios0rios0
    rios0rios0 about 3 years
    Properties file needs to be the same, to the application either the liquibase. If you're using different files won't work. Or if you're using different formats like ".yml" for application and "properties" for liquibase, won't work either.
  • rios0rios0
    rios0rios0 about 3 years
    If I want to use the rollback goal for example, how can I do it? The class approach doesn't matter for maven goals I think, right? There is a gap...