Flyway Spring Boot Autowired Beans with JPA Dependency - jpa

I am using Flyway 5.0.5 and I am unable to create a java (SpringJdbcMigration) with autowired properties... They end up null.
The closest thing I can find is this question: Spring beans are not injected in flyway java based migration
The answer mentions it being fixed in Flyway 5 but the links are dead.
What am I missing?

I struggled with this for a long time due to my JPA dependency. I am going to edit the title of my question slightly to reflect this...
#Autowired beans are instantiated from the ApplicationContext. We can create a different bean that is ApplicationContextAware and use that to "manually wire" our beans for use in migrations.
A quite clean approach can be found here. Unfortunately, this throws an uncaught exception (specifically, ApplicationContext is null) when using JPA. Luckily, we can solve this by using the #DependsOn annotation and force flyway to run after the ApplicationContext has been set.
First we'll need the SpringUtility from avehlies/spring-beans-flyway2 above.
package com.mypackage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
#Component
public class SpringUtility implements ApplicationContextAware {
#Autowired
private static ApplicationContext applicationContext;
public void setApplicationContext(final ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
}
/*
Get a class bean from the application context
*/
public static <T> T getBean(final Class clazz) {
return (T) applicationContext.getBean(clazz);
}
/*
Return the application context if necessary for anything else
*/
public static ApplicationContext getContext() {
return applicationContext;
}
}
Then, configure a flywayInitializer with a #DependsOn for springUtility. I extended the FlywayAutoConfiguration here hoping to keep the autoconfiguration functionality. This mostly seems to have worked for me, except that turning off flyway in my gradle.build file no longer works, so I had to add the #Profile("!integration") to prevent it from running during my tests. Other than that the autoconfiguration seems to work for me but admittedly I've only run one migration. Hopefully someone will correct me if I am wrong.
package com.mypackage;
import org.flywaydb.core.Flyway;
import org.springframework.boot.autoconfigure.flyway.FlywayMigrationInitializer;
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration.FlywayConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.DependsOn;
import com.mypackage.SpringUtility;
#Configuration
#Profile("!integration")
class MyFlywayConfiguration extends FlywayConfiguration {
#Primary
#Bean(name = "flywayInitializer")
#DependsOn("springUtility")
public FlywayMigrationInitializer flywayInitializer(Flyway flyway){
return super.flywayInitializer(flyway);
//return new FlywayMigrationInitializer(flyway, null);
}
}
And just to complete the example, here is a migration:
package db.migration;
import org.flywaydb.core.api.migration.spring.BaseSpringJdbcMigration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import com.mypackage.repository.AccountRepository;
import com.mypackage.domain.Account;
import com.mypackage.SpringUtility;
import java.util.List;
public class V2__account_name_ucase_firstname extends BaseSpringJdbcMigration {
private AccountRepository accountRepository = SpringUtility.getBean(AccountRepository.class);
public void migrate(JdbcTemplate jdbcTemplate) throws Exception {
List<Account> accounts = accountRepository.findAll();
for (Account account : accounts) {
String firstName = account.getFirstName();
account.setFirstName(firstName.substring(0, 1).toUpperCase() + firstName.substring(1));
account = accountRepository.save(account);
}
}
}
Thanks to avehlies on github, Andy Wilkinson on stack overflow and OldIMP on github for helping me along the way.
In case you are using more recent versions of Flyway, then extend BaseJavaMigration instead of BaseSpringJdbcMigration as the later is deprecated. Also, take a look at the below two comments by the user Wim Deblauwe.

The functionality hasn't made it into Flyway yet. It's being tracked by this issue. At the time of writing that issue is open and assigned to the 5.1.0 milestone.

