Introdução

Iniciado em 2006, o Liquibase é uma biblioteca que conta com documentação completa e de fácil entendimento, com isso, utilizá-lo não exige uma curva de aprendizado muito grande, tornando ele bastante atrativo. Seu uso, tende oferecer muito ganho em produtividade oferecendo a possibilidade de disponibilização de uma base de dados funcional, atualizada e compatível com a qualquer versão de sua aplicação em um ambiente de desenvolvimento ou testes em poucos minutos.

Porém, o principal objetivo do Liquibase é facilitar a rastreabilidade de alterações executadas no banco de dados, para isso, faz uso de um versionamento altamente poderoso, descomplicado e semelhante ao encontrado em ferramentas de controle de versão de código, como por exemplo, SVN e GIT. Além disso, ele não se limita apenas ao controle de versão, sendo também, uma poderosa ferramenta de migração e refatoração do banco de dados.

Para tornar possível o gerenciamento do banco de dados, o Liquibase faz uso de um arquivo XML denominado changelog. Neste arquivo, são concentradas informações sobre o histórico de evolução do banco de dados e, um exemplo disso são instruções SQL executadas ou pendentes para uma determinada base de dados. O changelog pode ser composto por um ou vários changesets, que, ao fazer analogia com um sistema de controle de versão do código, poderíamos dizer que o changelog corresponderia a uma branch e os changesets aos commits.

Além do changelog, o Liquibase também faz uso de duas tabelas denominadas: databasechangelog e databasechangeloglock, estas, são criadas no banco de dados a partir da primeira execução. Sempre que executado, o Liquibase fará uma comparação entre as instruções contidas no changelog com registros da tabela databasechangelog, desta forma, ele consegue identificar o que precisa ser executado para tornar o banco de dados compatível com a versão atual do sistema.

Neste artigo, os exemplos serão apresentados apenas utilizando a sintaxe do formato XML, porém, desde o seu lançamento o Liquibase está em constante evolução e, a partir da versão 3.x foram introduzidos novos formatos com sintaxes específicas para o changelog dando suporte as extensões: XML, JSON, YAML ou SQL.

A execução do Liquibase pode ser feita de duas formas, a primeira é conhecida como On Demand, sendo esta, por linha de comando, Maven ou Ant Builder. A segunda é a automatizada, podendo ser por: Servlet Listener, Spring Listener ou JEE CDI Listener.

Independente da sua opção não existe restrição para execução das principais funcionalidades do Liquibase, sendo: Update, Rolback, Diff, SQL Output e DB Doc.

Configuração

Para trabalhar com o Liquibase na versão 3.x é necessário utilizar Java 6 ou superior. Porém, também é possível utilizar a versão 2.x que neste caso tem como requisito Java 5 ou superior.

O primeiro passo é fazer o download do JAR no site oficial do projeto. Aqui optei por utilizar a versão 3.4.2 publicada em 24/11/2015. Obs: Atualmente a biblioteca possui versões mais recentes.

Após concluir o download, caso não tenha optado por executar o Liquibase via linha de comando, poderá incluir o JAR: liquibase-core-3.4.2.jar juntamente com o driver do banco de dados no classpath do seu projeto. Caso sua opção seja executar via linha de comando, copie os arquivos para um diretório de sua preferência.

Para este artigo optou-se pelo uso PostgresSql versão 9.5, sendo assim, o driver utilizado foi o: postgresql-9.1-901.jdbc3.jar

CHANGELOG

O changelog é o arquivo XML onde deverão ser registradas as instruções DDL e DML que serão executadas no banco de dados. A partir deste arquivo, o Liquibase consegue fazer a verificação de estado do banco de dados e executar somente instruções ainda pendentes.

O Exemplo 01 apresenta um exemplo padrão de changelog no formato xml. Nele encontram-se declarados os namespaces necessários para validação do arquivo na versão 3.4.x.

Exemplo 01

<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd"> </databaseChangeLog>

Como boa prática, é sugerida a criação de vários arquivos changelogs e, que em cada arquivo sejam concentradas as alterações necessárias para o banco de dados em um determinado release ou branch. Com isso, além de se obter um histórico mais preciso e detalhado sobre alterações efetuadas no banco, merges se tornam mais fáceis em caso de conflitos, uma vez que, o changelog de uma versão ou release, tende conter uma quantidade menor de instruções.

Seguindo esta dica, pode ser criado um arquivo xml, aqui chamado de changelog-master e, nele será incluído o caminho completo de cada changelog existente.

O Exemplo 02 apresenta a criação deste arquivo, que conta com a inclusão de dois arquivos changelog.

