· Bogdan Shulga · Backend Development  · 6 min de leitura

Backend Java: Migração Automática de Base de Dados com Flyway



Backend Java: Migração Automática de Base de Dados com Flyway

Introdução

A migração de bases de dados é um processo essencial e integrante de todo o desenvolvimento de software. Desde o início do desenvolvimento da aplicação, o número de scripts de migração da base de dados aumenta com cada funcionalidade que exige uma alteração na base de dados. Isto torna mais lento o processo de testes, deployment e integração da aplicação. Todos estes problemas podem ser eficazmente resolvidos através da automatização do processo de aplicação dos scripts de migração da base de dados. O Flyway é uma das ferramentas mais populares nesta área. Tem muitas funcionalidades, mas, mais importante ainda, fornece controlo de versões da base de dados. Neste artigo, vamos configurar a ferramenta Flyway para aplicar automaticamente os scripts de migração da base de dados numa aplicação típica Java EE a correr dentro de um contentor JBoss.

Preparação do Ambiente

Será preparado o seguinte ambiente:

Preparação do Controlador JDBC MySQL

O controlador JDBC MySQL ou, mais especificamente, o MySQL Java Connector pode ser descarregado diretamente do repositório público Nexus: MySQL Java Connector

Para instalar o MySQL Java Connector, execute os seguintes passos:

  1. Crie o novo diretório do módulo: $JBOSS_HOME/modules/com/mysql/main
  2. Coloque o ficheiro jar do MySQL Java Connector descarregado no diretório criado
  3. Crie o ficheiro module.xml dentro do diretório criado com o seguinte conteúdo:
<?xml version="1.0" encoding="UTF-8"?>
<module name="com.mysql" xmlns="urn:jboss:module:1.0">
 <resources>
 <resource-root path="mysql-connector-java-5.1.40-bin.jar"/>
 </resources>
 <dependencies>
 <module name="javax.api"/>
 </dependencies>
</module>

Se a versão do MySQL Java Connector for atualizada, também deve ser atualizada no ficheiro module.xml

Configuração da Ligação à Base de Dados no JBoss

Depois de instalar o MySQL Java Connector necessário, a nova fonte de dados e a ligação à base de dados podem ser facilmente configuradas no ficheiro de configuração standalone.xml do JBoss:

Configuração da Ligação à Base de Dados no JBoss

Depois de instalar o MySQL Java Connector necessário, a nova fonte de dados e a ligação à base de dados podem ser facilmente configuradas no ficheiro de configuração standalone.xml do JBoss:

...
 <drivers>
 <driver name="mysql" module="com.mysql">
 <xa-datasource-class>com.mysql.jdbc.Driver</xa-datasource-class>
 </driver>
 </drivers>
...
 <datasource jta="true" jndi-name="java:/MySqlDS" pool-name="MySqlDS" enabled="true" use-ccm="true">
 <connection-url>jdbc:mysql://127.0.0.1:3306/zoo</connection-url>
 <driver-class>com.mysql.jdbc.Driver</driver-class>
 <driver>mysql</driver>
 <security>
 <user-name>root</user-name>
 </security>
 <validation>
 <valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLValidConnectionChecker"/>
 <background-validation>true</background-validation>
 <exception-sorter class-name="org.jboss.jca.adapters.jdbc.extensions.mysql.MySQLExceptionSorter"/>
 </validation>
 <statement>
 <prepared-statement-cache-size>32</prepared-statement-cache-size>
 <share-prepared-statements>true</share-prepared-statements>
 </statement>
 </datasource>
...

Uma forma alternativa seria configurar o JBoss através da Consola de Gestão de Aplicações na Web UI: http://localhost:9990/console => Configuration => Subsystems => Create new Datasource

Consola de Administração do JBoss

Integração do Flyway

Configuração do Script de Build Gradle

Primeiro de tudo, as dependências e os plugins necessários devem ser configurados no ficheiro build.gradle do projeto Gradle:

