Criando diferentes estruturas de versionamento de scripts SQLs no Flyway

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.

Estrutura de versionamento

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.

Captura de Tela 2017-05-21 às 18.40.08.png

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.

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s