Exemplo 02

<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd"> <include file="changelog-1.0.xml" /> <include file="changelog-2.0.xml" /> </databaseChangeLog>

Como dito anteriormente cada arquivo changelog pode conter um conjunto de changesets que é o seu principal elemento.

O Exemplo 03 apresenta a estrutura básica de criação do changeset contendo os atributos obrigatórios, sendo: id e autor. Importante: Para o Liquibase é importante que o id tenha valor único por autor e changelog.

Os valores definidos para cada um destes atributos serão persistidos no banco de dados após a execução. Estas informações são utilizadas para identificar changesets já aplicados e pendentes em uma determinada base de dados.

Exemplo 03

<changeSet author="NOME_DO_AUTOR" id="VALOR_UNICO"> // Instruções </changeSet>

Nota: Cada desenvolvedor é responsável incrementar o id e inserir identificação no changeset. O Liquibase, apenas validará se está pendente execução e o aplicará no banco de dados registrando para que não seja executado novamente.

O Liquibase fornece uma estrutura sintática simplificada e semelhante a uma instrução SQL para ser usada no changeset.

O Exemplo 04 apresenta uma instrução de criação de uma tabela para armazenamento de cidades. Note que sintaxe proposta pelo Liquibase se assemelha a uma instrução SQL.

Exemplo 04

<changeSet author="DESENVOLVEDOR" id="1"> <createTable tableName="CIDADE"> <column autoIncrement="true" name="ID" type="BIGSERIAL"> <constraints primaryKey="true" primaryKeyName="ID_CIDADE "/> </column> <column name="NOME" type="VARCHAR(255)"/> <column name="ID_ESTADO" type="INT"/> </createTable> </changeSet>

O Liquibase também fornece a opção de criação de instruções no formato SQL, para isso, pode-se utilizar um bloco CDATA.

O Exemplo 05 apresenta uma instrução para criação de uma function exemplificando este cenário.

Exemplo 05

<changeSet author="DESENVOLVEDOR" id="2"> <sql splitStatements="false"><![CDATA[ CREATE OR REPLACE FUNCTION SUM(IN VALOR1 INTEGER, IN VALOR2 INTEGER) RETURNS INTEGER AS $BODY$BEGIN RETURN VALOR1 + VALOR2; END;$BODY$ LANGUAGE PLPGSQL; ]]></sql> </changeSet>

Nota: Ao criar utilizar um bloco CDATA para instruções SQL, as particularidades de cada banco devem ser levadas em consideração, como no exemplo, o valor LANGUAGE PLPGSQL.

TABELAS DO LIQUIBASE

Para atingir o objetivo a que se propõe, o Liquibase faz uso de duas tabelas, sendo: databasechangelog e databasechangeloglock.

A primeira tabela é onde serão armazenadas as informações referentes a instruções, DDL ou DML descritas em cada changeset. Sempre que um changeset é executado com sucesso, um novo registro será inserido nesta tabela.

O Exemplo 06 apresenta o código SQL de criação da tabela databasechangelog e a seguir são detalhados alguns dos campos existentes nesta tabela.

Exemplo 06

CREATE TABLE databasechangelog( id character varying(255) NOT NULL, author character varying(255) NOT NULL, filename character varying(255) NOT NULL, dateexecuted timestamp without time zone NOT NULL, orderexecuted integer NOT NULL, exectype character varying(10) NOT NULL, md5sum character varying(35), description character varying(255), comments character varying(255), tag character varying(255), liquibase character varying(20), contexts character varying(255), labels character varying(255) );

Os campos id e autor correspondem aos valores informados nos respectivos atributos encontrados no changeset.

O campo filename é utilizando para armazenar o nome do arquivo do qual o changeset está incluso.

Com base na informação contida nos campos id, autor e filename, o Liquibase irá gerar um hash e o campo md5sum é onde esta informação fica armazenada.

Nota: O campo md5sum é utilizando para identificar cada changeset já aplicado no banco dados, com isso, após aplicar um changeset, este não poderá sofrer qualquer tipo de alteração. Por exemplo, para a criação de uma determinada tabela foi criado um changeset contendo o id 1 no changelog-1.0.xml cujo autor foi o desenvolvedor1. Após concluir a execução, o desenvolvedor1 identificou que um campo da tabela foi criado com o tipo de dado errado e, para corrigir este problema o optou por efetuar a alteração no changeset que já havia sido executado. Ao executar novamente o Liquibase irá gerar um novo hash devido à alteração e ao comparar com md5sum persistido pela primeira execução, um erro será lançado.

