DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workloads.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Advanced Maintenance of a Multi-Database Citus Cluster With Flyway
  • Spring Boot Sample Application Part 1: Introduction and Configuration
  • Live Database Migration
  • The Hidden Facets of SQL Database Migration You're Probably Overlooking

Trending

  • Unmasking Entity-Based Data Masking: Best Practices 2025
  • Scaling DevOps With NGINX Caching: Reducing Latency and Backend Load
  • Accelerating Debugging in Integration Testing: An Efficient Search-Based Workflow for Impact Localization
  • From Zero to Production: Best Practices for Scaling LLMs in the Enterprise
  1. DZone
  2. Data Engineering
  3. Databases
  4. How To Use Flyway for Database Migration in Spring Boot

How To Use Flyway for Database Migration in Spring Boot

In this article, take a look at how to use Flyway to manage a SQL database schema in a Spring Boot application.

By 
Seun Matt user avatar
Seun Matt
DZone Core CORE ·
Aug. 08, 23 · Tutorial
Likes (3)
Comment
Save
Tweet
Share
15.9K Views

Join the DZone community and get the full member experience.

Join For Free

In this article, we're going to look at how we can use Flyway to manage our SQL database schema in a Spring Boot application.

Flyway is a database migration tool that offers capabilities for migration history and rollback and allows us to separate the database schema-related layer from the database entity layer of an application.

Application Setup

The Spring Boot application we will be using can be generated using this Spring Initializr link. It contains all the necessary dependencies.

After downloading the application and resolving the dependencies, we will create a new Postgres database called spring-boot-flyway and configure the application to connect to it.

Listing 2.1 application.properties:

Properties files
 
spring.datasource.url=jdbc:postgresql://localhost:5432/spring-boot-flyway
spring.datasource.username=demo
spring.datasource.password=demo


By default, Flyway will search the db/migration/ directory in the classpath for migration files containing SQL statements to manage database tables and records.

For older versions of the library, we may need to create an empty text file called .keep in resources/db/migration/ to ensure the directory is compiled and available during application start-up to avoid errors.

Having done that, we can now start the application and it should run successfully.

Basic Usage

The way Flyway works is that we create a migration file in the resources/db/migration directory and Spring Boot will automatically execute the migration script because we've added the Flyway dependencies to the classpath in section 2.

Listing 3.1 V1__Users.sql:

SQL
 
CREATE TABLE IF NOT EXISTS users
(
    id    SERIAL,
    email VARCHAR(200) NOT NULL,
    name  VARCHAR(200) NOT NULL,
    PRIMARY KEY (id)
);


Let's take a minute to examine the snippet in Listing 3.1. The file name, V1__Users.sql, follows a certain convention:

  • The "V" indicates that this is a versioned migration.
  • The "1" that follows the V is the actual version number. It can also be "V1_1", which will translate to version 1.1.
  • This is followed by the separator "__" (two underscores). This separates the version information from the name of the migration file, that is, Users, in this case.
  • The last part ".sql" is the extension; thus, the file contains a plain SQL statement.

At this point, restarting the application will create the users table in the database. Furthermore, we can see there's another table that we did not explicitly create - flyway_schema_history.

The flyway_schema_history is used by Flyway itself to track the migration that has been applied. If the table is missing, Flyway will assume we're initializing the database for the first time and run all the migrations in order of their version number.

When the flyway_schema_history table is present, Flyway will only apply newer migration files that have not been applied before. This means that in order for us to add new tables, we only need to create newer migration files with an updated version number and restart the application.

In addition to using SQL, we can also write our migration script in Java. In the Java migration style, our migration files are Java classes and must extend the abstract BaseJavaMigration class and implement the migrate method.

IDEs typically don't expect Java classes to be in the resources directory and so we're going to create a new package called db/migration in src/main/java. It is very important to know that this new package db/migration should be in the src/main/java directory.

Let's create a new Java migration to add a new table:

Listing 3.2 V2__Posts.java:

Java
 
public class V2__Posts extends BaseJavaMigration {
    @Override
    public void migrate(Context context) throws Exception {
        var sql = """
                CREATE TABLE posts (
                     id SERIAL,
                     author_id INT NOT NULL,
                     post TEXT NOT NULL,
                     created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                     updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
                     PRIMARY KEY (id)
                );
                """;
        try(var statement = context.getConnection().createStatement()) {
            statement.execute(sql);
        }
    }
}


The advantage of using Java migration over SQL files is that we can add custom logic, conditions, and validations that will not be possible with plain SQL. For example, we can check if another table exists or get certain values from the environment, etc.

As you're probably guessing now, yes, it is possible to mix both SQL and Java-style migration in the same codebase, as long as we ensure the Flyway location is the same in both cases.

Flyway Configuration and Customization

