# 讓開發自動化: 實現自動化數據庫遷移
_使用 LiquiBase 管理數據庫變更_
數據庫通常不能夠與它們支持的應用程序保持同步,從管理方面來講,將數據庫和數據置于一個已知狀態是個很大的挑戰。在本期的 [_讓開發自動化_](http://www.ibm.com/developerworks/cn/java/j-ap/)中,自動化專家 Paul Duvall 演示了如何使用開源的 LiquiBase 數據庫遷移工具輕松地處理數據庫和應用程序的頻繁變更。
在過去幾年中,我使用過的大多數應用程序都是需要管理大量數據的企業應用程序。從事這類項目的開發團隊常常將數據庫視為與應用程序完全脫離的單獨實體。造成這種現象的原因是組織結構經常將數據庫團隊從應用程序開發團隊分離出來。有時候,這是團隊的習慣引起的。不管怎樣,我發現這種分離會導致(或忽略)一些實踐:
* 手工變更數據庫
* 不能與團隊的其他成員分享數據庫變更
* 使用不一致的方法變更數據庫或數據
* 使用低效的手工方法管理數據庫版本之間的變更
這些實踐效率低下,使開發人員無法與數據變更保持同步。而且,還使應用程序的 _用戶_遇到與數據不一致和數據損壞等問題。
圖 1 演示了軟件開發項目中經常用到的手工方法。手工方法在使用時通常不能保證一致性并且容易產生錯誤,撤銷已完成的工作很困難,而且難以分析數據庫變更的歷史。例如,某個 DBA 可能想變更查找數據,但是開發人員卻忘記將這個數據插入到同一個表中。
## 關于本系列
作為開發人員,我們致力于用戶自動化流程;但許多開發人員卻疏忽了自動化自己的開發流程。為此,我們編寫了 [_讓開發自動化_](http://www.ibm.com/developerworks/cn/java/j-ap/)系列文章,專門探討軟件開發流程自動化的實踐應用,為您介紹 _何時_以及 _如何_成功應用自動化。
##### 圖 1\. 手工變更數據庫

通過實現最小化人為干預的數據庫變更策略,可以避免手工方法帶來的缺陷。通過結合各種實踐和工具,可以使用一致且可重復的過程變更數據庫和數據。在本文中,我將介紹以下內容:
* 使用一種稱為 LiquiBase 的工具在各個數據庫版本之間進行遷移
* 如何自動運行數據庫遷移
* 一致地變更數據庫的實踐
* 使用 LiquiBase 進行數據庫重構
在圖 2 中,一個 Build/Continuous Integration 服務器輪詢版本控制庫(例如子版本)中的變更。當它發現一個變更后,將運行一個自動化構建腳本,該腳本使用 LiquiBase 更新數據庫。
##### 圖 2\. 自動化數據庫遷移

通過使用類似圖 2 所示的過程,團隊中的任何人都可以將相同的變更應用到數據庫中 —可以是本地或共享數據庫服務器。此外,由于這個過程使用了自動化腳本,因此這些變更不需要任何人為干預就可以應用到不同環境中。
## DDL、DML 模式、數據庫或數據
在本文中,我使用術語 _數據庫變更_表示通過應用數據定義語言(Data Definition Language,DDL)腳本變更數據庫結構。(一些數據庫供應商將之稱為 _模式_)。同時,我將通過數據定義語言(DML)腳本變更數據庫稱為 _數據_變更。
## 使用 LiquiBase 管理數據庫變更
LiquiBase(從 2006 年開始投入使用)是一種免費開源的工具,可以實現不同數據庫版本之間的遷移(參見 [參考資料](#resources))。目前也存在少量其他開源數據庫遷移工具,包括 openDBcopy 和 dbdeploy。LiquiBase 支持 10 種數據庫類型,包括 DB2、Apache Derby、MySQL、PostgreSQL、Oracle、Microsoft?SQL Server、Sybase 和 HSQL。
要安裝 LiquiBase,下載經過壓縮的 LiquiBase Core 文件,解壓縮,然后將包含的 liquibase-_version_.jar 文件放到系統路徑中。
要開始使用 LiquiBase,需要以下四個步驟:
1. 創建一個數據庫 _變更日志(change log)_文件。
2. 在變更日志文件內部創建一個 _變更集(change set)_。
3. 通過命令行或構建腳本對數據庫運行變更集。
4. 檢驗數據庫中的變更。
### 創建一個變更日志和變更集
要運行 LiquiBase,如清單 1 所示,首先要創建一個 XML 文件,也稱為數據庫變更日志:
##### 清單 1\. 在 LiquiBase XML 文件中定義一個變更集
```
<?xml version="1.0" encoding="UTF-8"?>
<databaseChangeLogxmlns="http://www.liquibase.org/xml/ns/dbchangelog/1.7"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog/1.7
http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-1.7.xsd">
<changeSetid="2" author="paul">
<createTable tableName="brewer">
<column name="id" type="int">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="name" type="varchar(255)">
<constraints nullable="false"/>
</column>
<column name="active" type="boolean" defaultValue="1"/>
</createTable>
</changeSet>
</databaseChangeLog>
```
## 再次使用 XML
有些開發人員會使用 XML,而其他人則沒有涉足這個領域。很多開發人員甚至已經習慣使用 XML 腳本進行編程(例如,使用 Apache Ant),但是 DBA 不一定用到。最近,我非常興奮地向一個 DBA 同事展示 LiquiBase 的一些特性。他非常喜歡其中一些強大的數據庫變更管理工具,但是很懷疑 DBA 是否會使用基于 XML 的語法。我向他保證,LiquiBase 還通過它的 `sqlFile`和 `sql`定制重構支持調用定制 SQL 腳本。
可以看到,數據庫變更日志文件包括一個 XML 模式引用(LiquiBase 安裝中包含的 dbchangelog-1.7.xsd 文件)。我在變更日志文件中創建了一個 `<changeSet>`。在 `<changeSet>`中,我使用結構化的方式將變更應用到數據庫,如 LiquiBase 模式所定義。
### 從命令行運行 LiquiBase
定義完變更集后,可以從命令行運行 LiquiBase,如清單 2 所示:
##### 清單 2\. 從命令行運行 LiquiBase
```
liquibase --driver=org.apache.derby.jdbc.EmbeddedDriver \
--classpath=derby.jar \
--changeLogFile=database.changelog.xml\
--url=jdbc:derby:brewery;create=true \
--username= --password= \
update
```
在本例中,運行 LiquiBase 傳入的內容:
* 數據庫驅動器
* 數據庫驅動器 JAR 文件的位置所在的類路徑
* 所創建的變更日志文件(如 [清單 1](#listing1)所示)名稱為 database.changelog.xml
* 數據庫的 URL
* 用戶名和密碼
最后,清單 2 調用 `update`命令告訴 LiquiBase 將變更應用到數據庫中。
### 在自動構建中運行 LiquiBase
這里并不使用命令行選項,通過調用 LiquiBase 提供的 Ant 任務,可以將數據庫變更作為自動化構建的一部分。清單 3 展示了 Ant 任務的示例:
##### 清單 3\. 執行 `updateDatabase`Ant 任務的 Ant 腳本
```
<target name="update-database">
<taskdef name="updateDatabase" classname="liquibase.ant.DatabaseUpdateTask"
classpathref="project.class.path" />
<updateDatabase changeLogFile="database.changelog.xml"
driver="org.apache.derby.jdbc.EmbeddedDriver"
url="jdbc:derby:brewery"
username=""
password=""
dropFirst="true"
classpathref="project.class.path"/>
</target>
```
在清單 3 中,創建了一個名為 `update-database`的目標。在其中定義了一個將要用到的特殊 LiquiBase Ant 任務,稱為 `updateDatabase`。我傳入需要的值,包括 `changeLogFile`(指定 [清單 1](#listing1)中定義的變更日志文件)和數據庫的連接信息。`classpathref`中定義的類路徑必須包含 liquibase-_version_.jar。
### 運行前后
圖 3 展示了在 [清單 1](#listing1)中運行變更集之前的數據庫狀態:
##### 圖 3\. 運行 LiquiBase 變更集之前的數據庫狀態

圖 4 展示了運行數據庫變更集的結果,可以通過命令行(如 [清單 2](#listing2)所示)或從 Ant(如 [清單 3](#listing3)所示)運行:
##### 圖 4\. 運行 LiquiBase 變更集后將變更應用到數據庫

查看 [完整的圖](sidefile.html)。
需要注意圖 4 中的幾個方面。創建了兩個特定于 LiquiBase 的表,以及一個根據 [清單 1](#listing1)中的變更集定義創建的新表。第一個特定于 LiquiBase 的表稱為 `databasechangelog`,它跟蹤應用到數據庫的所有變更 —有助于跟蹤誰執行了數據庫變更以及原因。第二個特定于 LiquiBase- 的表是 `databasechangelock`,標識出具有數據庫變更鎖的用戶。
還可以使用多種其他方式運行 LiquiBase,但我已經介紹了應用數據庫變更所需的大部分信息。在使用 LiquiBase 時,將花很多時間研究應用數據庫重構的各種方法,以及變更特定數據庫的復雜性。例如,LiquiBase 提供了數據回滾支持,這可能是個很大的挑戰。在展示數據庫重構示例之前,我將快速瀏覽一些數據庫集成的基本原則和實踐,它們能幫助您充分利用數據庫遷移。
* * *
## 頻繁集成數據庫變更
最近幾年,開發團隊將類似于處理源代碼的原則和實踐應用到數據庫資產管理中。因此,可以將數據庫變更編寫為腳本、在一個源代碼庫中共享這些資產,以及將變更集成到構建和持續集成過程,這只是自然的演進。表 1 概括了開發團隊將數據庫變更變成一個自動化過程的一部分時,需要遵循的關鍵實踐:
## 自動化 DBA
在我曾經參與的一些項目中,DBA 在控制開發數據庫的變更時造成了一些不必要的瓶頸。DBA 應該把時間花在一些創新的、非重復性行為,例如監視和改善數據庫性能,而不是假借控制性和一致性的名義做一些無用的重復性工作。
##### 表 1\. 數據庫集成實踐
| 實踐 | 說明 |
| --- | --- |
| 腳本化所有 DDL 和 DML | 數據庫變更應該能夠從命令行運行。 |
| 數據資產的源代碼控制 | 使用一個版本控制庫管理所有與數據庫相關的變更。 |
| 本地數據庫沙盒 | 每個開發人員使用一個本地數據庫沙盒執行變更。 |
| 自動化數據庫集成 | 將數據庫相關的變更作為構建過程的一部分。 |
這些實踐確保了更好的一致性并防止變更在軟件版本轉換之間丟失。
* * *
## 對現有數據庫應用重構
隨著新特性添加到了應用程序中,經常需要變更數據庫的結構或修改表約束。LiquiBase 提了超過 30 種數據庫重構支持(參見 [參考資料](#resources))。本節將介紹 4 種重構:添加列(Add Column)、刪除列(Drop Column)、創建表(Create Table)和操作數據。
### 添加列
在項目的開始,幾乎不可能考慮到數據庫中的所有列。而有時候,用戶要求新的特性 —例如為存儲在系統中的信息收集更多的數據 —這就要求添加新的列。清單 4 使用 LiquiBase `addColumn`重構,向數據庫中的 `distributor`表添加了一個列:
##### 清單 4\. 使用 LiquiBase 變更集中的 Add Column 數據庫重構
```
<changeSet id="4" author="joe">
<addColumn tableName="distributor">
<column name="phonenumber" type="varchar(255)"/>
</addColumn>
</changeSet>
```
新的 `phonenumber`列被定義為 `varchar`數據類型。
### 刪除列
假如在以后幾個版本中,您想要刪除在清單 4 添加的 `phonenumber`列。只需要調用 `dropColumn`重構,如清單 5 所示:
##### 清單 5\. 刪除一個數據庫列
```
<dropColumn tableName="distributor" columnName="phonenumber"/>
```
### 創建表
向數據庫添加一個新表也是常見的數據庫重構。清單 6 創建了一個新表 `distributor`,定義了列、約束和默認值:
##### 清單 6\. 在 LiquiBase 中創建一個新數據庫表
```
<changeSet id="3" author="betsey">
<createTable tableName="distributor">
<column name="id" type="int">
<constraints primaryKey="true" nullable="false"/>
</column>
<column name="name" type="varchar(255)">
<constraints nullable="false"/>
</column>
<column name="address" type="varchar(255)">
<constraints nullable="true"/>
</column>
<column name="active" type="boolean" defaultValue="1"/>
</createTable>
</changeSet>
```
這個示例使用了 `createTable`數據庫重構作為變更集的一部分([清單 1](#listing1)中也使用了 `createTable`)。
### 操作數據
在應用了結構性數據重構后(例如添加列和創建表),通常需要向受重構影響的表中插入數據。此外,可能需要修改查找表(或其他類型的表)中的現有數據。清單 7 展示了如何使用一個 LiquiBase 變更集插入數據:
##### 清單 7\. 使用一個 LiquiBase 變更集插入數據
```
<changeSet id="3" author="betsey">
<code type="section" width="100%">
<insert tableName="distributor">
<column name="id" valueNumeric="3"/>
<column name="name" value="Manassas Beer Company"/>
</insert>
<insert tableName="distributor">
<column name="id" valueNumeric="4"/>
<column name="name" value="Harrisonburg Beer Distributors"/>
</insert>
</changeSet>
```
您應該編寫用于操作數據的 SQL 腳本,因為使用 LiquiBase XML 變更集限制很多。有時候使用 SQL 腳本向數據庫應用大量的變更會簡單一些。LiquiBase 也可以支持這些情景。清單 8 調用變更集中的 `insert-distributor-data.sql`來插入 `distributor`表數據:
##### 清單 8\. 從 LiquiBase 變更集運行一個定制 SQL 文件
```
<changeSet id="6" author="joe">
<sqlFile path="insert-distributor-data.sql"/>
</changeSet>
```
LiquiBase 支持很多其他數據庫重構,包括 Add Lookup Table 和 Merge Columns。可以使用如清單 4 到清單 8 所示的方式定義所有這些支持。
* * *
## 持續保持數據同步
在軟件開發中,如果遇到一些難題,您應該更多地關注它,而不是等到以后才手動執行這些操作,從而使問題變得更嚴重,花費也更大。數據庫遷移非常重要,自動化遷移過程能夠獲得很多好處。在本文中,我已經介紹了以下內容:
* 演示如何使用 LiquiBase 腳本化數據庫遷移并將這些變更變成自動化構建過程的一部分
* 描述了實現一致性的數據庫集成的原則和實踐
* 展示如何通過使用 LiquiBase 應用數據庫重構,比如 Add Column、Create Table 和更新數據
表 2 總結了 LiquiBase 提供的一些特性的列表:
##### 表 2\. LiquiBase 部分特性總結
| 特性 | 說明 |
| --- | --- |
| 支持多個數據庫 | 支持 DB2、Apache Derby、MySQL、PostgreSQL、Oracle、Microsoft SQL Server、Sybase 和 HSQL 等。 |
| 查看應用到數據庫的變更的歷史。 | 使用 `databasechangelog`表,可以查看應用到數據庫的每一個變更。 |
| 生成數據庫差異日志 | 了解 LiquiBase 變更集以外的應用到數據庫的變更。 |
| 能夠運行定制 SQL 腳本 | 使用 LiquiBase 調用已經編寫好的 SQL 腳本。 |
| 回滾數據庫變更的工具 | 可以對應用到數據庫的任何變更執行回滾。 |
可以看到,通過自動化腳本恰當地應用變更時,數據庫遷移變得更加輕松,并且成為團隊中的多數成員都可以運行的重復過程。
- 讓開發自動化
- 讓開發自動化: 部署自動化模式,第 2 部分
- 讓開發自動化: 部署自動化模式,第 1 部分
- 讓開發自動化: 使用基于向導的安裝程序
- 讓開發自動化: 針對廣大開發人員的并行開發
- 讓開發自動化: 實現自動化數據庫遷移
- 讓開發自動化: 持續重構
- 讓開發自動化: 文檔化一鍵通
- 讓開發自動化: 利用 Ivy 管理依賴項
- 讓開發自動化: 自動負載測試
- 讓開發自動化: 使用自動化加速部署
- 讓開發自動化: 持續集成反模式
- 讓開發自動化: 斷言架構可靠性
- 讓開發自動化: 持續測試
- 讓開發自動化: 用 Eclipse 插件提高代碼質量
- 讓開發自動化: 除掉構建腳本中的氣味
- 讓開發自動化: 選擇持續集成服務器
- 讓開發自動化: 持續檢查