Este comportamento é realmente o esperado, pois, em um cenário real quando algo do tipo ocorre em um versionador de código fonte, não é possível alterar um commit já efetuado e sim criar um novo comit para a correção do problema. Sendo assim, neste cenário, a correção deverá ocorrer por meio de uma instrução alter table em um novo changeset.

O Exemplo 07 apresenta a estrutura da tabela databasechangeloglock. Esta tabela, é utilizada para tratamento de concorrência. Quando se inicia sua execução um lock e feito no banco de dados e somente é desfeito após a sua conclusão.

Exemplo 07

CREATE TABLE public.databasechangeloglock( id integer NOT NULL, locked boolean NOT NULL, lockgranted timestamp without time zone, lockedby character varying(255), CONSTRAINT pk_databasechangeloglock PRIMARY KEY (id) );

O campo locked é utilizado para identificar o estado atual da base dados podendo receber o valor “T” quando um lock é feito e “F” quando é desfeito.

Se durante uma execução ocorrer um problema, a base de dados permanecerá no estado locked, que poderá ser revertido de forma manual por meio de uma instrução de update nesta coluna ou por um comando de rollback.

O campo lockedby indicará o nome e ip da maquina de onde se originou a execução do Liquibase.

EXECUÇÃO

Linha de Comando

Para executar o Liquibase via linha de comando, é necessário juntar em um diretório de sua preferência o JAR do Liquibase e o driver do banco de dados.

A execução pode ser feita de duas formas. Em uma delas, os parâmetros a serem considerados para cada comando a ser executado são inseridos diretamente no prompt conformo apresentado no Exemplo 08.

Exemplo 08

java –jar liquibase.jar -–driver=org.postgresql.Driver --classpath=postgresql-jdbc-driver.jar –-changeLogFile=changelog.xml --url="jdbc:postgresql://localhost:5432/xpto" --username=usuario --password=senha update

Esta forma se torna pouco praticável devido à quantidade de parâmetros que varia de acordo com o comando desejado.

Outra forma, seria criar no mesmo diretório de arquivos do Liquibase um arquivo de propriedades denominado liquibase.properties e nele definir os parâmetros necessários para sua execução conforme apresentado no Exemplo 09

Exemplo 09

classpath: postgresql-9.1-901.jdbc3.jar driver: org.postgresql.Driver url: jdbc:postgresql://localhost:5432/xpto username: usuario password: senha changeLogFile=changelog.xml

Após a criação deste arquivo, a execução do comando se dá conforme Exemplo 10

Exemplo 10

java –jar liquibase.jar update



Servlet Listener

Utilizando esta opção, Liquibase irá aplicar as instruções contidas no changelog durante o deploy da aplicação. Para isso, é necessário incluir alguns parâmetros de contexto no Web Descriptor (web.xml). O Exemplo 11 demostra os parâmetros necessário e que serão detalhados na seqüência.

Exemplo 11

<context-param> <param-name>liquibase.changelog</param-name> <param-value>com/example/db.changelog.xml</param-value> </context-param> <context-param> <param-name>liquibase.datasource</param-name> <param-value>java:comp/env/jdbc/default</param-value> </context-param> <context-param> <param-name>liquibase.host.includes</param-name> <param-value>production1.example.com, production2.example.com</param-value> </context-param> <context-param> <param-name>liquibase.onerror.fail</param-name> <param-value>true</param-value> </context-param> <context-param> <param-name>liquibase.contexts</param-name> <param-value>production</param-value> </context-param> <listener> <listener-class>liquibase.integration.servlet.LiquibaseServletListener</listener-class> </listener>

liquibase.changelog – Caminho para localização dos changelogs.

– Caminho para localização dos changelogs. liquibase.datasource – Caminho para localização do datasource. Caso o usuário do banco de dados utilizado pela aplicação não tenha os privilégios necessários para criar, alterar ou deletar tabelas no banco de dados, uma opção, seria criar um novo usuário para o Liquibase que contenha estes privilégios, desta forma, não obrigando ser o mesmo usuário da aplicação.

– Caminho para localização do datasource. Caso o usuário do banco de dados utilizado pela aplicação não tenha os privilégios necessários para criar, alterar ou deletar tabelas no banco de dados, uma opção, seria criar um novo usuário para o Liquibase que contenha estes privilégios, desta forma, não obrigando ser o mesmo usuário da aplicação. liquibase.host.includes – Configurações para hosts onde se deseja que o Liquibase seja executado. Pode ser útil quando se possui clientes onde por definição de compliance, somente DBAs podem aplicar scripts no banco de dados. Esta configuração, irá garantir que o Liquibase não seja executado em determinadas bases de dados.