apply plugin: 'java'
apply plugin: "org.flywaydb.flyway"
apply plugin: 'war'

buildscript {
    repositories {
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
    dependencies {
        classpath 'mysql:mysql-connector-java:5.1.40'
        classpath 'gradle.plugin.com.boxfuse.client:flyway-release:4.2.0'
    }
}

repositories {
    mavenCentral()
}

flyway {
    url = 'jdbc:mysql://127.0.0.1:3306/zoo'
    user = 'root'
    password = ''
}

sourceCompatibility = 1.8

dependencies {
    compile 'javax.ejb:javax.ejb-api:3.2'
    compile 'org.flywaydb:flyway-core:4.2.0'
    compile 'org.apache.commons:commons-lang3:3.5'
    compile 'org.slf4j:slf4j-api:1.7.22'
    compile group: 'mysql', name: 'mysql-connector-java', version: '5.1.40'
}

Implementação do Inicializador da Aplicação

A migração automática requer um chamado inicializador, que invocará o serviço adequado para a inicialização da base de dados durante o arranque da aplicação:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import persistence.DbService;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.ejb.Singleton;
import javax.ejb.Startup;

@Startup
@Singleton
public class ApplicationInitializer {

    private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationInitializer.class);

    @EJB
    private DbService dbService;

    @PostConstruct
    private void init() {
        dbService.initialize();
        LOGGER.info("DBase successfully initialised");
    }

}

Implementação do Serviço de Base de Dados

O Serviço de Base de Dados, por sua vez, seria responsável por executar as tarefas de limpeza (clean) e migração (migrate) da base de dados. Neste caso, a variável tasks é declarada explicitamente, mas, consoante o ambiente, pode obtê-la a partir do ficheiro de configuração:

package persistence;

public interface DbService {
    void initialize();
    void clean();
    void migrate();
}
package persistence;

import org.apache.commons.lang3.StringUtils;
import org.flywaydb.core.Flyway;
import org.flywaydb.core.api.FlywayException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.PostConstruct;
import javax.ejb.Singleton;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.naming.InitialContext;
import javax.sql.DataSource;

@Singleton
@TransactionManagement(TransactionManagementType.BEAN)
public class DbServiceEjb implements DbService {

  private static final Logger LOGGER = LoggerFactory.getLogger(DbServiceEjb.class);

  private static final String CLEAN_TASK_NAME = "clean";

  private static final String MIGRATE_TASK_NAME = "migrate";

  private String tasks = "clean,migrate";

  private String datasourceJndiName = "java:/MySqlDS";

  private Flyway flyway;

  @PostConstruct
  private void init() {
     DataSource dataSource = InitialContext.doLookup(datasourceJndiName);
     flyway = new Flyway();
     flyway.setDataSource(dataSource);
     LOGGER.info("About to use {} locations for database management", "sql/main");
     flyway.setLocations("sql/main");
     flyway.setBaselineOnMigrate(true);
  }

  @Override
  public void initialize() {
     if (StringUtils.isNotBlank(tasks)) {
        if (tasks.contains(CLEAN_TASK_NAME)) {
           clean();
        }
        if (tasks.contains(MIGRATE_TASK_NAME)) {
           migrate();
        }
     }
  }

  @Override
  public void clean() {
     LOGGER.info("About to clean database");
     flyway.clean();
  }

  @Override
  public void migrate() {
     LOGGER.info("About to migrate database");
     try {
        flyway.migrate();
     } catch (FlywayException e) {
        LOGGER.warn("Flyway has encountered an error while performing migrate. About to repair");
        flyway.repair();
        flyway.migrate();
     }
  }
}

Criação de Scripts de Migração SQL

Os scripts de migração SQL podem ser armazenados no diretório de recursos do projeto, com uma estrutura predefinida: src/main/resources/sql/main/v1

