Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Use default and custom liquibase configurations in spring boot application

I'd like to use two configurations of Liquibase in current project. The default configuration I'd like to use for DDL changes and second one for custom inserts where changelog will be in another location.

If I configure SpringLiquibase the default autoconfiguration will be skipped due to @ConditionalOnClass(SpringLiquibase.class) annotation in LiquibaseAutoConfiguration class. How can I use default autoconfiguration + my custom? Can I overwrite the @ConditionalOnClass annotation somehow? Or maybe is there way how to tell Liquibase that I have another changelog outside of application and run it only if it's present?

Thanks

edit:

This could be the solution for my problem, however I have problem with loading external files (files outside of classpath) in liquibase.

@Configuration
@EnableConfigurationProperties(LiquibaseProperties.class)
public class LiquibaseConfiguration {

    @Bean
    SpringLiquibase liquibase(DataSource dataSource, LiquibaseProperties properties) {
        SpringLiquibase liquibase = new SpringLiquibase();
        liquibase.setChangeLog(properties.getChangeLog());
        liquibase.setContexts(properties.getContexts());
        liquibase.setDataSource(dataSource);
        liquibase.setDefaultSchema(properties.getDefaultSchema());
        liquibase.setDropFirst(properties.isDropFirst());
        liquibase.setShouldRun(properties.isEnabled());
        liquibase.setLabels(properties.getLabels());
        liquibase.setChangeLogParameters(properties.getParameters());
        liquibase.setRollbackFile(properties.getRollbackFile());
        return liquibase;
    }

    @Bean
    SpringLiquibase commandInitializerLiquibase(DataSource dataSource,
            @Value("${docu.system.initializer.command.liquibase.changeLog}") String changeLogPath,
            @Value("${docu.system.initializer.command.liquibase.contexts}") String contexts) {
        File changeLog = new File(changeLogPath);
        SpringLiquibase liquibase = new SpringLiquibase();
        liquibase.setDataSource(dataSource);
        liquibase.setContexts(contexts);
        liquibase.setIgnoreClasspathPrefix(true);
        liquibase.setChangeLog(changeLog.getAbsolutePath());
        liquibase.setShouldRun(changeLog.exists());
        //liquibase.setResourceLoader(liquibaseResourceLoader());
        addPathToClassloader(changeLogPath);
        return liquibase;
    }
}
like image 482
bilak Avatar asked Aug 18 '16 09:08

bilak


1 Answers

If you want to use Spring Boot autoconfigured Liquibase feature then you can have only one SpringLiquibase bean in your context. This is because of @ConditionalOnMissingBean(SpringLiquibase.class) annotation in LiquibaseAutoConfiguration class. Spring's conditional feature searches for SpringLiquibase instances and it's subclasses instances, so extending SpringLiquibase class won't fix that problem.

There is no good way to override LiquibaseAutoConfiguration. In that case you have 3 solutions which could solve your problem:

1) Implement two separate Liquibase bean configurations:

@Configuration
public class LiquibaseConfiguration {

    @Autowired
    private DataSource dataSource;

    //define this property in your embedded properties file or use spring's default
    @Value("${liquibase.change-log}")
    private String defaultLiquibaseChangelog;

    @Bean
    public SpringLiquibase liquibase() {
        SpringLiquibase liquibase = new SpringLiquibase();
        liquibase.setDataSource(dataSource);
        liquibase.setChangeLog(defaultLiquibaseChangelog);
        // Configure rest of liquibase here...
        // ...
        return liquibase;
    }
}

and

@Configuration
public class LiquibaseConfiguration2 {

    @Autowired
    private DataSource dataSource;

    //optional, define it in external configuration or through command line param
    @Value("${liquibase.change-log-additional:#{null}}")
    private String additionalLiquibaseChangelog;

    @Bean(name = "additionalLiquibase")
    public SpringLiquibase liquibase() {
        if (additionalLiquibaseChangelog != null) {
            SpringLiquibase liquibase = new SpringLiquibase();
            liquibase.setDataSource(dataSource);
            liquibase.setChangeLog(additionalLiquibaseChangelog);
            // Configure rest of liquibase here...
            // ...
            return liquibase;
        }
        return null;
    }
}

2) Use Liquibase instead of SpringLiquibase for manually configured liquibase instance

Use one autoconfigured SpringLiquibase and one pure Liquibase configuration instead of SpringLiquibase (You will need to manually run your migrations and handle other stuff which is implemented in SpringLiquibase)

3) Use only one SpringLiquibase instance

Use combination of Liquibase's changelogParameters (http://www.liquibase.org/documentation/changelog_parameters.html), include tag (http://www.liquibase.org/documentation/include.html) and only one SpringLiquibase instance.

Example implementation:

Liquibase bean configuration

@Configuration
public class LiquibaseConfiguration {

    @Autowired
    private DataSource dataSource;

    //define this property in your embedded properties file or use spring's default
    @Value("${liquibase.change-log}")
    private String defaultLiquibaseChangelog;

    //define this property in your embedded properties file
    @Value("${liquibase.extended-change-log}")
    private String extendedLiquibaseChangelog;

    //optional, define it in external configuration or through command line param
    @Value("${liquibase.data-change-log:#{null}}")
    private String liquibaseDataChangelog;

    @Bean
    public SpringLiquibase liquibase() {
        SpringLiquibase liquibase = new SpringLiquibase();
        liquibase.setDataSource(dataSource);
        if (liquibaseDataChangelog != null) {
            //here you can check if file exists...
            Map<String, String> liquibaseChangelogParameters = new HashMap<>();
            liquibaseChangelogParameters.put("liquibaseExternalDataChangelogPath", liquibaseDataChangelog);
            liquibase.setChangeLog(extendedLiquibaseChangelog);
            liquibase.setChangeLogParameters(liquibaseChangelogParameters);
        } else {
            liquibase.setChangeLog(defaultLiquibaseChangelog);
        }
        // Configure rest of liquibase here...
        // ...
        return liquibase;
    }
}

changelog.xml (liquibase.change-log)

<databaseChangeLog
        xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.3.xsd">

    <include relativeToChangelogFile="true" file="changelog-master.xml"/>

</databaseChangeLog>

changelog-with-external-data.xml ( liquibase.extended-change-log )

<databaseChangeLog
        xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.3.xsd">

    <include relativeToChangelogFile="true" file="changelog-master.xml"/>
    <include relativeToChangelogFile="false" file="${liquibaseDataChangelogPath}"/>

</databaseChangeLog>

Remember that having separate changelogs could be dangerous. You have to make sure that your changelogs are independent:

Included change-logs are run in the order they are found so care does need to be taken to make sure that the included changelogs are either completely independent, or that any required changelogs are run first.

like image 200
Maciej Marczuk Avatar answered Nov 14 '22 22:11

Maciej Marczuk