– Configurações para hosts onde se deseja que o Liquibase seja executado. Pode ser útil quando se possui clientes onde por definição de compliance, somente DBAs podem aplicar scripts no banco de dados. Esta configuração, irá garantir que o Liquibase não seja executado em determinadas bases de dados. liquibase.onerror.fail – Configurações para que o Liquibase tome alguma ação caso ocorra alguma exceção. Tal configuração é importante para garantir o estado do banco de dados quando um erro surgir durante a implantação.

– Configurações para que o Liquibase tome alguma ação caso ocorra alguma exceção. Tal configuração é importante para garantir o estado do banco de dados quando um erro surgir durante a implantação. liquibase.contexts – Configuração do contexto para aplicação. Caso exista uma lista de contextos, estes, podem ser declarados separando-os por vírgula.

– Configuração do contexto para aplicação. Caso exista uma lista de contextos, estes, podem ser declarados separando-os por vírgula. Listener – ServletListener responsável pela inicialização do Liquibase durante o deploy da aplicação.

Spring Framework

Para integração entre o Liquibase e o Spring, é necessário a criação de um Bean no contexto do Spring. O Exemplo 12 apresenta um exemplo completo de criação deste Bean e propriedades necessárias para configuração.

Exemplo 12

<bean id="liquibase" class="liquibase.integration.spring.SpringLiquibase"> <property name="dataSource" ref="myDataSource" /> <property name="changeLog" value="classpath:db-changelog.xml" /> <property name="contexts" value="test, production" /> </bean>

Datasource : Caso já exista um Data Source basta fazer referência.

: Caso já exista um Data Source basta fazer referência. Path : Declaração do caminho onde está localizado o arquivo changelog.

: Declaração do caminho onde está localizado o arquivo changelog. Contexto: Declaração dos contextos onde se deseja que o Liquibase seja executado.

Maven Plugin

Com Maven é possível executar o Liquibase durante o processo de build. Para isso, é necessário declarar um plugin no arquivo POM do projeto.

Esta configuração pode ser feita de duas formas, a primeira declarando os parâmetros necessários para a execução do Liquibase diretamente no corpo do plugin e a segunda, externalizando os parâmetros. O Exemplo 13 apresenta a configuração do plugin utilizando um arquivo de propriedades externo.

Exemplo 13

<project> <build> <plugins> <plugin> <groupId>org.liquibase</groupId> <artifactId>liquibase-maven-plugin</artifactId> <version>3.0.5</version> <configuration> <propertyFile>src/main/resources/liquibase/liquibase.properties</propertyFile> </configuration> <executions> <execution> <phase>process-resources</phase> <goals> <goal>update</goal> </goals> </execution> </executions> </plugin> </plugins> </build> </project>

O Exemplo 14 apresenta o arquivo liquibase.properties, contendo os parâmetros para execução do liquibase via plugin.

Exemplo 14

contexts: test changeLogFile: com/company/client/project/db.changelog.xml driver: org.postgresql.Driver url: jdbc:postgresql://localhost:5432/xpto username: usuario password: senha

O Exemplo 15 apresenta a declaração do plugin contendo os parâmetros declarados diretamente em seu corpo

<plugin> <groupId>org.liquibase</groupId> <artifactId>liquibase-maven-plugin</artifactId> <version>3.0.5</version> <configuration> <changeLogFile>src/main/resources/org/liquibase/changelog.xml</changeLogFile> <driver> org.postgresql.Driver</driver> <url> jdbc:postgresql://localhost:5432/xpto </url> <username>user</username> <password>password</password> </configuration> <executions> <execution> <phase>process-resources</phase> <goals> <goal>update</goal> </goals> </execution> </executions> </plugin>

Alterações e Refatorações

O Liquibase fornece uma estrutura sintática para criação de instruções SQL simplificada. A seguir serão apresentados exemplos de uso de algumas das principais instruções utilizadas no cotidiano de um desenvolvedor executadas a partir do comando update cuja execução se da conforme apresentado no Exemplo 10.

Create Table

Comando utilizado para criação de tabelas, o Exemplo 16 demostrar a criação de duas tabelas, sendo a primeira a tabela para armazenar os Estados e a segunda as Cidades.

Exemplo 16