Seems the updated answer provided by #mararn1618 is under documented on the official documentation, so I will provide a working setup here. Thanks to #mararn1618 for guiding in that direction.
Disclaimer, it's written in Kotlin :)
First you need a configuration for loading the migration classes, in Spring Boot (and perhaps Spring) you need either an implementation of FlywayConfigurationCustomizer or a setup of FlywayAutoConfiguration.FlywayConfiguration. Only the first is tested, but both should work
Configuration a, tested
import org.flywaydb.core.api.configuration.FluentConfiguration
import org.flywaydb.core.api.migration.JavaMigration
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.autoconfigure.flyway.FlywayConfigurationCustomizer
import org.springframework.context.ApplicationContext
import org.springframework.stereotype.Component
#Component
class MyFlywayConfiguration #Autowired constructor(
val applicationContext: ApplicationContext
) : FlywayConfigurationCustomizer {
override fun customize(configuration: FluentConfiguration?) {
val migrationBeans = applicationContext.getBeansOfType(JavaMigration::class.java)
val migrationBeansAsArray = migrationBeans.values.toTypedArray()
configuration?.javaMigrations(*migrationBeansAsArray)
}
}
Configuration option B, untested, but should also work
import org.flywaydb.core.api.migration.JavaMigration
import org.springframework.boot.autoconfigure.flyway.FlywayAutoConfiguration
import org.springframework.boot.autoconfigure.flyway.FlywayConfigurationCustomizer
import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
#Configuration
class MyFlywayConfiguration : FlywayAutoConfiguration.FlywayConfiguration() {
#Bean
fun flywayConfigurationCustomizer(applicationContext: ApplicationContext): FlywayConfigurationCustomizer {
return FlywayConfigurationCustomizer { flyway ->
val p = applicationContext.getBeansOfType(JavaMigration::class.java)
val v = p.values.toTypedArray()
flyway.javaMigrations(*v)
}
}
}
And with that you can just write your migrations as almost any other Spring bean:
import org.flywaydb.core.api.migration.BaseJavaMigration
import org.flywaydb.core.api.migration.Context
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
#Component
class V7_1__MyMigration #Autowired constructor(
) : BaseJavaMigration() {
override fun migrate(context: Context?) {
TODO("go crazy, mate, now you can import beans, but be aware of circular dependencies")
}
}
Side notes:
Be careful of circular dependencies, your migrations can most likely not depend on repositories (also makes sense, you are preparing them, after all)
Make sure your migrations are located where Spring scans for classes. So if you want to place them in the namespace db/migrations, you need to ensure that Spring scans that location
I haven't tested, but it's likely one should be cautious with mixing the path for these migrations and the locations where Flyway scans for migrations

