Official statement about introducing LiquiBase to existing project are:
There are two approaches:
To create full schema definition in LiquiBase XML changelog format you may run:
$ mvn liquibase:generateChangeLog -Dliquibase.outputChangeLogFile=...
on clean project without Liquibase. Or set name for changelog output file in pom.xml. (here reduced not fully worked declaration):
<plugin> <groupId>org.liquibase</groupId> <artifactId>liquibase-maven-plugin</artifactId> <version>${liquibase.version}</version> <configuration> <outputChangeLogFile>changelog.xml</outputChangeLogFile> </configuration> </plugin>
Note
pom.xml settings have precedence over -Dliquibase.outputChangeLogFile=... unless you define it as:
<properties> <liquibase.outputChangeLogFile>${project.build.directory}/changelog.xml</liquibase.outputChangeLogFile> </properties> <plugins> <plugin> <configuration> <outputChangeLogFile>${liquibase.outputChangeLogFile}</outputChangeLogFile> </configuration> </plugin> </plugins>
To create full schema definition in LiquiBase SQL format you may first to dump your schema definition:
$ mysqldump --no-data -u $USER -p $DB_NAME > schema.sql
then check schema.sql for danger operations. For example I forget to add --skip-add-drop-table option to mysqldump and so remove any DROP TABLE lines by editor.
Becase we already have tables I replace CREATE TABLE statements with:
CREATE TABLE IF NOT EXISTS
Now my file is safely can be applied to production database without loosing data and can recreate new schema in empty database.
This site suggest using Hibernate hibernate.hbm2ddl.auto=create to create clean schema with Hibernate and compare it with previous one. So you:
create empty schema and grand permissions for user
register project to new user and schema, for example with Spring config:
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/app"/> <property name="username" value="dbuser"/> <property name="password" value="123456"/> </bean> <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="jpaVendorAdapter"> <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> <property name="generateDdl" value="false"/> <property name="showSql" value="false"/> </bean> </property> <property name="packagesToScan" value="com.app.domain" /> <property name="jpaProperties"> <props> <prop key="hibernate.dialect">org.hibernate.dialect.MySQLDialect</prop> <prop key="hibernate.hbm2ddl.auto">create</prop> <prop key="hibernate.default_schema">schema_v2_0</prop> </props> </property> </bean>
run Hibernate initialisation code via test or application deploy so hibernate.hbm2ddl.auto=create trigger to new schema generation.
configure Maven pom.xml with paths and authentication data to old and new schema:
<plugin> <groupId>org.liquibase</groupId> <artifactId>liquibase-maven-plugin</artifactId> <version>${liquibase.version}</version> <configuration> <changeLogFile>${basedir}/src/main/resources/sql/master.xml</changeLogFile> <diffChangeLogFile>changelogDiff.xml</diffChangeLogFile> <promptOnNonLocalDatabase>false</promptOnNonLocalDatabase> <driver>com.mysql.jdbc.Driver</driver> <username>dbuser</username> <password>123456</password> <url>jdbc:mysql://localhost:3306/app</url> <defaultSchemaName>schema_v1.1</defaultSchemaName> <referenceDriver>com.mysql.jdbc.Driver</referenceDriver> <referenceUsername>dbuser</referenceUsername> <referencePassword>123456</referencePassword> <referenceUrl>jdbc:mysql://127.0.0.1:3306/app</referenceUrl> <referenceDefaultSchemaName>schema_v2.0</referenceDefaultSchemaName> </configuration> <dependencies> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> </dependencies> </plugin> .. NOTE:: ``username`` and ``referenceUsername`` as ``url`` and ``referenceUrl``, and ``defaultSchemaName`` and ``referenceDefaultSchemaName``, etc pairs should be adopted to your connection/authentication data. This settings also possible externalize to:: <changeLogFile>${basedir}/src/main/resources/sql/master.xml</changeLogFile>
Review diff in changelogDiff.xml after:
$ mvn liquibase:diff
Resulted changelogDiff.xml may be registered to project with schema_v1.1 schema state.
It is possible to move inner part of changelogDiff.xml to the last changeset that managed by LiquiBase in your project.
Alternatively that file may be registered via <include file="..."/> syntax.
After that staying in project at schema_v1.1 state you may upgrade from schema_v1.1 to schema_v2.0 schema by:
$ mvn liquibase:update
Or create, review and apply SQL upgrade script (for your DBA):
$ mvn liquibase:updateSQL
TODO:
$ mvn liquibase:update -Dliquibase.changesToApply=1
LiquiBase come with plug-in which mimics like SQL driver. So we may run:
$ mvn liquibase:diff
to get difference in LiquiBase XML format between our current database and JPA schema description.
With:
$ mvn liquibase:updateSQL
we get SQL update code to state that described by Hibernate.
Depending on Hibernate metadata form we shoud adapt referenceUrl property. For example for Spring enabled project with JPA annotations:
<referenceUrl>hibernate:spring:com.app.domain?dialect=org.hibernate.dialect.MySQLDialect</referenceUrl>
Here com.app.domain represent package with JPA annotated entities.
So complete reference visit: https://github.com/liquibase/liquibase-hibernate/wiki
Also we need Spring Beans and LiquiBase driver in classpath to resolve dependencies during diff calculation. Complete Maven piece look like:
<plugin> <groupId>org.liquibase</groupId> <artifactId>liquibase-maven-plugin</artifactId> <version>${liquibase.version}</version> <configuration> <changeLogFile>${basedir}/src/main/resources/sql/master.xml</changeLogFile> <propertyFile>${liquibase.profile}</propertyFile> <promptOnNonLocalDatabase>false</promptOnNonLocalDatabase> <logging>info</logging> <driver>com.mysql.jdbc.Driver</driver> <defaultSchemaName>app</defaultSchemaName> <changelogSchemaName>app</changelogSchemaName> <!-- For mvn liquibase:updateSQL --> <migrationSqlOutputFile>migration.sql</migrationSqlOutputFile> <referenceUrl>hibernate:spring:com.app.domain?dialect=org.hibernate.dialect.MySQLDialect</referenceUrl> <diffChangeLogFile>changelogDiff.xml</diffChangeLogFile> <diffTypes>tables,views,columns,indexes,foreignkeys,primarykeys,uniqueconstraints</diffTypes> </configuration> <dependencies> <dependency> <groupId>org.springframework.data</groupId> <artifactId>spring-data-jpa</artifactId> <version>${spring-data.version}</version> </dependency> <dependency> <groupId>org.liquibase.ext</groupId> <artifactId>liquibase-hibernate4.2</artifactId> <version>3.5</version> </dependency> </dependencies> </plugin>
http://stackoverflow.com/questions/27877154/liquibase-and-jpa-annotated-entities
http://www.baeldung.com/liquibase-refactor-schema-of-java-app
Upstream deside use three field to identify changeset:
user:id is actually any non-whitespace and non-colon text with non-whitespace text. Upstream suggest to use changeset authro identity for user and numbers or reason/name for changeset for id.
user:id pair should be unique for file.
Reason to capture full path as part of identifier is very dumb. Upstream arguments:
is very Java centric and require sticking to fixed changeset file location in CLASSPATH.
When you work with same file on different hosts / tools you should very carefully check that path satisfy conventions that used with previous LiquiBase work for concrete DB.
I have incompatibilities between String integration and Maven plug-in. Because I use full path in Maven and CLASSPATH relative in String.
Hopefully there is logicalFilePath attribute.
In XML syntax in may be applied to top level tag:
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog" 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" logicalFilePath="legacy.xml"> ... </databaseChangeLog>
or may be overridden by each changeset:
<changeSet author="admin" id="fix-25" logicalFilePath="fix.xml"> ... </changeSet>
SQL syntax also have logicalFilePath attribute for top level file mark (implemented in v3.3):
--liquibase formatted sql logicalFilePath:legacy.sql
and may be overridden by each changeset:
--changeset legacy:1 logicalFilePath:other.sql
To verify that settings have effect run:
mvn liquibase:changelogSyncSQL
and review migration.sql. Corresponding pom.xml part:
<plugin> <groupId>org.liquibase</groupId> <artifactId>liquibase-maven-plugin</artifactId> <version>${liquibase.version}</version> <configuration> <changeLogFile>${basedir}/src/main/resources/sql/master.xml</changeLogFile> <propertyFile>${liquibase.profile}</propertyFile> <promptOnNonLocalDatabase>false</promptOnNonLocalDatabase> <migrationSqlOutputFile>migration.sql</migrationSqlOutputFile> </configuration> </plugin>
Consult liquibase-core/src/main/java/liquibase/parser/core/formattedsql/FormattedSqlChangeLogParser.java for further info .
It is not possible to split file formatted in SQL LiquiBase syntax.
It is not possible to set tag with SQL LiquiBase syntax. To workaround this issue you may split SQL file and set tags in XML sytax:
<changeSet author="master" id="1"> <tagDatabase tag="v0.1"/> </changeSet> <include file="legacy.sql" relativeToChangelogFile="true"/> <changeSet author="master" id="2"> <tagDatabase tag="v1.0"/> </changeSet> <include file="v1.0.sql" relativeToChangelogFile="true"/>
Also you should be interested in storing stored procedures in separate file and apply runOnChange attribure for XML syntax:
<changeSet author="admin" id="proc-hello"> <createProcedure runOnChange="true" encoding="utf8" dbms="oracle"> CREATE OR REPLACE PROCEDURE hello AS BEGIN DBMS_OUTPUT.PUT_LINE('Hello'); END; </createProcedure> </changeSet>
or with SQL syntax:
--changeset admin:proc-hello runOnChange:true CREATE OR REPLACE PROCEDURE hello AS BEGIN DBMS_OUTPUT.PUT_LINE('Hello'); END;
Some people suggest to use one file per stored procedure / package.
Less is better.
Some databases doesn't support transactional DDL (e.g. Oracle, SQL Server), each DDL statement should be put into a single changeset.
Putting unrelated operations into single changeset leads to trouble when last or intermediate of them fail to apply.
Try to group constraint adding in corresponding update statements. If you have wrong update that doesn't prepare data to constraint it shouldn't be commited but rewritten.