O flyway é uma excelente ferramenta para versionamento de scripts de banco de dados. Por padrão, ele utiliza a tabela schema_version para fazer o gerenciamento dos scripts executados, no entanto, podemos precisar utilizar mais que uma estrutura de versionamento por exemplo: pode ser preciso versionar os scripts por cliente ou separar scripts de estrutura e dados.
Nesse post, vou usar como exemplo como criar duas estruturas de versionamento, uma para estrutura da base de dado, na qual deve ser gerenciada pelo tabela padrão do Flyway (schema_version) e vou criar uma nova tabela para gerenciar apenas o scripts de dados, onde serão versionados apenas os scripts de insert/update.
Apenas uma observação, nesse cenário há uma regra importante, que os scripts de estrutura sempre devem ser executados antes dos scripts de dados.
O primeiro passo é separar os scripts em pastas diferentes(structure e data), pois por padrão, o Flyway olha a pasta /db/migration e caso tiver número de scripts repetidos no diretório irá ocorrer erro.
Após os diretórios criados, os scripts devem ser criados em suas devidas pastas como demonstrado exemplificado abaixo.
O primeiro script da estrutura precisa configurar a nova tabela para gerenciar os scripts de dados, com isso, no V1 vai ser criado a tabela schema_data_version.
/db/migration/structure/V1__script.sql
CREATE TABLE schema_data_version ( version_rank integer NOT NULL, installed_rank integer NOT NULL, version character varying(50) NOT NULL, description character varying(200) NOT NULL, type character varying(20) NOT NULL, script character varying(1000) NOT NULL, checksum integer, installed_by character varying(100) NOT NULL, installed_on timestamp without time zone NOT NULL DEFAULT now(), execution_time integer NOT NULL, success boolean NOT NULL, CONSTRAINT schema_data_version_pk PRIMARY KEY (version) ); CREATE INDEX schema_data_version_ir_idx ON schema_data_version USING btree (installed_rank); CREATE INDEX schema_data_version_s_idx ON schema_data_version USING btree (success); CREATE INDEX schema_data_version_vr_idx ON schema_data_version USING btree (version_rank); insert into schema_data_version (version_rank, installed_rank, version, description, type, script, checksum, installed_by, installed_on, execution_time, success) values (1, 1, 1, '<< Flyway Baseline >>', 'BASELINE', '<< Flyway Baseline >>', null, 'postgres', '2016-08-15 09:33:44.566255', 0, true);
Observação: Os scripts SQLs foram exemplificados para o banco de dados PostgresSQL.
Caso criado uma nova tabela de estrutura o flyway exige que a pasta não esteja vazia, por isso foi inserido apenas um registro para inicializar o versionamento.
Os dois próximos scripts são de criação de tabelas da aplicação e não mais configuração, onde o V2 cria a tabela de empresa e o V3 cria a tabela de usuário.
/db/migration/structure/V2__script.sql
create table empresa ( id bigserial PRIMARY KEY, version bigint NOT NULL DEFAULT 0, cnpj VARCHAR(14) NOT NULL, nome_fantasia VARCHAR(100) NOT NULL, razao_social VARCHAR(100) NOT NULL, ativo BOOLEAN NOT NULL, CONSTRAINT empresa_uk UNIQUE (cnpj) );
/db/migration/structure/V3__script.sql
create table usuario ( id bigserial PRIMARY KEY, version bigint NOT NULL DEFAULT 0, login VARCHAR(14) NOT NULL, senha VARCHAR(255) NOT NULL, ativo BOOLEAN NOT NULL, CONSTRAINT usuario_uk UNIQUE (login) );
Finalizado a criação das tabelas, agora é possível utilizar a estrutura de dados para fazer alterações nas tabelas do sistema, assim o V2 da estrutura de dados insere uma empresa na tabela de empresa.
/db/migration/data/V2__script.sql
INSERT INTO empresa(cnpj, nome_fantasia, razao_social, ativo) VALUES ('03025433000190', 'Empresa', 'Empresa LTDA', true);
Criados os scripts em suas respectivas pastas agora é preciso configurar o Flyway para utilizar duas estruturas de versionamento, pois caso utilizado a configuração padrão ele fará a leitura de toda pasta db/migration e lançará a exception que tem mais de uma arquivo com o prefixo V2.
A configuração consiste em basicamente criar duas instâncias do flyway, uma para cada diretório de script, passando o parâmetro locations com o path da pasta, demonstrado no trecho abaixo.
Flyway flywayScructure = new Flyway(); flyway.setLocations("db/migration/structure"); flyway.setDataSource(getDataSource()); flyway.migrate(); Flyway flywayData = new Flyway(); flywayData.setLocations("db/migration/data"); flywayData.setDataSource(getDataSource()); flywayData.migrate();
Como estratégia de execução dos scripts utilizei o método migrate, que compara a versão atual da base dados com os scripts versionados e executa os novos arquivos inseridos na estrutura, caso queiram conhecer mais afundo sobre essa estratégia segue o link da documentação.
A configuração em projetos utilizando Spring, Spring Boot e Java EE7 são similares, basicamente mudando apenas a forma que é utilizado o dataSource da aplicação no Flyway e a forma de inicializar o método migrate para executar o processo de migração dos scripts.
Configuração no Spring
Utilizando Spring, as configurações devem ser feitas separadas e anotadas com a anotação @Bean, passando o parâmetro initMethod = migrate, que baseado no retorno do método será invocado o método migrate do Flyway.
@Bean(initMethod = "migrate") public Flyway flywayStructure() { final Flyway flyway = new Flyway(); flyway.setLocations("classpath:db/structure"); flyway.setDataSource(dataSource()); return flyway; } @DependsOn("flywayStructure") @Bean(initMethod = "migrate") public Flyway flywayInitData() { final Flyway flyway = new Flyway(); flyway.setLocations("classpath:db/migration/data"); flyway.setTable("schema_data_version"); flyway.setDataSource(dataSource()); return flyway; } private DataSource dataSource() { JndiDataSourceLookup lookup = new JndiDataSourceLookup(); return lookup.getDataSource("jdbc/exemplo"); }
Para garantir a ordem de criação dos beans de configuração do Flyway pode ser usado ao anotação @DependsOn, que garante que o método anotado só vai ser executado após o método passado entre parênteses.
Configuração no Spring Boot
No Spring Boot a configuração de Flyway é idêntica a utilização em um aplicação Spring “pura”, no entanto, ele oferece uma facilidade para injeção do dataSource, é possível injetar o dataSource de acordo com as configurações feitas no applicationProperties usando a anotação @ConfigurationProperties.
@Bean(initMethod = "migrate") public Flyway flywayStructure() { final Flyway flyway = new Flyway(); flyway.setLocations("classpath:db/structure"); flyway.setDataSource(getDataSource()); return flyway; } @DependsOn("flywayStructure") @Bean(initMethod = "migrate") public Flyway flywayInitData() { final Flyway flyway = new Flyway(); flyway.setLocations("classpath:db/migration/data"); flyway.setTable("schema_data_version"); flyway.setDataSource(getDataSource()); return flyway; } @Primary @Bean @ConfigurationProperties("spring.datasource") public DataSource getDataSource() { return DataSourceBuilder.create().build(); }
Nessa abordagem, é sempre bom anotar o método que cria o dataSource com @Primary, que qualifica esse método com maior prioridade sobre os outros beans.
Configuração no Java EE
No Java EE a idéia é a mesma, mas implementada com recursos diferentes, onde a chamada das configurações de Flyway serão chamadas dentro do mesmo método que será executado no Startup da aplicação.
@Startup @Singleton @TransactionManagement(TransactionManagementType.BEAN) public class FlywaySetup { @Resource(lookup = "java:jboss/datasources/sistemaDS") private DataSource dataSource; @PostConstruct public void init() { final Flyway flywayStructure = new Flyway(); flywayStructure.setDataSource(dataSource); flywayStructure.setLocations("db/migration/structure"); flywayStructure.migrate(); final Flyway flywayData = new Flyway(); flywayData.setDataSource(dataSource); flywayData.setLocations("db/migration/data"); flywayData.setTable("schema_data_version"); flywayData.migrate(); } }
A classe FlywaySetup é anotada com @Startup para ser executada na inicialização da aplicação e a criação das configurações do Flyway devem ser executadas no PostConstruct para que o dataSouce esteja injetado nesse momento.
Também foi utilizada a anotação @TransactionManagement do tipo Bean porque se trata de alterações na base de dados, pois o Flyway trabalha com transações (em algumas bancos, consulte a documentação) para executar os scripts, pois, caso haja falhas na execução é precisa realizar o rollback das alterações.