24

In Spring Boot, the documentation seems to encourage running migrations on app startup.

This is fine, but sometimes app startup may have side effects / dependencies that I don't want to bother with - I just want to run the migrations on their own. Think just setting up a local dev database, to poke around in it, without even running the app.

In Dropwizard by comparison, running migrations alone is straightforward with built in arguments for the app, like so

java -jar hello-world.jar db migrate helloworld.yml

Is there anything equivalent for Spring Boot? Or do I just have to drop down and run liquibase directly?

I'm interested in a direct answer, but also kind of interested in seeing if I'm misunderstanding something at a higher level - like perhaps this approach of running on startup is generally 'better' for some reasons I haven't discovered yet, so you're encouraged solely to do it this way by Spring Boot as a design choice.

Bart
  • 496
  • 10
  • 23
davnicwil
  • 28,487
  • 16
  • 107
  • 123
  • have you tried to run the specific liquibase maven goal `liquibase:update`? http://www.liquibase.org/documentation/maven/maven_update.html – Paizo Mar 28 '18 at 11:21
  • Can you disable it by configuration ? :https://stackoverflow.com/questions/41491234/configure-datasource-for-liquibase-in-spring-boot – pdem Mar 28 '18 at 12:14
  • 2
    We want to do this in a CI/CD pipeline _before_ our container gets deployed. Otherwise, if Liquibase runs at app startup, it will run with every auto-scaling event which is pointless. I take it you haven't found a solution? – DarthPablo Jul 10 '19 at 07:27
  • @DarthPablo no, never found a solution. Unless switching back to Dropwizard counts ;-) And indeed, good point, just as you might not want side effects from running the app when you just want to run migrations, it's true vice versa too. Interesting design choice, never did find any convincing argument that it's a better way or had other benefits I wasn't aware of - I guess it's really just the obvious one that it's convenient you can never accidentally start the app without having run the latest migrations, but I am not sure that slight pro makes up for the cons here. – davnicwil Jan 15 '20 at 14:58
  • "Otherwise, if Liquibase runs at app startup, it will run with every auto-scaling event which is pointless" @DarthPablo when there are no migrations to apply, Liquibase only takes half a second to compare the list of migrations applied in the database with your migration files - it has never been an issue for us. And if you start 2 instances of your application at the same time, they will not interfere with each other because Liquibase uses a database lock to make sure only one instance is processing the migrations at a time. – Michael Feb 23 '21 at 07:11
  • @Michael, I take your point, but we did actually have issues with slow starting applications and poor health check configuration with containers running on EC2 backed ECS clusters in AWS (when I believe containers can sometimes "steal" CPU from each other). A slow starting container would get the Liquibase lock and then ECS would sometimes terminate it for taking too long to start leaving the lock in place, preventing any other containers from starting, taking the service out until the lock was manually cleared. As I say though, this was down to poor configuration, but can happen. – DarthPablo Feb 24 '21 at 09:56
  • @DarthPablo actually that has happened to us too, especially when doing an alter table command on a large and busy table, where the migration isn't able to complete in time, gets killed by Kubernetes and then the lock remains on the liquibase table and has to be cleared out manually... So I see your point. We had to increase the Kubernetes liveness probe timeouts, and still sometimes run into this. – Michael Feb 25 '21 at 11:51

6 Answers6

6

You can use different Spring profiles: For instance, use a profile called 'init' which will activate the 'liquibase' profile.

application.yml: (disable Liquibase by default)

spring:
  liquibase:
    enabled: false

application-init.yml: (does not run a web container, so spring will close automatically after startup)

spring:
  profiles:
    include: liquibase
  main:
    web-application-type: none

spring-liquibase.yml: (enables liquibase)

spring:
  liquibase:
    enabled: true
    change-log: classpath:/db/changelog/changelog.xml

This setup allows you to run Liquibase as an init container (spring.profiles.active=init), but if you want to you can still run Liquibase as part of your web-app (spring.profiles.active=liquibase).

blagerweij
  • 3,154
  • 1
  • 15
  • 20
5

I know this is an old question, but in case someone else stumbles upon it, this might be useful.

You can define a command line argument for your app, that you will use to only spin up a portion of the app context that will run migration.

Here's an example in Kotlin:

import org.springframework.boot.ApplicationContextFactory.ofContextClass
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
import org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration
import org.springframework.boot.builder.SpringApplicationBuilder
import org.springframework.boot.runApplication
import org.springframework.context.annotation.AnnotationConfigApplicationContext
import org.springframework.context.annotation.Import

@SpringBootApplication
class Application

@Import(DataSourceAutoConfiguration::class, LiquibaseAutoConfiguration::class)
class LiquibaseInit

fun main(args: Array<String>) {
    if (args.contains("dbinit")) {
        SpringApplicationBuilder(LiquibaseInit::class.java)
            .contextFactory(ofContextClass(AnnotationConfigApplicationContext::class.java))
            .run(*args)
        return
    }

    runApplication<Application>(*args)
}

Note: with Spring Boot 2.4.0 and earlier use SpringApplicationBuilder#contextClass instead of contextFactory.

We have 2 classes declared here: Application (the main app class having @SpringBootApplication annotation) and LiquiBaseInit (having @DataSourceAutoConfiguration and @LiquibaseAutoConfiguration), the first one will spin up the whole context, while the latter will only spin up the beans necessary for Liquibase to run migration.

Inside the main function we check if the arguments array has a string dbinit and if it is there we start an application context out of LiquiBaseInit class.

Now you can run migration with your jar file like this:

java -jar hello-world.jar dbinit

If you're going to run your app in Kubernetes, you might also want to check out this article in my blog: https://blog.monosoul.dev/2021/12/26/using-liquibase-with-kubernetes/ .

2

This answer mentions a hook that runs after Liquibase. In that question, it was used to populate the database, probably with test or default values.

@Bean
@DependsOn("liquibase")
public YourBean yourBean() {
    return new YourBean();
}

static class YourBean {

    @PostConstruct
    public void shutdown() {
         // Exit your application here.
         );
    }

}

That could work. I don't know, you'd probably even have access to the liquibase mode and only shut down when it was "create."

knallfrosch
  • 387
  • 6
  • 16
0

In general, it might be just less work to run migrations on app start, especially if you're using Spring Boot and its all set up for you. You can then just stop the app.

However if you really would like to run migrations without the Spring Boot app, you can use the Liquibase Gradle and Maven plugins: https://github.com/liquibase/liquibase-gradle-plugin https://docs.liquibase.com/tools-integrations/maven/home.html

This will require you to set up the database credentials in another config file, in addition to your application config, so that the plugins can connect to the database.

Michael
  • 556
  • 3
  • 12
0

For this purpose we created our own script that allows not only to gen migrations but also to manually apply them, however it could be applied only from gradle, see: https://github.com/Wissance/SpringUu

to apply update we use following command:

.\gradlew.bat update -PrunList='changesApply'

to specify db connection edit gradle section changesApply

For more details about our tool see article:

https://m-ushakov.medium.com/code-first-with-spring-boot-hibernate-and-liquibase-48f5c9998d95

Michael Ushakov
  • 1,639
  • 1
  • 10
  • 18
0

U can set up and configure maven liquibase plugin: https://docs.liquibase.com/tools-integrations/maven/commands/home.html

It allows to use migrations without starting spring boot app itself

can be set up as part of building artifact or like separate task

Oleg Maksymuk
  • 441
  • 4
  • 10