<changeSet author="Fernando" id="1"> <comment>Criação da tabela de Estado</comment> <createTable tableName="estado"> <column autoIncrement="true" name="id" type="SERIAL"> <constraints primaryKey="true" primaryKeyName="pk_estado_id"/> </column> <column name="nome" type="VARCHAR(25)"/> <column name="sigla" type="CHAR(2)"/> </createTable> </changeSet> <changeSet author="Fernando" id="2"> <comment>Criação da tabela de Cidade</comment> <createTable tableName="cidade"> <column autoIncrement="true" name="id" type="SERIAL"> <constraints primaryKey="true" primaryKeyName="pk_cidade_id"/> </column> <column name="nome" type="VARCHAR(80)"/> <column name="populacao" type="NUMERIC"/> <column name="gentilico" type="VARCHAR(250)"/> <column name="area" type="NUMERIC"/> </createTable> </changeSet>

Após a execução o Liquibase irá popular a tabela databasechangelog com informações referentes às instruções envolvidas em cada changeset. A Imagem 1 apresenta os dados constantes na tabela.

Imagem 1

Nota: Para a apresentação de resultados, algumas colunas ficaram ocultas com o objetivo de facilitar a visualização do conteúdo na imagem.

Insert

Comando utilizado para popular tabelas no banco de dados, o Exemplo 17 apresenta o uso deste comando utilizando para inserir dados nas tabelas criadas no Exemplo 16.

Exemplo 17

<changeSet author="Fernando" id="3"> <comment>Inserindo o estado Paraná</comment> <insert tableName="estado"> <column name="sigla" value="PR" /> <column name="nome" value="Paraná" /> </insert> </changeSet> <changeSet author="Fernando" id="4"> <comment>Inserindo a cidade Maringá</comment> <insert tableName="cidade"> <column name="nome" value="Maringá" /> <column name="populacao" value="385.753" /> <column name="gentilico" value="Maringaense" /> <column name="area" value="487.9" /> </insert> </changeSet>

A Imagem 2 apresenta os resultados da tabela datatabasechangelog atualizados.

Add Column

Comando utilizado para adição de colunas a uma determinada tabela do banco de dados. O Exemplo 18 apresenta um exemplo de uso deste comando aplicado na criação da coluna estado na tabela cidade.

Exemplo 18

<changeSet author="Fernando" id="5"> <comment>Adição da coluna id_estado </comment> <addColumn tableName="cidade"> <column name="id_estado" type="INT"/> </addColumn> </changeSet>

Alter Table

Comando utilizando para criar relacionamento entre tabelas, o Exemplo 19 apresenta uso deste comando na criação da foreign key entre as tabelas cidade e estado.

Exemplo 19

<changeSet author="Fernando" id="5"> <comment>Criação da Foreign Key</comment> <addForeignKeyConstraint constraintName="fk_estado" baseTableName="cidade" baseColumnNames="id_estado" referencedTableName="estado" referencedColumnNames="id"/> </changeSet>

SQL

Também é possível executar instruções SQL dentro de um changeset. O Exemplo 20 apresenta um exemplo deste comando utilizado para executar o update na tabela Cidade inserindo o id do estado a partir de uma instrução select.

Exemplo 20

<changeSet author="Fernando" id="6"> <comment>Referenciado cidade Maringá com Estado Paraná</comment> <sql> UPDATE cidade SET id_estado = (SELECT id FROM estado WHERE sigla = 'PR') WHERE nome = 'Maringá'; </sql> </changeSet>

Preconditions

O Liquibase também oferece a opção para definição de condições para execução de um determinado comando no banco de dados. O Exemplo 21 apresenta o exemplo de uma instrução de insert que somente será executada caso a condição dada seja cumprida.

Para atributo onFail apresetado na Linha 2, os seguintes valores poderão ser utilizados:

HALT: Este é o valor default, usado para indicar um erro durante a execução, o que fará com que o Liquibase se encerre imediatamente não executado changesets declarados posteriormente.

CONTINUE: Usado para indicar que caso ocorra um problema a execução de um determinado changeset, o Liquibase deverá pular para o próximo changeset. Desta forma não será registrado na tabela databasechangelog, fazendo com que o Liquibase tente executar novamente sempre que for inicializado. O Liquibase irá registrar a execução deste changeset na tabela databasechangelog, caso a condição se atenda em algum momento.

MARK_RAN: semelhante ao comportamento definido pela flag CONTINUE, no entanto o Liquibase registrará o changeset como MARK_RAN na tabela databasechangelog, desta forma não tentará mais executar em suas próximas inicializações.

WARN: O Liquibase irá emitir apenas um alerta, porém sua execução ocorrerá normalmente mesmo que um erro ocorra.

Exemplo 21

