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

Restore run order of data.sql/schema.sql against high-level database migration tools #26692

Closed
michael-simons opened this issue May 28, 2021 · 7 comments
Assignees
Labels
type: regression A regression from a previous release
Milestone

Comments

@michael-simons
Copy link
Contributor

michael-simons commented May 28, 2021

Spring Boot 2.5 changed the order in which high-level database migration tools like Flyway and Liquibase and the Spring specific data.sql and schema.sql are run.

Prior to 2.5 one was able to define a schema via Flyway and add an additional data.sql to populate the scheme. While this is not that useful in production, it is very valuable during tests.

For example, here's a schema from Flyway: https://github.com/michael-simons/biking2/tree/3d753e6c1dee236c81170ce4933223ce1379d2e8/src/main/resources/db/migration

In the test resources is a data.sql https://github.com/michael-simons/biking2/blob/5694e1c85f4df0961a106f586c3911954a99ab15/src/test/resources/data.sql which will be picked up via @DataJpaTest.

This works because first Flyway (or Liquibase) runs, create the schema and then the inserts are run.

While I completely agree that having a scheme.sql AND a migration tool doesn't make sense or even is harmful, combining migrations and data isn't.

Together with spring.datasource.initialization-mode=never (see this test one was able to selectively disable any additional data with one simple property.

In the case of Flyway (and my small example (I do have more, I like that pattern)), I can rework this basically with that idea

mkdir -p src/test/resources/db/migration/ && mv src/test/resources/data.sql src/test/resources/db/migration/R__add-test-data.sql

using a repeatable migration, but that is brittle and will start to cause issues when there are repeatables in production already, as one has to fiddle with their ordering.

So my suggestion is to restore the old order or provide an additional property like spring.jpa.defer-datasource-initialization but for Flyway/Liquibase with the same result.

I also hope you do reconsider this plan stated in the 2.5 docs:

If you are using a Higher-level Database Migration Tool, like Flyway or Liquibase, you should use them alone to create and initialize the schema. Using the basic schema.sql and data.sql scripts alongside Flyway or Liquibase is not recommended and support will be removed in a future release.

Thank you.

Edit: For Flyway there is an actual solution via Callbacks: https://flywaydb.org/documentation/concepts/callbacks (Via Richard on Twitter)

Here's my (final) solution: michael-simons/biking2@3dc263d The use case described above can be solved with Flyway alone. I don't know about Liquibase enough to propose something with it.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged label May 28, 2021
@philwebb philwebb added the for: team-meeting An issue we'd like to discuss as a team to make progress label May 28, 2021
@philwebb philwebb self-assigned this Jun 7, 2021
@philwebb
Copy link
Member

philwebb commented Jun 7, 2021

We talked about this issue today and decided that we'd like to restore the old order (if it's not too hard) so that upgrades are easier.

We don't want to remove the documentation note since we still think it's best not to mix technologies but we'll try to add a how-to note describing test-only migrations (see #26796).

@philwebb philwebb changed the title Consider restoring the old behavior / order of data.sql and high-level database migration tools. Restore run order of data.sql/schema.sql against high-level database migration tools Jun 7, 2021
@philwebb philwebb added type: regression A regression from a previous release and removed for: team-meeting An issue we'd like to discuss as a team to make progress status: waiting-for-triage An issue we've not yet triaged labels Jun 7, 2021
@philwebb philwebb added this to the 2.5.x milestone Jun 7, 2021
philwebb added a commit that referenced this issue Jun 8, 2021
Update `DatabaseInitializationDependencyConfigurer` so that depends-on
ordering is applied based on the `DatabaseInitializerDetector` order.

Prior to this commit, if multiple DatabaseInitializer beans were
detected the order in which they were initialized was not defined.

See gh-26692
@philwebb philwebb modified the milestones: 2.5.x, 2.5.1 Jun 8, 2021
@gbrehmer
Copy link