Hitherto, we've been working with the default Flyway behavior. We can further tweak Flyway to suit our needs. For example, we can change the default location for migration files, configure the database schema (a.k.a., tablespace), change the SQL migration prefix from "V" to whatever we want and so much more.

In the configuration below, we configure the path where the migration files are located and disable cleaning the database (i.e., dropping all tables) to prevent accidental use in a production environment.

Listing 4.1 application.properties:

Properties files
 
spring.flyway.locations=classpath:migrations
spring.flyway.clean-disabled=true


There are other configurable properties under the spring.flyway key that we can use to fine-tune the behavior of the library. Also, we can consult the Flyway documentation page for reference.

Flyway Callbacks

Flyway provides us with the ability to configure callbacks that can be invoked at different stages of the migration process. The callback mechanism is a handy way of performing certain actions at different phases of the migration life cycle. 

Let's say we have some default data that we want to seed when our application is starting up. We can simply create a callback that supports the AFTER_MIGRATE event.

Listing 5.1 FlywayDatabaseSeeder.java:

Java
 
public class FlywayDatabaseSeeder implements Callback {

    @Override
    public boolean supports(Event event, Context context) {
        return event.name().equals(Event.AFTER_MIGRATE.name());
    }
    
    @Override
    public void handle(Event event, Context context) {
        try(var statement = context.getConnection().createStatement()) {

            var ADMIN_EMAIL = "superadmin@example.com";
            
            var checkQuery = "SELECT id FROM users WHERE email = %s"
                    .formatted(ADMIN_EMAIL);
            
            statement.execute(checkQuery);
            ResultSet resultSet = statement.getResultSet();
            resultSet.last();

            //return if the seeder has already been executed
            if(resultSet.getRow() >= 0) return;

            var sql = """
                    INSERT INTO users (email, name) VALUES
                    ('%s', 'Super Admin')
                    """.formatted(ADMIN_EMAIL);
            
            statement.execute(sql);
            
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public boolean canHandleInTransaction(Event event, Context context) {
        return true;
    }

    @Override
    public String getCallbackName() {
        return FlywayDatabaseSeeder.class.getName();
    }
}


In the listing above, in the supports method, we declared that this callback should only be executed for the AFTER_MIGRATE event and in the handle method, we outlined the logic for inserting the default super admin user if it does not exist already.

Before this can work, we need to register the callback class with Flyway in SpringBoot. We do this by creating a FlywayMigrationStrategy bean.

Listing 5.2 SpringBootFlywayApplication.java:

Java
 
@Bean
public FlywayMigrationStrategy flywayMigrationStrategy() {
	return (flywayOld) -> {

		/*
		 Update the existing autoconfigured Flyway
		 bean to include our callback class
		*/
		Flyway flyway = Flyway.configure()
				.configuration(flywayOld.getConfiguration())
				.callbacks(new FlywayDatabaseSeeder())
				.load();

		flyway.migrate();

	};
}


There are other events in the org.flywaydb.core.api.callback.Event enum that we can configure our Callback classes to support. For example, you can have a callback that'll support the AFTER_MIGRATE_ERROR event and send a Slack notification to alert the engineers.

Tips and Tricks

While developing in your local environment, you can delete the entry for a migration from the flyway_schema_history table.

Next time you start your application, the migration for which you deleted its history will be executed again. This way, you can correct errors or update your schema while still developing on your local machine without dropping the entire database.

Additionally, in SpringBoot, you can control when Flyway should execute the migration script on application start-up. For example, let's say we don't want the migration to be automatically executed on our local environment. We can do the following:

Listing 6.1 SpringBootFlywayApplication.java:

Java
 
@Bean
public FlywayMigrationStrategy flywayMigrationStrategy(@Value("${spring.profiles.active}") String activeProfile) {
	return (flywayOld) -> {

		/*
		 Update the existing autoconfigured Flyway
		 bean to include our callback class
		*/
		Flyway flyway = Flyway.configure()
				.configuration(flywayOld.getConfiguration())
				.callbacks(new FlywayDatabaseSeeder())
				.load();

		if(!"local".equalsIgnoreCase(activeProfile)) {
			flyway.migrate();
		}

	};
}


Conclusion

One of the advantages of using a database migration tool is that it makes the database schema a part of the application codebase. This makes it easier to track changes to the database over time as there's a central reference point in the application.

The complete source code is available on GitHub, Happy coding.

Flyway (software) Schema Spring Boot sql Schema migration

Published at DZone with permission of Seun Matt. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Advanced Maintenance of a Multi-Database Citus Cluster With Flyway
  • Spring Boot Sample Application Part 1: Introduction and Configuration
  • Live Database Migration
  • The Hidden Facets of SQL Database Migration You're Probably Overlooking

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!