<changeSet author="Fernando" id="7"> <preConditions onFail="CONTINUE"> <sqlCheck expectedResult="1"> SELECT count(*) FROM estado WHERE sigla = 'SP' </sqlCheck> </preConditions> <comment>Adicionando a cidade de São Paulo</comment> <insert tableName="cidade"> <column name="nome" value="São Paulo" /> <column name="populacao" value="11967825" /> <column name="gentilico" value="Paulista" /> <column name="area" value="1521.110" /> </insert> <sql> UPDATE cidade SET id_estado = (SELECT id FROM estado WHERE sigla = 'SP') WHERE nome = 'São Paulo'; </sql> </changeSet>

Uma prática bastante interessante de uso para precoditions é validação do banco de dados antes da execução de uma determinada instrução. Para isso pode-se usar a tag dbms informando o nome banco de dados no atributo type. O Exemplo 22 apresenta um exemplo de como utilizar este comando.

Exemplo 22

<preConditions> <dbms type="oracle" /> </preConditions>

Ao adicionar esta precondition no inicio do changelog, é possível garantir que a execução do Liquibase está condicionada ao uso do banco de dados Oracle, como para este artigo optou-se pelo uso do PostgreSql, ao executar o comando de update do Liquibase, o seguinte erro seria retornado: “DBMS Precondition failed: expected oracle, got postgresql”, com isso, nenhuma instrução contida no changelog deverá ser executada no banco de dados.

Além do oracle, o Liquibase também também esta apto a trabalhar com os seguinte bancos: mysql, postgresql, mssql, sybase, asany, db2, derby, hsqldb, informix, firebird, sqlite.

Load Data

O Liquibase fornece também uma forma de carregamento de arquivos para o banco de dados. Por exemplo, para popular as tabelas Cidade e Estado, podem ser utilizados arquivos no formato csv contendo os registros a serem importados. O Exemplo 23 apresenta um exemplo de uso deste comando.

Exemplo 23

<changeSet author="Fernando" id="8"> <comment>Carregando arquivo csv para o banco de dados</comment> <loadUpdateData primaryKey="id" tableName="estado" file="estados.csv" separator=";"> <column name="id" type="NUMERIC"/> <column name="nome" type="STRING" /> <column name="sigla" type="STRING"/> </loadUpdateData > <update tableName="cidade"> <column name="id_estado" value="18"/> <where>nome = 'Maringá'</where> </update> </changeSet>

Custon Change

Esta opção se torna excelente para refatoração do banco de dados. Por exemplo, imagine um cenário onde optou-se por mesclar os dados das tabelas cidade e estado em uma única tabela, para isso criou-se uma nova tabela denominada cidade_temp que recebera alguns dados existentes nas tabelas cidade e estado.

Utilizando a tag customChange, é possível fazer a copia dos dados para a nova tabela utilizando uma classe Java. O Exemplo 24 apresenta o exemplo para declaração da tag custonChange linhas 12 a 15 e o Exemplo 25 apresenta a classe Java contendo a implentação para efetuar a cópia dos dados.

Exemplo 24

<changeSet author="Fernando" id="9"> <comment>Criação da tabela cidade_temp</comment> <createTable tableName="cidade_temp"> <column autoIncrement="true" name="id" type="SERIAL"> <constraints primaryKey="true" primaryKeyName="pk_cidade_temp_id" /> </column> <column name="cidade" type="VARCHAR(80)" /> <column name="estado" type="VARCHAR(25)" /> </createTable> </changeSet> <changeSet author="Fernando" id="10"> <comment>Copia de dados de cidade e estado</comment> <customChange class="br.fernandogodoy.CidadeTemp"></customChange> </changeSet>

Exemplo 25

public class CidadeTemp implements CustomTaskChange{ @Override public String getConfirmationMessage() { return "Dados copiados com Sucesso!"; } @Override public void setFileOpener(ResourceAccessor arg0) {} @Override public void setUp() throws SetupException { } @Override public ValidationErrors validate(Database arg0) { return null; } @Override public void execute(Database database) throws CustomChangeException { try { JdbcConnection connection = (JdbcConnection) database.getConnection(); ResultSet resultSet = connection.createStatement().executeQuery( "SELECT c.nome, e.nome, e.sigla " + " FROM cidade c, estado e WHERE c.id_estado = e.id"); while (resultSet.next()) { String nome = resultSet.getString(1); String estado = resultSet.getString(2); String sigla = resultSet.getString(3); String estadoStr = estado + ", " + sigla; PreparedStatement ps = connection.prepareStatement( "INSERT INTO cidade_temp(cidade, estado) VALUES(?, ?)"); ps.setString(1, nome); ps.setString(2, estadoStr); ps.execute(); } resultSet.close(); } catch (DatabaseException | SQLException e) { throw new CustomChangeException("Ocorreu um erro: ", e.getCause()); } } }

