I have a Spring project, and I need to configure Flyway.
Using the default FlywayAutoConfiguration, Flyway migration is immediatly executed before everything else (caching, PostConstruct annotations, services). This is the behavior I expected (in term or startup workflow)
Unfortunatly, I need to override the default FlywayAutoConfiguration because I use a custom Flyway implementation, but that's not the my main problem here, my issue is really related to Spring Priority for configuration and initialization sequence.
So do use my own flyway, I first copied FlywayAutoConfiguration to my maven module, name it CustomFlywayAutoConfiguration and just adapt imports. I also change the property "spring.flyway.enabled" to false and create another one "spring.flywaycustom.enabled" to be able to activate mine and not the default one.
Doing that fully change the sequence of startup. Now, flyway is executed at the end of the startup sequence (after caching and other #PostConstruct that are in my project)
The following bean defined in CustomFlywayAutoConfiguration is now created onyl at the end of the startup sequence. With the default FlywayAutoConfiguration , was well created on the beginning.
#Bean
#ConditionalOnMissingBean
public FlywayMigrationInitializer flywayInitializer(Flyway flyway,
ObjectProvider<FlywayMigrationStrategy> migrationStrategy) {
return new FlywayMigrationInitializer(flyway, migrationStrategy.getIfAvailable());
}
I tryied to play a lot with ordering (HIGHEST_PRECEDENCE, and LOWEST_PRECEDENCE)
#AutoConfigureOrder on configuration class
#Order on components
Try to Autowire FlywayMigrationInitializer to force the bean initialisation earlier
It's doesn't change anything, looks like Spring ignore #Order and #AutoConfigureOrder
Any idea why when Configuration is inside spring-boot-autoconfigure dependencies, it started as first priority, and when the same Configuration code is inside my project, I don't have the same ordering?
Thank you so much for your help.
Thanks to your answer, Focusin my attention FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor help me to solve the issue.
#Configuration(proxyBeanMethods = false)
#AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
#EnableConfigurationProperties({ DataSourceProperties.class, FlywayProperties.class })
#Import({ FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor.class }) // Very important to force flyway before
public class CustomFlywayConfiguration {
#Bean
public Flyway flyway(FlywayProperties properties, DataSourceProperties dataSourceProperties,
ResourceLoader resourceLoader, ObjectProvider<DataSource> dataSource,
#FlywayDataSource ObjectProvider<DataSource> flywayDataSource,
ObjectProvider<FlywayConfigurationCustomizer> fluentConfigurationCustomizers,
ObjectProvider<JavaMigration> javaMigrations, ObjectProvider<Callback> callbacks) {
FluentConfiguration configuration = new FluentConfiguration(resourceLoader.getClassLoader());
DataSource dataSourceToMigrate = configureDataSource(configuration, properties, dataSourceProperties, flywayDataSource.getIfAvailable(), dataSource.getIfUnique());
checkLocationExists(dataSourceToMigrate, properties, resourceLoader);
configureProperties(configuration, properties);
List<Callback> orderedCallbacks = callbacks.orderedStream().collect(Collectors.toList());
configureCallbacks(configuration, orderedCallbacks);
fluentConfigurationCustomizers.orderedStream().forEach((customizer) -> customizer.customize(configuration));
configureFlywayCallbacks(configuration, orderedCallbacks);
List<JavaMigration> migrations = javaMigrations.stream().collect(Collectors.toList());
configureJavaMigrations(configuration, migrations);
return new CustomFlyway(configuration);
}
/**
* FlywayAutoConfiguration.FlywayConfiguration is conditioned to the missing flyway bean. #Import annotation are not executed in this case.
*
* So if we declare our own Flyway bean, we also need to create this bean to trigger the flyway migration.
*
* The main issue is now that bean is create after the #PostConstruct init method in MyInitComponent.
*
*/
#Bean
public FlywayMigrationInitializer flywayInitializer(Flyway flyway,
ObjectProvider<FlywayMigrationStrategy> migrationStrategy) {
return new FlywayMigrationInitializer(flyway, migrationStrategy.getIfAvailable());
}
/**
* Thanks to that, it's now working, because make sure it's required before to create EntityManager
*/
// #ConditionalOnClass(LocalContainerEntityManagerFactoryBean.class)
// #ConditionalOnBean(AbstractEntityManagerFactoryBean.class)
static class FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor
extends EntityManagerFactoryDependsOnPostProcessor {
FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor() {
super(FlywayMigrationInitializer.class);
}
}
...
So that confirm we can't easily override the Flyway bean without copy some logic from FlywayAutoConfiguration.
I created a small project to reproduce the bug, with the solution I found.
https://github.com/w3blogfr/flyway-issue-demo
I don't know if it's necessary to fix it or not in spring auto-configuration project?
I checked the history to find back the commit
https://github.com/spring-projects/spring-boot/commit/795303d6676c163af899e87364846d9763055cf8
And the ticket was this one.
https://github.com/spring-projects/spring-boot/issues/18382
The change looks technical and probably he missed that was an #ConditionalOnClass
None of the annotations that you've described have an impact on runtime semantics:
#AutoConfigureOrder works only on auto-configurations (so if you've copied the auto-configuration as a user config, it won't even be considered). This is used to order how auto-configurations are parsed (typical use case: make sure an auto-config contribute a bean definition of type X before another one check if a bean X is available).
#Order orders components of the same type. Doesn't have any effect as to "when" something occurs
Forcing initialisation using an injection point is a good idea but it'll only work if the component you're injecting it into is initialised itself at the right time.
The auto-configuration has a bunch of post-processors that create links between various components that use the DataSource and the FlywayMigrationInitializer. For instance, FlywayMigrationInitializerEntityManagerFactoryDependsOnPostProcessor makes sure that FlywayMigrationIntializer is a dependency of the entity manager so that the database is migrated before the EntityManager is made available to other components. That link is what's making sure Flyway is executed at the right time. I don't know why that's not working with your copy, a sample we can run ourselves shared on GitHub could help us figure out.
With all that said, please don't copy an auto-configuration in your own project. I'd advise to describe your use case in more details and open an issue so that we consider improving the auto-configuration.
I tried to make a #ConfigurationProperties bean without setters so I used #ConstructorBinding. Let's call this class PropertiesFromYml.java.
This works fine, bean is created perfectly by the properties from the yml file.
The configuration class has the necessary setup:
#Configuration
#ConfigurationPropertiesScan("com.my.package") // this is where PropertiesFromYml.java is stored
public class MyConfig
I wanted to add the #StepScope to that PropertiesFromYml.java because my SpringBoot application will run scheduled batch jobs, so I don't want to initiate this class, but only when the job needs it.
BUT: Unfortunately, the bean is always created during startup. I guess this is because of the #ConfigurationPropertiesScan annotation on MyConfig.java class. But how can I tell to create the bean only when necessary, like with #StepScope?
Configuration will be created at the start of your application. Instead what you may want to do is split your class into 2. The actual configuration part and the schedule part: SchedularConfiguration, SchedularComponent. And then inject the configuration into the component.
#StepScope
#Component
class SchedularComponent {
#Autowired
SchedularConfiguration config;
}
That will allow you to have the scope you want. Usually you wouldn't have a whole other class for configuration of a component (but your requirements may vary) and use directly the #Value annotation to select what application property you want: #Value("${schedular.cron}").
I'm looking for a way to follow source of spring configuration from annotation.
E.g. Having below Bean is any way to e.g. click on my-components-service.books.configurations and be redirect or list yaml files which contains config which would be injected in runtime?
#Bean
#ConfigurationProperties(prefix = "my-components-service.books.configurations")
Map<ComponentType, BooksConfiguration> booksConfiguration() {
return new HashMap<>();
}
If #ConfigurationProperties is at the class level then there should be some gutter icons that will show where the properties have been set.
It doesn't look like this works when it's specified on a #Bean like in your example however. A possible workaround is to use a nested #Configuration class, though that may be undesirable.
I have multitenant database in Spring Boot. I store multi spring JDBC templates (based on tomcat Data Sources, configured manually) in map (immutable bean). And I choose proper data source based on uuid in a request (connection pool per database). I have disabled standard configuration in Spring Boot by:
#SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
What is the proper way of transaction manager configuration? With single data source I can use PlatformTransactionManager, but how it should be done with multiple jdbc templates/data sources in spring? It would be the best if I could set everything dynamically. Thanks in advance.
Here a solution for using multiple datasources
http://www.baeldung.com/spring-data-jpa-multiple-databases
Configure Two DataSources
If you need to configure multiple data sources, you can apply the same tricks that are described in the previous section. You must, however, mark one of the DataSource #Primary as various auto-configurations down the road expect to be able to get one by type.
If you create your own DataSource, the auto-configuration will back off. In the example below, we provide the exact same features set than what the auto-configuration provides on the primary data source
#Bean
#Primary
#ConfigurationProperties("app.datasource.foo")
public DataSourceProperties fooDataSourceProperties() {
return new DataSourceProperties();
}
#Bean
#Primary
#ConfigurationProperties("app.datasource.foo")
public DataSource fooDataSource() {
return fooDataSourceProperties().initializeDataSourceBuilder().build();
}
#Bean
#ConfigurationProperties("app.datasource.bar")
public BasicDataSource barDataSource() {
return (BasicDataSource) DataSourceBuilder.create()
.type(BasicDataSource.class).build();
}
fooDataSourceProperties has to be flagged #Primary so that the database initializer feature uses your copy (should you use that).
app.datasource.foo.type=com.zaxxer.hikari.HikariDataSource
app.datasource.foo.maximum-pool-size=30
app.datasource.bar.url=jdbc:mysql://localhost/test
app.datasource.bar.username=dbuser
app.datasource.bar.password=dbpass
app.datasource.bar.max-total=30
I want to use SpringBoot with Ebean. I found this article: http://ebean-orm.github.io/docs/setup/spring and I could set it up and make it work with an own implementation of a EbeanServerFactory as shown in the article.
It states, that if I add ebean-spring to my dependencies along with a default-ebean-server.xml than it should work with a default EbeanServerFactoryBean. But what should I write to this file? Where do I set up the FactoryBean to use my datasource etc.? Sorry if my question is silly, but I am really new to SpringBoot and don't understand it deeply.
If I add ebean-spring and remove my own factory I get an error:
No qualifying bean of type [com.avaje.ebean.EbeanServer] found for dependency
So I could solve it after a day of thinking, and trying. In Spring you usually have an Application.java or something which starts your app with your main(). Here you can define a EbeanServer Factory like the following:
#Bean
public EbeanServerFactoryBean ebeanServerFactoryBean() {
EbeanServerFactoryBean ebeanServerFactoryBean = new EbeanServerFactoryBean();
ServerConfig config = new ServerConfig();
config.setName("pg");
config.loadFromProperties();
//other configs
config.setDefaultServer(true);
ebeanServerFactoryBean.setServerConfig(config);
return ebeanServerFactoryBean;
}