Schema Migration with Hibernate and FlywayDB

    Vlad Mihalcea
    Vlad Mihalcea
    Share

    This tutorial explains the benefits of using an automated schema migration tool such as FlywayDB, as well as the limitations of the Hibernate schema generation utility (hbm2ddl). The article assumes almost no knowledge of Hibernate, and it can be read by both junior and senior developers alike.

    Introduction

    In a relational database, data is stored in table rows, and relations are expressed through a foreign key. While the database is responsible for storing and retrieving data, the business logic is usually embedded in the application layer. When using an object-oriented programming language, the object graph becomes the most natural representation of data. The discrepancy between graph-like structures and database tuples is typically known as the object-relational impedance mismatch.

    The object-relational mapping (ORM) pattern aims to address this problem, and Hibernate ORM is undoubtedly one of the most successful implementations of this pattern. However, Hibernate is more than an ORM framework that conforms to the Java Persistence API specification.

    Hibernate is a data access framework implementing many patterns that have been popularized by Martin Fowler in his book Patterns of Enterprise Application Architecture. For instance, aside from the typical object-relational mapping feature, Hibernate also implements the following data access patterns:

    Data Model Duality

    While the ORM tool allows us to translate the object graph state changes into SQL statements, we are still managing two distinct data models. On one side, the database schema defines the rules for how data is ought to be stored, whereas on the application side, the domain model is an object-graph mirror of the underlying database schema.

    So, who is in charge of driving the model representation? Is it the database or the domain model?

    Schema ownership

    Hibernate has long been offering a schema generation tool which takes the entity mappings and translates them to an actual database schema. This tool is very useful for testing. For instance, Hibernate unit tests rely on it to generate a new database schema prior to running the test suite, only to drop every newly created database structure after the tests have been run.

    Aside from creating and dropping the schema, this tool offers an update option which is supposed to generate a DDL script that is capable of migrating an old database schema to a newer version given by the current entity structure.

    However, using hbm2ddl’s automatic schema generation for a production environment is not recommended because it does not support every database-specific DDL structure (like partial indexes, user-defined types, triggers, checks, etc.). So as soon as a database schema takes advantage of an advanced DDL feature, schema migration must also use them and this can only be done through manually created incremental scripts.

    The schema ownership goes to the database, and the application-level domain model is mapped to the underlying database structures.

    schema-migration

    Schema migration

    Just like the code base evolves, so does the database schema. In fact, the scripts that generate the database schema are source code as well, and all the source code resides in a Version Control System, like Git.

    The database schema evolution must be captured by incremental schema migration scripts. Every time the database schema must undergo a certain change, a new schema migration script is added.

    The beauty of having these schema migration scripts is that we can automate the whole application deployment. For this purpose, we need a tool to automate the database schema migration scripts deployment process.

    In Java, there are two automatic schema migration options: Liquibase and FlywayDB. In this article, I’m going to show you how easy you can integrate FlywayDB in a Spring and Hibernate enterprise application.

    First, the schema migration scripts must reside in the src/main/resources folder:

    resources
      flyway
        db
          postgresql
            migration
              V1_0__initial_script.sql
              V1_1__post_details.sql
              V1_2__post_comment.sql
              V1_3__post_tag.sql
    

    Considering we have a Spring framework application that uses Hibernate for persistence, in order to use FlywayDB, we just have to configure an org.flywaydb.core.Flyway Spring bean:

    @Bean(initMethod = "migrate")
    public Flyway flyway() {
        Flyway flyway = new Flyway();
        flyway.setDataSource(actualDataSource());
        flyway.setBaselineOnMigrate(true);
        flyway.setLocations(
            String.format(
                "classpath:/flyway/db/%1$s/migration",
                databaseType()
            )
        );
        return flyway;
    }
    

    Another thing to consider is that the LocalContainerEntityManagerFactoryBean, which is responsible for instantiating the Java persistence EntityManagerFactory, must be initialized after the Flyway component so those migrations are executed prior to bootstrapping Hibernate.

    @Bean
    // depends on flyway bean, so that database initialization
    // runs before Hibernate gets bootstrapped
    @DependsOn("flyway")
    public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
        // the usual initialization
    }
    

    If in the current development iteration, we realized that we need a new database schema migration script:

    CREATE TABLE users (id BIGINT NOT NULL, name VARCHAR(255), PRIMARY KEY (id));
    

    We just have to add it to the Flyway resources folder:

    resources
      flyway
        db
          postgresql
            migration
              V1_0__initial_script.sql
              V1_1__post_details.sql
              V1_2__post_comment.sql
              V1_3__post_tag.sql
              V1_4__users.sql
    

    And when the application is started, Flyway is going to apply it automatically:

    INFO  : DbValidate - Validated 5 migrations (execution time 00:00.025s)
    DEBUG : DbSchemas - Schema "public" already exists. Skipping schema creation.
    DEBUG : Table - Locking table "public"."schema_version"...
    DEBUG : Table - Lock acquired for table "public"."schema_version"
    INFO  : DbMigrate - Current version of schema "public": 1.3
    INFO  : DbMigrate - Migrating schema "public" to version 1.4 - users
    DEBUG : SqlScript - Found statement at line 1: create table users (id bigint not null, name varchar(255), primary key (id))
    DEBUG : SqlScript - Executing SQL: create table users (id bigint not null, name varchar(255), primary key (id))
    DEBUG : DbMigrate - Successfully completed and committed migration of schema "public" to version 1.4
    DEBUG : MetaDataTableImpl - MetaData table "public"."schema_version" successfully updated to reflect changes
    DEBUG : Table - Locking table "public"."schema_version"...
    DEBUG : Table - Lock acquired for table "public"."schema_version"
    INFO  : DbMigrate - Successfully applied 1 migration to schema "public" (execution time 00:00.091s).
    

    From the application logs, we can see how Flyway managed to discover and apply the new schema migration script, bumping up the database schema version. Behind the scenes, Flyway uses a separate database table (e.g. schema_version) where it records every script that it has previously applied:

    version description script installed_on execution_time success
    1.0 initial script V1_0__initial_script.sql 2016-08-05 07:50:22.418753 126 t
    1.1 post details V1_1__post_details.sql 2016-08-05 07:50:22.729402 164 t
    1.2 post comment V1_2__post_comment.sql 2016-08-05 07:50:22.92953 97 t
    1.3 post tag V1_3__post_tag.sql 2016-08-05 07:50:23.038776 8 t
    1.4 users V1_4__users.sql 2016-08-05 07:51:35.315481 43 t

    Conclusion

    While the schema generation tool offered by Hibernate is useful for testing, or even for creating a first initial script, on the long run, it is much more flexible to use an automatic schema migration tool such as FlywayDB. This way we can leverage all the features offered by the database-specific DDL syntax with just a minimum amount of configuration.

    If you enjoyed this article, you might be interested in reading my book as well.

    Frequently Asked Questions (FAQs) on Schema Migration with Hibernate and FlywayDB

    What is the role of FlywayDB in schema migration with Hibernate?

    FlywayDB is a powerful tool that aids in database schema migration. It allows developers to apply version control to their databases, making it easier to manage and track changes. When used with Hibernate, FlywayDB helps to automate the process of schema migration, reducing the risk of errors and ensuring consistency across different environments. It does this by applying migration scripts in a sequential manner, ensuring that each change is applied correctly and in the right order.

    How does FlywayDB compare to other database migration tools?

    FlywayDB stands out for its simplicity and ease of use. It has a straightforward setup process and a clear, intuitive command structure. It also supports both SQL and Java-based migrations, making it versatile and adaptable to different project needs. Furthermore, FlywayDB has robust error handling and rollback capabilities, which can be crucial in preventing data loss or corruption during migrations.

    Can I use FlywayDB with other ORM frameworks apart from Hibernate?

    Yes, FlywayDB is not exclusive to Hibernate. It can be used with any ORM framework that supports JDBC, including JPA, MyBatis, and Spring Data JPA. This makes FlywayDB a flexible and versatile tool for database schema migration, regardless of the ORM framework you’re using.

    How does version control work in FlywayDB?

    FlywayDB uses a versioning system to manage and track changes to the database schema. Each migration script is assigned a unique version number. When a migration is run, FlywayDB checks the version history to determine which scripts need to be applied. This ensures that all changes are applied in the correct order and that no changes are missed or applied twice.

    What are the benefits of automating schema migration with Hibernate and FlywayDB?

    Automating schema migration with Hibernate and FlywayDB brings several benefits. It reduces the risk of human error, ensures consistency across different environments, and makes it easier to track and manage changes. It also saves time and effort, as developers no longer need to manually apply each change. Furthermore, it facilitates collaboration and coordination among team members, as everyone can easily see what changes have been made and when.

    How can I handle errors during schema migration with FlywayDB?

    FlywayDB has robust error handling capabilities. If an error occurs during a migration, FlywayDB will stop the migration and provide a detailed error message, allowing you to identify and fix the problem. It also supports rollbacks, which means you can revert the database to a previous state if something goes wrong.

    Can I use FlywayDB with a non-relational database?

    FlywayDB is primarily designed for use with relational databases. It supports a wide range of relational databases, including MySQL, PostgreSQL, Oracle, and SQL Server. However, it does not natively support non-relational databases. If you’re working with a non-relational database, you may need to consider other migration tools or strategies.

    How can I integrate FlywayDB with my existing Hibernate project?

    Integrating FlywayDB with an existing Hibernate project is straightforward. You simply need to add the FlywayDB library to your project, configure it to connect to your database, and then create your migration scripts. Once this is done, you can use FlywayDB’s commands to manage and apply your migrations.

    What is the best practice for writing migration scripts in FlywayDB?

    When writing migration scripts for FlywayDB, it’s important to keep them small and focused. Each script should make a single change to the database schema. This makes it easier to manage and track changes, and reduces the risk of errors. It’s also important to test your scripts thoroughly before applying them to ensure they work as expected.

    Can I use FlywayDB in a continuous integration/continuous deployment (CI/CD) environment?

    Yes, FlywayDB is well-suited to CI/CD environments. It can be integrated with popular CI/CD tools like Jenkins, Travis CI, and CircleCI. This allows you to automate the process of applying migrations as part of your build and deployment process, ensuring that your database schema is always up-to-date and consistent across all environments.