Drop

Comando utilizado para dropar foreign key, colunas, indice, constraints, primary key, procedures, sequences, tables, e views.

O Exemplo 26 apresenta o uso do comando drop table para deleção das tabelas cidade e estado.

Exemplo 26

<pre><code><changeSet author="Fernando" id="11"> <comment>Dropando tabela estados</comment> <dropTable cascadeConstraints="true" schemaName="public" tableName="estado"/> </changeSet> <changeSet author="Fernando" id="12"> <comment>Dropando tabela cidade</comment> <dropTable cascadeConstraints="true" schemaName="public" tableName="cidade"/> </changeSet>

Rename

Comando utilizado para renomeação de tabelas, colunas e view. O Exemplo 27 apresenta um exemplo de uso deste comando aplicado na tabela cidade_temp.

Exemplo 27

<changeSet author="Fernando" id="13"> <renameTable newTableName="cidade" oldTableName="cidade_temp" schemaName="public"/> </changeSet>

Geração de Changelog

Além do comando para aplicar alterações no banco de dados, o Liquibase também oferece um comando responsável por fazer o papel inverso, ou seja, exportar a estrutura de uma base de dados para um arquivo changelog por meio do comando generateChangelog.

O Exemplo 28 apresenta o comando generateChangelog e a Exemplo 29 apresenta o resultado da execução. Neste exemplo será utilizado o mesmo arquivo de propriedades criadas no Exemplo 09, a diferença é que o atributo changeLogFile possui um papel diferente neste cenário.

Para o comando update ele é utilizado para indicar qual arquivo changelog contém os changesets aplicados ou pendentes execução no banco de dados. Já no comando generateChangeLog ele indicará o nome do arquivo irá conter o resultado da exportação.

Exemplo 28

java –jar liquibase.jar generateChangeLog

Exemplo 29

<?xml version="1.0" encoding="UTF-8" standalone="no"?> <databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" xmlns:ext="http://www.liquibase.org/xml/ns/dbchangelog-ext" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.4.xsd"> <changeSet author="Fernando (generated)" id="1462859827908-1"> <createSequence sequenceName="cidade_temp_id_seq"/> </changeSet> <changeSet author="Fernando (generated)" id="1462859827908-2"> <createTable tableName="cidade"> <column autoIncrement="true" name="id" type="SERIAL"> <constraints primaryKey="true" primaryKeyName="pk_cidade_temp_id"/> </column> <column name="cidade" type="VARCHAR(80)"/> <column name="estado" type="VARCHAR(25)"/> </createTable> </changeSet> </databaseChangeLog>

Diff

Embora não seja o propósito do Liquibase, sentiu-se a necessidade de efetuar comparação de bases de dados. Para isso foi disponibilizado o comando diff, porém como descrito na própria documentação da ferramenta, este comando é falho por dos motivos e deve ser utilizado com cuidado.

A primeira falha do diff é que assim como acontece no SVN, ele efetua apenas a análise sintática do banco dados, um exemplo disso acontece quando fazemos a renomeação de uma tabela ou coluna. Ao executar comando diff, o Liquibase irá apontar que um item foi removido e um novo foi criado exatamente por não saber diferenciar semanticamente as alterações.

A segunda falha está ligada a informações que podem influênciam no funcionamento da aplicação, uma vez que, o diff não compreende a exportação de dados.

Portanto ao usar o diff, deve-se ter o cuidado de analisar calmamente os resultados apresentados para evitar perca de informações.

O Exemplo 30 apresenta o arquivo de propriedades contendo os parâmetros necessários para a comparação e o Exemplo 31 apresenta o comando a ser executado

Exemplo 30

classpath: postgresql-9.1-901.jdbc3.jar driver=org.postgresql.Driver url=jdbc:postgresql://localhost:5432/artigo username=postgres password=admin referenceUrl=jdbc:postgresql://localhost:5432/artigoex referenceUsername=postgres referencePassword=admin

Exemplo 31

java –jar liquibase.jar diff > diff.log

Como resultado, o Liquibase deverá gerar um relatório conforme apresentado no Exemplo 32.

Exemplo 32