Current flyway 6.5.5 version is released and back from 6.0.0 I believe support for spring beans is provided.
You can directly autowire spring beans into your Java based migrations (using #autowired), But the hunch is your Migration class also should be managed by Spring to resolve dependency.
There is a cool and simple way for it, by overriding default behavior of Flyway, check out https://reflectoring.io/database-migration-spring-boot-flyway/
the article clearly answers your question with code snippets.

If you are using deltaspike you can use BeanProvider to get a reference to your DAO.
Change your DAO code:
public static UserDao getInstance() {
return BeanProvider.getContextualReference(UserDao.class, false, new DaoLiteral());
}
Then in your migration method:
UserDao userdao = UserDao.getInstance();
And there you've got your reference.
(referenced from: Flyway Migration with java)

Related

Apache Camel + MongoDB documentation incorrect?

I'm reading the instructions on using findOneByQuery using Apache Camel + MongoDBs, and I think I'm doing exactly what it says:
import com.mongodb.client.model.Filters;
import deletion.manager.Constants;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mongodb.MongoDbConstants;
import org.springframework.stereotype.Component;
#Component
public class NotifyClientRoute extends RouteBuilder {
// ...
#Override
public void configure() throws Exception {
from("direct:findOneByQuery")
.setHeader(MongoDbConstants.CRITERIA, Filters.eq("name", "Raul Kripalani"))
.to("mongodb:myDb?database=flights&collection=tickets&operation=findOneByQuery")
.to("mock:resultFindOneByQuery");
I'm getting this compilation error:
Cannot resolve method 'setHeader(java.lang.String, org.bson.conversions.Bson)'
I've tried looking around for other/better examples, but I haven't been able to find any. The options for setHeader are:
#setHeader(String name)
#setHeader(String name, Expression expression)
#setHeader(String name, Supplier<Object>)
I'm pretty sure I'm importing correct classes. I'd rather not create a simple Bean class just so I can #Autowired a MongoTemplate, but that's looking like my only option right now. Any thoughts anyone?

Eclipse JUnit 5 SecruityException when running Tests

I think I may be the only one experiencing this issue.
I, today, updated my eclipse install to version 2020-03 (4.15.0). I am also attempting to write a very simple JUnit 5 test for a new method I'm working on.
When I run my test, right now just a basic stub, I get the following error:
java.lang.SecurityException: class "org.junit.platform.commons.PreconditionViolationException"'s signer information does not match signer information of other classes in the same package
at java.base/java.lang.ClassLoader.checkCerts(ClassLoader.java:1150)
at java.base/java.lang.ClassLoader.preDefineClass(ClassLoader.java:905)
at java.base/java.lang.ClassLoader.defineClass(ClassLoader.java:1014)
at java.base/java.security.SecureClassLoader.defineClass(SecureClassLoader.java:151)
at java.base/jdk.internal.loader.BuiltinClassLoader.defineClass(BuiltinClassLoader.java:821)
at java.base/jdk.internal.loader.BuiltinClassLoader.findClassOnClassPathOrNull(BuiltinClassLoader.java:719)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClassOrNull(BuiltinClassLoader.java:642)
at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:600)
at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:178)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:521)
at org.eclipse.jdt.internal.junit5.runner.JUnit5TestLoader.createUnfilteredTest(JUnit5TestLoader.java:75)
at org.eclipse.jdt.internal.junit5.runner.JUnit5TestLoader.createTest(JUnit5TestLoader.java:66)
at org.eclipse.jdt.internal.junit5.runner.JUnit5TestLoader.loadTests(JUnit5TestLoader.java:53)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:526)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:770)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:464)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:210)
I also see the following dialog
My run Configuration is:
I've tried all major junit-jupiter (aggregator) releases back to 5.5.0 all resulting in the same issue.
I've tried this solution. However, that question deals with a class not found issue. I also tried that same solution using using junit-platform-commons version 1.6.1. no change.
However, I can run maven configuration with -Dtest=DeaFileListTest test the the tests run.
My test case is simple, I instantiate an object that has the method I want to test and then my test.
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.not;
import java.io.IOException;
import java.util.List;
import javax.ws.rs.core.Response;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.mfgweb.FileRepo;
class DeaFileListTest {
private static FileRepo filerepo;
private static Response response;
#BeforeAll
static void setUpBeforeClass() throws Exception {
filerepo = new FileRepo();
response = filerepo.getDeaFiles();
}
#AfterAll
static void tearDownAfterClass() throws Exception {
response = null;
filerepo = null;
}
#Test
public void deaFileListIsNotEmptyTest() throws IOException {
#SuppressWarnings ( "unchecked" )
List< String > files = ( List< String > )response.getEntity();
assertThat( files, not( empty() ) );
}
}
So I am curious why I'm receiving the Security Exception when I run the test in eclipse, yet Maven seems to execute them fine.

Why Use PathVariable instead of PathParam?

