Liquibase使い方（基本）メモ

DBリファクタリングツール「LiquiBase」の基本的な使い方をひとめぐり。

#環境
##OS
Windows7 64bit SP1

##Java
1.7.0_55

##データベース
###MySQL
Ver 14.14 Distrib 5.5.28, for Win64 (x86)

###Oracle
11.2.0.1.0

#LiquiBase とは？
- DB をバージョン管理してしまうツール。
- DB へのパッチ当てや、マイグレーションなど色々利用できる。
- DB への変更を XML ファイル（ChangeLog）で管理する。
- 異なる DB 製品であっても、同じ XML ファイルで変更を反映することができる。
    - シーケンスオブジェクトなど、特定の DB 製品でしかサポートされていないオブジェクトの作成も可能。
- タグ付けなどを行って、任意の状態に DB を __ロールバック__ したりできる。

#ひとめぐり
##インストール
[こちら](http://www.liquibase.org/download/index.html) から、バイナリをダウンロードする。

今回は、 `liquibase-3.0.8-bin.zip` をダウンロードした（2014/06/24 現在最新の 3.2.0 にはデグレがあって、[タグの生成でぬるぽが発生する](http://stackoverflow.com/questions/24155071/error-in-using-tag-in-command-line-when-using-liquibase)）。

ダウンロードした zip を解凍したフォルダにパスを通す。

以下のコマンドを実行して、インストールが完了したことを確認する。

```text:
>liquibase --version
Liquibase Version: 3.0.8
```

##Changelog ファイルを作成する
```xml:sample.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.1.xsd">
  
  <changeSet id="1" author="opengl-8080">
    <createTable tableName="TEST_TABLE">
      <column name="ID" type="int">
        <constraints primaryKey="true" nullable="false"/>
      </column>
      <column name="CODE" type="varchar2(8)">
        <constraints nullable="false"/>
      </column>
      <column name="VALUE" type="varchar2(8)">
        <constraints nullable="false"/>
      </column>
    </createTable>
  </changeSet>
</databaseChangeLog>
```

##実行する
```text:
>liquibase --driver=com.mysql.jdbc.Driver --classpath=.\mysql-connector-java-5.1.22-bin.jar --changeLogFile=sample.xml --url="jdbc:mysql://localhost/test" --username=test --password=test update
Liquibase Update Successful

>liquibase --driver=oracle.jdbc.driver.OracleDriver --classpath=.\ojdbc6.jar --changeLogFile=sample.xml --url="jdbc:oracle:thin:@localhost:1521:test" --username=test --password=test update
Liquibase Update Successful
```

##確認する
```text:
>mysql -u test -ptest test

mysql> show tables;

+-----------------------+
| Tables_in_test        |
+-----------------------+
| databasechangelog     |
| databasechangeloglock |
| test_table            |
+-----------------------+
3 rows in set (0.00 sec)

mysql> desc test_table;
+-------+------------+------+-----+---------+-------+
| Field | Type       | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+-------+
| ID    | int(11)    | NO   | PRI | NULL    |       |
| CODE  | varchar(8) | NO   |     | NULL    |       |
| VALUE | varchar(8) | NO   |     | NULL    |       |
+-------+------------+------+-----+---------+-------+
3 rows in set (0.02 sec)
```

```text:
>sqlplus test/test@test

SQL> select table_name from user_tables;

TABLE_NAME
------------------------------
DATABASECHANGELOGLOCK
DATABASECHANGELOG
TEST_TABLE

SQL> desc test_table;
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------------------
 ID                                        NOT NULL NUMBER(10)
 CODE                                      NOT NULL VARCHAR2(8)
 VALUE                                     NOT NULL VARCHAR2(8)

SQL> SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE FROM USER_CONSTRAINTS WHERE TABLE_NAME='TEST_TABLE' AND CONSTRAINT_TYPE='P';

CONSTRAINT_NAME                C
------------------------------ -
PK_TEST_TABLE                  P
```

##ここまでの説明
- ChangeLog という XML ファイルに、「テーブル作成」「データの挿入・変更」「テーブル定義の変更」などを記述する。
    - ChangeLog ファイルを指定して liquibase を実行することで、定義した変更をデータベースに反映できる。
    - ChangeLog ファイルには、 `changeSet` を１つの単位として変更を定義する。
    - `changeSet` は１トランザクションで実行される。
- データベースには liquibase が使用するテーブルが自動で作成される（`DATABASECHANGELOG` と `DATABASECHANGELOGLOCK`）。
    - `DATABASECHANGELOG` には、適用した `changeSet` の履歴が保存され、一度適用した `changeSet` が繰り返し実行されないようになっている。
    - `DATABASECHANGELOG` には記録されていない、新しい `changeSet` だけが適用される。

##データを登録する
```xml:sample.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.1.xsd">
  
  <changeSet id="1" author="opengl-8080">
    <createTable tableName="TEST_TABLE">
      <column name="ID" type="int">
        <constraints primaryKey="true" nullable="false"/>
      </column>
      <column name="CODE" type="varchar2(8)">
        <constraints nullable="false"/>
      </column>
      <column name="VALUE" type="varchar2(8)">
        <constraints nullable="false"/>
      </column>
    </createTable>
  </changeSet>
  
  <changeSet id="2" author="opengl-8080">
    <insert tableName="TEST_TABLE">
      <column name="ID" value="1" />
      <column name="CODE" value="hoge" />
      <column name="VALUE" value="HOGE" />
    </insert>
  </changeSet>
</databaseChangeLog>
```

先ほどと同じ方法で liquibase を実行する。

```text:oracle
SQL> select * from test_Table;

        ID CODE     VALUE
---------- -------- --------
         1 hoge     HOGE
```

```text:mysql
mysql> select * from test_table;
+----+------+-------+
| ID | CODE | VALUE |
+----+------+-------+
|  1 | hoge | HOGE  |
+----+------+-------+
1 row in set (0.00 sec)
```

データが登録されている。

##毎回入力するオプションをプロパティファイルにまとめる
```properties:liquibase.properties
driver=oracle.jdbc.driver.OracleDriver
changeLogFile=sample.xml
classpath=.\\ojdbc6.jar
url=jdbc:oracle:thin:@localhost:1521:test
username=test
password=test
```

- `driver` などを毎回コマンドのオプションに設定するのは面倒なので、カレントフォルダに `liquibase.properties` を作成し、そこに設定を記載する。
- バックスラッシュは二重にする。
- url のダブルクォーテーションは不要なので取り除く。

これで以下のコマンドで実行できるようになる。

```text:
>liquibase update
```

##タグを設定してロールバックする
###タグを設定する
```text:
>liquibase tag hoge
Successfully tagged TEST@jdbc:oracle:thin:@localhost:1521:test
Liquibase 'tag' Successful
```

###ChangeLog を更新して DB に反映させる
```xml:sample.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.1.xsd">
  
  <changeSet id="1" author="opengl-8080">
    <createTable tableName="TEST_TABLE">
      <column name="ID" type="int">
        <constraints primaryKey="true" nullable="false"/>
      </column>
      <column name="CODE" type="varchar2(8)">
        <constraints nullable="false"/>
      </column>
      <column name="VALUE" type="varchar2(8)">
        <constraints nullable="false"/>
      </column>
    </createTable>
  </changeSet>
  
  <changeSet id="2" author="opengl-8080">
    <insert tableName="TEST_TABLE">
      <column name="ID" value="1" />
      <column name="CODE" value="hoge" />
      <column name="VALUE" value="HOGE" />
    </insert>
  </changeSet>
  
  <changeSet id="3" author="opengl-8080">
    <addColumn tableName="TEST_TABLE">
      <column name="MEMO" type="varchar2(16)" />
    </addColumn>
  </changeSet>
  
</databaseChangeLog>
```

```text:
>liquibase update
Liquibase Update Successful
```

```text:確認
>sqlplus test/test@test

SQL> desc test_table;
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------------------
 ID                                        NOT NULL NUMBER(10)
 CODE                                      NOT NULL VARCHAR2(8)
 VALUE                                     NOT NULL VARCHAR2(8)
 MEMO                                               VARCHAR2(16)

SQL> quit

>mysql -u test -ptest test

mysql> desc test_table;
+-------+-------------+------+-----+---------+-------+
| Field | Type        | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| ID    | int(11)     | NO   | PRI | NULL    |       |
| CODE  | varchar(8)  | NO   |     | NULL    |       |
| VALUE | varchar(8)  | NO   |     | NULL    |       |
| MEMO  | varchar(16) | YES  |     | NULL    |       |
+-------+-------------+------+-----+---------+-------+
4 rows in set (0.00 sec)
```

###タグを設定したところまでロールバックする
```text:
>liquibase rollback hoge
Liquibase Rollback Successful
```

```text:確認
>sqlplus test/test@test

SQL> desc test_table;
 名前                                      NULL?    型
 ----------------------------------------- -------- ----------------------------
 ID                                        NOT NULL NUMBER(10)
 CODE                                      NOT NULL VARCHAR2(8)
 VALUE                                     NOT NULL VARCHAR2(8)

SQL> quit

>mysql -u test -ptest test

mysql> desc test_table;
+-------+------------+------+-----+---------+-------+
| Field | Type       | Null | Key | Default | Extra |
+-------+------------+------+-----+---------+-------+
| ID    | int(11)    | NO   | PRI | NULL    |       |
| CODE  | varchar(8) | NO   |     | NULL    |       |
| VALUE | varchar(8) | NO   |     | NULL    |       |
+-------+------------+------+-----+---------+-------+
3 rows in set (0.00 sec)
```

- `liquibase tag <タグ名>` で現在の状態でしるし（タグ）を付けることができる。
- `liquibase rollback <タグ名>` で、指定したタグまで DB の状態を戻すことができる。

##異なる DB 間の差分を取る
```properties:liquibase.properties
driver=oracle.jdbc.driver.OracleDriver
changeLogFile=sample.xml
classpath=.\\ojdbc6.jar
url=jdbc:oracle:thin:@localhost:1521:test
username=test
password=test
referenceUrl=jdbc:oracle:thin:@localhost:1521:test
referenceUsername=test2
referencePassword=test2
```

```text:
>liquibase diff


Diff Results:
Reference Database: TEST2 @ jdbc:oracle:thin:@localhost:1521:test (Default Schema: TEST2)
Comparison Database: TEST @ jdbc:oracle:thin:@localhost:1521:test (Default Schema: TEST)
Product Name: EQUAL
Product Version: EQUAL
Missing Catalog(s):
     TEST2
Unexpected Catalog(s):
     TEST
Changed Catalog(s): NONE
Missing Column(s):
     HOGE.ID
     TEST_TABLE.MEMO
Unexpected Column(s): NONE
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): NONE
Changed Index(s): NONE
Missing Primary Key(s): NONE
Unexpected Primary Key(s): NONE
Changed Primary Key(s): NONE
Missing Schema(s): NONE
Unexpected Schema(s): NONE
Changed Schema(s): NONE
Missing Sequence(s): NONE
Unexpected Sequence(s):
     TEST_TABLE_SEQ
Changed Sequence(s): NONE
Missing Table(s):
     HOGE
Unexpected Table(s): NONE
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
Liquibase 'diff' Successful
```

- 比較する DB の設定を `reference**` で追加して `diff` コマンドを実行すると、 DB 間の差分を取ることができる。

##差分を ChangeLog 形式で出力する
```text:
>liquibase diffChangeLog
Liquibase 'diffChangeLog' Successful
```

```xml:sample.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.1.xsd">
  
  <changeSet id="1" author="opengl-8080">
    <createTable tableName="TEST_TABLE">
      <column name="ID" type="int">
        <constraints primaryKey="true" nullable="false"/>
      </column>
      <column name="CODE" type="varchar2(8)">
        <constraints nullable="false"/>
      </column>
      <column name="VALUE" type="varchar2(8)">
        <constraints nullable="false"/>
      </column>
    </createTable>
  </changeSet>
  
  <changeSet id="2" author="opengl-8080">
    <insert tableName="TEST_TABLE">
      <column name="ID" value="1" />
      <column name="CODE" value="hoge" />
      <column name="VALUE" value="HOGE" />
    </insert>
  </changeSet>
  
  <changeSet id="3" author="opengl-8080">
    <addColumn tableName="TEST_TABLE">
      <column name="MEMO" type="varchar2(16)" />
    </addColumn>
  </changeSet>
  
    <changeSet author="xxx (generated)" id="1403615872792-1">
        <createTable tableName="HOGE">
            <column name="ID" type="NUMBER(38)"/>
        </createTable>
    </changeSet>
    <changeSet author="xxx (generated)" id="1403615872792-2">
        <addColumn tableName="TEST_TABLE">
            <column name="MEMO" type="VARCHAR(16)"/>
        </addColumn>
    </changeSet>
    <changeSet author="xxx (generated)" id="1403615872792-3">
        <dropSequence sequenceName="TEST_TABLE_SEQ"/>
    </changeSet>
</databaseChangeLog>
```

- `diffChangeLog` コマンドを実行すると、差分を ChangeLog に追記させることができる。

##データベースの状態を Javadoc 風の HTML で出力する
```text:
>liquibase dbDoc docs
Liquibase 'dbDoc' Successful

>dir /b docs
authors
authors.html
changelogs
changelogs.html
columns
currenttables.html
globalnav.html
index.html
overview-summary.html
pending
recent
stylesheet.css
tables
```

![liquibase.JPG](https://qiita-image-store.s3.amazonaws.com/0/28302/394c1d8b-b10f-f328-8d6b-b374c613fe1c.jpeg)

- `liquibase dbDoc <出力先フォルダ>` で DB の状態を Javadoc 風の HTML で出力できる。
- 各テーブルのカラム定義や、どこまで ChangeLog が適用されているかなどが確認できる。

##既存の DB から ChangeLog を生成する
```properties:liquibase.properties
driver=oracle.jdbc.driver.OracleDriver
changeLogFile=generate.xml
classpath=.\\ojdbc6.jar
url=jdbc:oracle:thin:@localhost:1521:test
username=test
password=test
```

```text:
>liquibase generateChangeLog
Liquibase 'generateChangeLog' Successful
```

生成が成功すると、 `liquibase.properties` で指定していた `changeLogFile=generate.xml` が出力される。

```xml:generate.xml
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<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.0.xsd">
    <changeSet author="xxx (generated)" id="1403616998655-1">
        <createSequence sequenceName="TEST_TABLE_SEQ"/>
    </changeSet>
    <changeSet author="xxx (generated)" id="1403616998655-2">
        <createTable tableName="TEST_TABLE">
            <column name="ID" type="NUMBER(10, 0)">
                <constraints nullable="false"/>
            </column>
            <column name="CODE" type="VARCHAR2(8)">
                <constraints nullable="false"/>
            </column>
            <column name="VALUE" type="VARCHAR2(8)">
                <constraints nullable="false"/>
            </column>
        </createTable>
    </changeSet>
    <changeSet author="xxx (generated)" id="1403616998655-3">
        <addPrimaryKey columnNames="ID" constraintName="PK_TEST_TABLE" tableName="TEST_TABLE"/>
    </changeSet>
</databaseChangeLog>
```

- `generateChangeLog` で、既存の DB の状態を ChangeLog ファイルで出力できる。
- これなら、 Liquibase を適用していない既存の DB でも途中から Liquibase を使い始められる？ wktk
- ただし、現状以下のオブジェクトは出力できない。
	- ストアドプロシージャ
	- ファンクション
	- パッケージ
	- トリガー

##DB に反映されていない ChangeSet を確認する
```text:
>liquibase status --verbose
1 change sets have not been applied to TEST@jdbc:oracle:thin:@localhost:1521:test
     sample.xml::3::opengl-8080
Liquibase 'status' Successful
```

- `liquibase status --verbose` で、まだ DB に反映されていない ChangeSet が分かる。

##DB に適用されているのに ChangeLog に記載されていない変な ChangeSet が無いか確認する
```xml:sample.xml
<?xml version="1.0" encoding="UTF-8"?>
<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.1.xsd">
  
  <changeSet id="1" author="opengl-8080">
    <createTable tableName="TEST_TABLE">
      <column name="ID" type="int">
        <constraints primaryKey="true" nullable="false"/>
      </column>
      <column name="CODE" type="varchar2(8)">
        <constraints nullable="false"/>
      </column>
      <column name="VALUE" type="varchar2(8)">
        <constraints nullable="false"/>
      </column>
    </createTable>
  </changeSet>
  
  <changeSet id="2" author="opengl-8080">
    <insert tableName="TEST_TABLE">
      <column name="ID" value="1" />
      <column name="CODE" value="hoge" />
      <column name="VALUE" value="HOGE" />
    </insert>
  </changeSet>
  
  <changeSet id="3" author="opengl-8080">
    <addColumn tableName="TEST_TABLE">
      <column name="MEMO" type="varchar2(16)" />
    </addColumn>
  </changeSet>
  
</databaseChangeLog>
```

```text:
>liquibase unexpectedChangeSets --verbose
1 unexpected changes were found in TEST@jdbc:oracle:thin:@localhost:1521:test
     sample.xml::4::opengl-8080
Liquibase 'unexpectedChangeSets' Successful
```

- DB には、 sample.xml の ID=4, author=opengl-8080 という ChangeSet が適用されたという記録が残っている。
- しかし、実際の sample.xml にはそんな ChangeSet は無い。
- つまり、 sample.xml が最新になっていない、もしくは誤って ChangeSet を消してしまったような状態。
- これをチェックするには、 `liquibase unexpectedChangeSets --verbose` を実行する。
- 前述の `liquibase status` と合わせることで、 DB と ChangeLog が同期できているかをチェックできる。


#参考
- [Liquibase | Database Refactoring | Liquibase](http://www.liquibase.org/index.html)
- [DBAを救え! DBリファクタリングツール「LiquiBase」を使ってみよう データベースのリファクタリングツール | マイナビニュース](http://news.mynavi.jp/articles/2007/10/25/liquibase/)