Diff Results: Reference Database: postgres@jdbc:postgresql://localhost:5432/artigoex (Default Schema: public) Comparison Database: postgres@jdbc:postgresql://localhost:5432/artigo (Default Schema: public) Product Name: EQUAL Product Version: EQUAL Missing Catalog(s): NONE Unexpected Catalog(s): NONE Changed Catalog(s): artigoxe name changed from 'artigoex' to 'artigo' Missing Column(s): NONE Unexpected Column(s): cidade.cidade cidade.estado cidade.id Changed Column(s): NONE Missing Foreign Key(s): NONE Unexpected Foreign Key(s): NONE Changed Foreign Key(s): NONE Missing Index(s): NONE Unexpected Index(s): pk_cidade_id unique on cidade(id) Changed Index(s): NONE Missing Primary Key(s): NONE Unexpected Primary Key(s): pk_cidade_id on cidade(id) Changed Primary Key(s): NONE Missing Schema(s): NONE Unexpected Schema(s): NONE Changed Schema(s): NONE Missing Sequence(s): NONE Unexpected Sequence(s): cidade_id_seq Changed Sequence(s): NONE Missing Stored Procedure(s): NONE Unexpected Stored Procedure(s): NONE Changed Stored Procedure(s): NONE Missing Table(s): NONE Unexpected Table(s): cidade Changed Table(s): NONE Missing Unique Constraint(s): NONE Unexpected Unique Constraint(s): NONE Changed Unique Constraint(s): NONE Missing View(s): NONE Unexpected View(s): NONE Changed View(s): NONE

Além do relatório de diferenças o Liquibase também possibilita que seja gerado o changelog a partir do comando diffChangeLog. O Exemplo 32 apresenta o uso deste comando e o Exemplo 33 apresenta o resultado da execução.

Exemplo 32

java –jar liquibase.jar diffChangeLog > diff.xml</code

Exemplo 33

<changeSet author="Fernando (generated)" id="1463634090806-1"> <createSequence sequenceName="cidade_id_seq"/> </changeSet> <changeSet author="Fernando (generated)" id="1463634090806-2"> <createTable tableName="cidade"> <column autoIncrement="true" name="id" type="SERIAL"> <constraints primaryKey="true" primaryKeyName="pk_cidade_id"/> </column> <column name="cidade" type="VARCHAR(80)"/> <column name="estado" type="VARCHAR(25)"/> </createTable> </changeSet>

DBDoc

Outra funcionalidade interessante existente no Liquibase é o DBDoc, com este, é possível criar uma documentação bastante completa para o banco de dados em um padrão semelhante ao JavaDoc.

O Eemplo 34 apresenta o comando utilizado para geração do DBDoc onde “doc/dbdoc” será o diretório para geração dos arquivos e a Imagem 3 apresenta o resultado após a geração.

Eemplo 34

java –jar liquibase.jar dbdoc doc/dbdoc

Conclusão

Este artigo abordou alguns dos comandos e funcionalidade do Liquibase para reastreabilidade e aplicação de alterações em banco de dados. Ao que se propõe, o Liquibase demonstra ser uma das ferramentas mais poderosas existentes no mercado.

Uma das vantagens de se usar o Liquibase é que além da quantidade de comandos e funcionalidade já existentes, seu código fonte é open source, o que permite a quem o utiliza, customizar comandos e adequar a sua realidade, tornando assim o framework flexível e adaptável.

Referências

Download Liquibase: http://www.liquibase.org/download/

Documentação: http://www.liquibase.org/documentation/index.html

O Problema de Versionamento do Banco de Dados: https://dicasdolampada.wordpress.com/2012/03/15/o-problema-do-versionamento-do-banco-de-dados/

Evoluindo o Banco de Dados com Liquibase: https://dicasdolampada.wordpress.com/2012/03/19/evoluindo-o-banco-de-dados-com-o-liquibase/

Liquibase: Gerenciamento de Alteração em banco de dados: http://www2.dc.ufscar.br/~paulo.papotti/material_liquibase.pdf

Liquibase With Maven: http://www.yegor256.com/2014/07/20/liquibase-in-maven.html

Managing your Database Migrations using Liquibase: https://blog.codecentric.de/en/2015/01/managing-database-migrations-using-liquibase

Liquibase – Open Source Tool for Database Version Control: http://www.methodsandtools.com/tools/tools.php?liquibase

Automated Liquibase Generator And Validator(ALGV): http://www.ijstr.org/final-print/sep2015/Automated-Liquibase-Generator-And-Validatoralgv.pdf

Liquibase Introduction: http://mmstratton.com/wp/liquibase-introduction/

Banco de Dados Evolutivo: https://arthurluz.wordpress.com/tag/liquibase/