O Flyway reconhece apenas os scripts SQL que seguem o seguinte padrão de nomenclatura: V<Number>__FILE_NAME.sql

-- V1.1__CREATE_TABLE_ANIMALS.sql
CREATE TABLE ZOO.ANIMALS
(
  ID INT PRIMARY KEY AUTO_INCREMENT,
  NAME VARCHAR(25),
  NICKNAME VARCHAR(25),
  AGE INT(2)
);

CREATE UNIQUE INDEX ANIMALS_ID_IDX ON ZOO.ANIMALS (ID);
INSERT INTO ZOO.ANIMALS (NAME, NICKNAME, AGE) VALUES ('Lion', 'Simba', 5);
COMMIT;

-- V1.2__UPDATE_TABLE_ZOO.sql
ALTER TABLE ZOO.ANIMALS ADD CELL INT NULL;
INSERT INTO ZOO.ANIMALS (NAME, NICKNAME, AGE, CELL) VALUES ('Penguin', 'Kowalski', 3, 35);
COMMIT;

Estrutura de Diretórios do Projeto

Como resultado, espera-se a seguinte estrutura de diretórios:

├───main
│   ├───java
│   │   ├───core
│   │   │       ApplicationInitializer.java
│   │   │
│   │   └───persistence
│   │           DbService.java
│   │           DbServiceEjb.java
│   │
│   ├───resources
│   │   └───sql
│   │       └───main
│   │           └───v1
│   │                   V1.1__CREATE_TABLE_ANIMALS.sql
│   │                   V1.2__UPDATE_TABLE_ZOO.sql
│   │
│   └───webapp
└───test
    ├───java
    └───resources

Verificação

Depois de a aplicação ser implementada, entre outras mensagens, as mensagens de migração da base de dados aparecem na saída do registo:

INFO  [persistence.DbServiceEjb] (ServerService Thread Pool -- 167) About
 to use sql/main locations for database management

INFO  [persistence.DbServiceEjb] (ServerService Thread Pool -- 167) About
 to clean database

INFO  [org.flywaydb.core.internal.util.VersionPrinter] (ServerService
 Thread Pool -- 167) Flyway 4.2.0 by Boxfuse

INFO  [org.flywaydb.core.internal.dbsupport.DbSupportFactory] (ServerService
 Thread Pool -- 167) Database: jdbc:mysql://127.0.0.1:3306/zoo (MySQL 5.5)

INFO  [org.flywaydb.core.internal.command.DbClean] (ServerService
 Thread Pool -- 167) Successfully cleaned schema `zoo` (execution time 00:00.109s)

INFO  [persistence.DbServiceEjb] (ServerService Thread Pool -- 167) About
 to migrate database

INFO  [org.flywaydb.core.internal.command.DbValidate] (ServerService
 Thread Pool -- 167) Successfully validated 2 migrations (execution time 00:00.013s)

INFO  [org.flywaydb.core.internal.metadatatable.MetaDataTableImpl] (ServerService
 Thread Pool -- 167) Creating Metadata table: `zoo`.`schema_version`

INFO  [org.flywaydb.core.internal.command.DbMigrate] (ServerService
 Thread Pool -- 167) Current version of schema `zoo`: << Empty Schema >>

INFO  [org.flywaydb.core.internal.command.DbMigrate] (ServerService
 Thread Pool -- 167) Migrating schema `zoo` to version 1.1 - CREATE TABLE ANIMALS

INFO  [org.flywaydb.core.internal.command.DbMigrate] (ServerService
 Thread Pool -- 167) Migrating schema `zoo` to version 1.2 - UPDATE TABLE ZOO

INFO  [org.flywaydb.core.internal.command.DbMigrate] (ServerService
 Thread Pool -- 167) Successfully applied 2 migrations to schema `zoo` (execution time 00:00.226s).

INFO  [core.ApplicationInitializer] (ServerService
 Thread Pool -- 167) DBase successfully initialised
Back to Blog

Related Posts

View All Posts »