Before marking this as duplicate, just wanted you guys to know I have checked out the question posted here:
What is the difference between #PathParam and #PathVariable
Thing is, if the usage of PathParam and PathVariable are same (only that one is from the JAX-RS API and one is provided by Spring), why is it that using one gives me null and the other gives me the proper value?
I am using Postman to invoke the service as:
http://localhost:8080/topic/2
(I'm very new to SpringBoot)
Using PathParam :
import javax.websocket.server.PathParam;
import org.apache.tomcat.util.json.ParseException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class TopicController {
#Autowired
TopicService topicService;
#RequestMapping(method=RequestMethod.GET,path="/topic/{id}")
public Topic getById(#PathParam("id") long id) throws ParseException {
return topicService.getTopicById(id); //-- here id comes as null (when id is declared as a wrapper type - Long, else it throws an error)
}
}
Using PathVariable:
#RestController
public class TopicController {
#Autowired
TopicService topicService;
#RequestMapping(method=RequestMethod.GET,path="/topic/{id}")
public Topic getById(#PathVariable("id") long id) throws ParseException {
return topicService.getTopicById(id); //-- here id comes as 2
}
}
I think the pathparam in your project is under javax.ws... This one is not what they talked about. It is used in websocket, which means it is not a http annotaiton. JBoss implement pathparam needs additional jars.

Spring Boot. Running liquibase changelog after jpa auto-dll tables generation on hsqldb

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
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.
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.
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);
}
You should use Liquibase for DDL statements too. It doesn't make sense to use it solely for DML statements and then use another solution for DDL. Liquibase is equally well suited for either. And Liquibase is especially well-suited for the case where you develop on one type of database and deploy to another. In fact Liquibase is database engine agnostic.
If you need to execute some SQL before Liquibase fires (like creating the schema where Liquibase itself lives) then you can use Pre-Liquibase, but that should only be for whatever it is that absolutely cannot be in Liquibase changelogs.
All in all: I would advice against using all of the following:
JPA ddl (meaning the spring.jpa.generate-ddl settings)
Hibernate ddl (meaning the spring.jpa.hibernate.ddl-auto setting)
DataSource initialization (meaning the spring.sql.init.mode setting)
when using Spring Boot and Liquibase. The above methods are not guaranteed to fire before Liquibase. When you have Liquibase you don't need any of the above methods.

Play framework 2 + JPA with multiple persistenceUnit

I'm struggling with Play and JPA in order to be able to use two different javax.persistence.Entity model associated to two different persistence units (needed to be able to connect to different DB - for example an Oracle and a MySQL db).
The problem come from the Transaction which is always bind to the default JPA persitenceUnit (see jpa.default option).
Here is two controller actions which show the solution I found to manually define the persistence :
package controllers;
import models.Company;
import models.User;
import play.db.jpa.JPA;
import play.db.jpa.Transactional;
import play.mvc.Controller;
import play.mvc.Result;
public class Application extends Controller {
//This method run with the otherPersistenceUnit
#Transactional(value="other")
public static Result test1() {
JPA.em().persist(new Company("MyCompany"));
//Transaction is run with the "defaultPersistenceUnit"
JPA.withTransaction(new play.libs.F.Callback0() {
#Override
public void invoke() throws Throwable {
JPA.em().persist(new User("Bobby"));
}
});
return ok();
}
//This action run with the otherPersistenceUnit
#Transactional
public static Result test2() {
JPA.em().persist(new User("Ryan"));
try {
JPA.withTransaction("other", false, new play.libs.F.Function0<Void>() {
public Void apply() throws Throwable {
JPA.em().persist(new Company("YourCompany"));
return null;
}
});
} catch (Throwable throwable) {
throw new RuntimeException(throwable);
}
return ok();
}
}
This solution doesn't seem to be really "clean". I'd like to know if you know a better way to avoid the need to manually modify the transaction used.
For this purpose, I created a repo on git with a working sample application which shows how I configured the project.
https://github.com/cm0s/play2-jpa-multiple-persistenceunit
Thank you for your help
i met the same problem, too. too many advices are about PersistenceUnit annotation or getJPAConfig. but both them seem not work in play framework.
i found out a method which works well in my projects. maybe you can try it.
playframework2 how to open multi-datasource configuration with jpa
gud luk!