gbrehmer commented Jun 13, 2021

Probably this change has broken our build test pipeline, because it was working with <= 2.5.0. we use schema.sql to prepopulate our test db. this sql file is generated by a nightly job based on the current master branch, an empty test db and all liquibase diff scripts. Then in a feature branch schema.sql is executed first and after that only new(!) liquibase diff files must be executed. So its used to reduce test run time.
Now with 2.5.1 schema.sql file is executed after(!) liquibase. So all tests are failing, because schema.sql contains most of the changes which are already applied by liquibase due to the order change.
It is possible to restore old <= 2.5.0 order? (additional configuration is okay)

@felix4321
Copy link

@gbrehmer I'm having exactly the same problem.

@wilkinsona wilkinsona added for: team-meeting An issue we'd like to discuss as a team to make progress and removed for: team-meeting An issue we'd like to discuss as a team to make progress labels Jun 14, 2021
@wilkinsona
Copy link
Member

wilkinsona commented Jun 14, 2021

Sorry for the inconvience. This only really worked in 2.4 and earlier by accident rather than design. We used to document that it wouldn't work at all, but refined that to more accurately reflect the (rather complex) reality. Since that update in Spring Boot 2.3.x, the documentation has said the following:

If you are using a Higher-level Database Migration Tool, like Flyway or Liquibase, you should use them alone to create and initialize the schema. Using the basic schema.sql and data.sql scripts alongside Flyway or Liquibase is not recommended and support will be removed in a future release.

In short, you should move away from using both schema.sql and Liquibase at the same time with any version of Spring Boot as soon as you can. In the meantime, we plan to make a small change that will allow you to fine tune database initialization ordering with a bean factory post-processor of your own:

@Bean
@Order(Ordered.LOWEST_PRECEDENCE)
static BeanFactoryPostProcessor databaseInitializationReordering() {
    return (beanFactory) -> {
        modifyDependencies(beanFactory.getBeanDefinition("liquibase"),
                (dependencies) -> dependencies.add("dataSourceScriptDatabaseInitializer"));
        modifyDependencies(beanFactory.getBeanDefinition("dataSourceScriptDatabaseInitializer"),
                (dependencies) -> dependencies.remove("liquibase"));
    };
}

static void modifyDependencies(BeanDefinition definition, Consumer<Set<String>> modifier) {
    String[] dependsOn = definition.getDependsOn();
    Set<String> dependencies = new LinkedHashSet<>(
            dependsOn != null ? Arrays.asList(dependsOn) : Collections.emptySet());
    modifier.accept(dependencies);
    definition.setDependsOn(dependencies.toArray(new String[0]));
}

This'll make it possible to restore 2.4.x's ordering with 2.5 but it should only be relied upon in the short-term and, as I said above, you should move to using a single database initialization tool as soon as you can.

@gbrehmer
Copy link

Ok so we need a new solution to reduce test duration. and that will probably be a difficult task
we currently keep all db-changesets since beginning (e.g. for documentation)

@philwebb
Copy link
Member

Perhaps you can write a JUnit test extension that could prepare your database before the Spring ApplicationContext is initialized. The actual code used to run the script is pretty minimal. You might even be able to use the @Sql annotation from org.springframework.test.context.jdbc to do this already (although I'm not sure exactly when those scripts run).

@gbrehmer
Copy link

gbrehmer commented Jul 1, 2021

@philwebb thanks for the hints. @Sql was not working but I found a pretty simple solution for H2

you can add init script to h2 connection string: ;INIT=RUNSCRIPT FROM './src/test/resources/schema.sql' and if you want to use the schema.sql filename you have to disable the auto init feature with spring.sql.init.enabled: false

Because init command is called for every connection, it should only be added to the liquibase url spring.liquibase.url
Otherwise it will produce problems e.g. in async threads or nested transactions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: regression A regression from a previous release
Projects
None yet
Development

No branches or pull requests

6 participants