Integrating Flyway with Java EE and using its datasource

Flyway is my favourite tool when it comes to database migrations because its convention over configuration approach makes it fairly easy to use while bootstrapping and configuration is reduced to a minimum. For those of you who don’t know anything about database migration tools (evolutionary database design) in short the concept is to track SQL scripts with your source code which reflect the actual version of your database that matches the code version to run properly. This could be done without any framework but what tools like Flyway or liquibase bring to the table is the ability to check automatically at build- or start-time if the database is at the latest available version and if not run all available SQL scripts from the actual database version to the newest one available. Flyway can be used with all the major SQL dialects, many different relational databases and has plugins for a huge variety of frameworks to be integrated into. For more detailed information visit the projects website.

As I’m a heavy Java EE user I ran into the problem that Java EE has no special support by Flyway. My problems with this are that there is no build in way to get the actual JPA datasource (defined in the persistence.xml) and there is no solution provided that wires Flyway in the startup process to run the migration scripts at startup. Flyway provides integration plugins with this functionalities for other frameworks like Spring Boot but fortunately it is not that complicated to realise the same thing in Java EE.

In many scenarios it is no problem to trigger Flyway in the right moment as it is possible to integrate it in the maven build process or start it manually via the command line but both solutions doesn’t fit my needs. I want to have Flyway check my database at startup as the production server is not the server where the build process happens and I want to avoid a situation where I have to execute a shell command manually after deploying a new version. For this cases Flyway can be started from the source code as well. As it has to be run before the application starts and tries using the database you have to make sure that the Flyway scripts are run before that. I found two solutions to make this work. The first one uses Hibernates “Service Provider Interface” (SPI) -I use WildFly in this example which comes with Hibernate- to register a new integrator and grab the datasource via a little bit of reflection code while the second approach (which is my favourited solution) defines a new singleton bean which uses @resource injection to get the datasource.

The interesting part is in the lines 16-18. While the general connection interface doesn’t provide a possibility to get the actually used datasource, the Hibernate implementation of this interface comes with a getDatasource method which can be used via reflection as shown above. When we have the datasource the rest is very straightforward. The datasource is given to Flyway, some log statements are generated and flyway.migrate() triggers the migration on the actual datasource.

The advantage of this solution is that you don’t need to specify the datasource you want to use because you will get it per reflection. The downside is that you are highly dependent to Hibernate and its internal API which could and has been changed over time.

Singleton EJB solution

This approach uses just the mechanisms Java EE provides and no private APIs

The annotation @Singleton tells the container to instantiate the class as an EJB-Singleton which means that there is only one instance of this class. The problem with @Singleton is that the EJB container can define when to create it. The additional @Startup annotation tells the container to create it before any other invocation of the application is triggered. The @TransactionManagement annotation is necessary because the default behaviour of an EJB is to let the container manage transactions via JTA (Java Transaction API). This is not possible in this use case because Flyway internally manages its own transactions therefore the transaction management is delegated to the bean for this class.
In the lines 9 and 10 the datasource is injected via the @Resource annotation. This just works with the default datasource. If you are in a scenario where you have multiple datasources defined you can inject the datasource by name and switch the datasource name dynamically by using the maven resources plugin (as described here). Just insert a placeholder instead of the real datasource name for this to work.

Java

1

2

@Resource(name="${datasource.name}")

privateDataSource dataSource;

The @PostConstruct annotation is part of the EJB lifecycle and is triggered after the dependency injection is done. The rest is similar to the previous solution. The datasource is set in Flyway and the migration is called.

The charm of this solution is that there are no dependencies to a special ORM or application server and that there is no internal API used. When everything is done via standard Java EE APIs you achieve high portability and stability. This demonstration source code can be checked out on my GitHub account in the project flywayIntegrations/flywayEjbIntegration.

3 Comments

Thanks for this enlightening article.I tried @Resource(name = “${datasource.name}”)
and got the following error in wildfly. How can I solve the problem.
“WFLYCTL0412: Required services that are not installed:” => [“jboss.naming.context.java.module.\”Unduire-1.0-SNAPSHOT\”.\”Unduire-1.0-SNAPSHOT\”.env.\”${datasource.name}\””],
“WFLYCTL0180: Services with missing/unavailable dependencies” => [“jboss.naming.context.java.module.\”Unduire-1.0-SNAPSHOT\”.\”Unduire-1.0-SNAPSHOT\”.env.\”com.karau.unduire.migration.FlywayIntegrator\”.dataSource is missing [jboss.naming.context.java.module.\”Unduire-1.0-SNAPSHOT\”.\”Unduire-1.0-SNAPSHOT\”.env.\”${datasource.name}\”]”]

when you use the placeholder ${datasource.name} you have to replace it at buildtime for example via maven like I described here. If you don’t want to do that you need to replace it manually with your concrete datasources name.