· Bogdan Shulga · Backend Development · 6 min de leitura
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:
- Crie o novo diretório do módulo:
$JBOSS_HOME/modules/com/mysql/main - Coloque o ficheiro jar do MySQL Java Connector descarregado no diretório criado
- Crie o ficheiro
module.xmldentro 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

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



