3
votes

Case is like this. I have liquibase changelog contaning only inserts. I am trying to force Spring Boot to initialize database (hsqldb) schema using JPA based on @Entities and later execute liquibase changelog. Unfortunatelly Spring Boot is doing it in oposite order.

I checked LiquibaseAutoConfiguration and it has:

@AutoConfigureAfter({ DataSourceAutoConfiguration.class,
        HibernateJpaAutoConfiguration.class })

so it is executed after HibernateJpaAutoConfiguration however Spring Boot still do it not the way I wish ;).

Spring Boot version: 1.3.0.RELEASE Liquibase-core version: 3.5.1

Thank you in advance for any naswer

3

3 Answers

9
votes

Possible solution is to disable automatic boot liquibase run via application.properties:

spring.jpa.hibernate.ddl-auto=create
liquibase.enabled=false

and then manually configure SpringLiquibase bean to depends on entityManagerFactory:

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.autoconfigure.liquibase.LiquibaseProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.DependsOn;

import liquibase.integration.spring.SpringLiquibase;

@SpringBootApplication
public class DemoApplication {

    @Autowired
    private DataSource dataSource;

    @Bean
    public LiquibaseProperties liquibaseProperties() {
        return new LiquibaseProperties();
    }

    @Bean
    @DependsOn(value = "entityManagerFactory")
    public SpringLiquibase liquibase() {
        LiquibaseProperties liquibaseProperties = liquibaseProperties();
        SpringLiquibase liquibase = new SpringLiquibase();
        liquibase.setChangeLog(liquibaseProperties.getChangeLog());
        liquibase.setContexts(liquibaseProperties.getContexts());
        liquibase.setDataSource(getDataSource(liquibaseProperties));
        liquibase.setDefaultSchema(liquibaseProperties.getDefaultSchema());
        liquibase.setDropFirst(liquibaseProperties.isDropFirst());
        liquibase.setShouldRun(true);
        liquibase.setLabels(liquibaseProperties.getLabels());
        liquibase.setChangeLogParameters(liquibaseProperties.getParameters());
        return liquibase;
    }

    private DataSource getDataSource(LiquibaseProperties liquibaseProperties) {
        if (liquibaseProperties.getUrl() == null) {
            return this.dataSource;
        }
        return DataSourceBuilder.create().url(liquibaseProperties.getUrl())
            .username(liquibaseProperties.getUser())
            .password(liquibaseProperties.getPassword()).build();
    }

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

However I'd strongly encourage to use liquibase to build schema as well. I believe it was designed (see org.springframework.boot.autoconfigure.liquibase.LiquibaseAutoConfiguration.LiquibaseJpaDependencyConfiguration) to run before hibernate's ddl-auto so that it's possible to set ddl-auto=validate and have liquibase schema validated by hibernate.

0
votes

The solution provided by Radek Postołowicz served me quite some time but didn't work anymore after updating to spring-boot 2.5.0. I think it can be fully replaced by adding the following property to application.properties (or yml):

spring.jpa.defer-datasource-initialization=true

This is also mentioned in the release notes.

0
votes

I just updated Spring Boot to 2.5.3 and have the same problem. I solved the issue by using a class CustomSpringLiquibase (Kotlin version) :

class CustomSpringLiquibase(
        private var springLiquibase: SpringLiquibase
) : InitializingBean, BeanNameAware, ResourceLoaderAware {

    companion object {
        private val LOGGER = LoggerFactory.getLogger(CustomSpringLiquibase::class.java)
    }

    @Throws(LiquibaseException::class)
    override fun afterPropertiesSet() {
        LOGGER.info("Init Liquibase")
        springLiquibase.afterPropertiesSet()
    }

    override fun setBeanName(name: String) {
        springLiquibase.beanName = name
    }

    override fun setResourceLoader(resourceLoader: ResourceLoader) {
        springLiquibase.resourceLoader = resourceLoader
    }
}

And in my SpringBootApplication class I added the following (Java Version):

@Bean
@DependsOn(value = "entityManagerFactory")
public CustomSpringLiquibase liquibase() {
    LiquibaseProperties liquibaseProperties = liquibaseProperties();
    SpringLiquibase liquibase = new SpringLiquibase();
    ....
    return new CustomSpringLiquibase(liquibase);
}