在執行資料庫遷移時,我們推薦使用jOOQ與Flyway - 資料庫遷移輕鬆。 在本章中,我們將簡單的來使用這兩個框架。
一、Flyway簡介
什麼是Flyway
Flyway是獨立於資料庫的應用、管理、跟蹤資料庫變更的資料庫版本管理工具。
Flyway的專案主頁是:https://flywaydb.org/
為什麼使用Flyway
不同的開發人員在開發產品特性時,都有可能更新資料庫(新增新表,新的約束等)。當開發人員完成工作並提交程式碼時,程式碼會被合併到主分支並在測試伺服器上執行單元測試與整合測試。我們在哪個環節來執行資料庫的更新操作呢?由QA 部門手工執行sql 指令碼?或者我們開發一斷程式自動執行資料庫更新?以什麼順序來執行這些更新指令碼?這些問題同樣存在於生產環境。
我們的產品部署在不同的客戶伺服器上,以及很多的測試、聯調、實驗局、銷售環境上。不同的客戶和測試環境上都部署著不同版本的產品。當他們需要升級他們的產品到新的版本時,我們不僅需要讓他們的管理員可以升級產品到新的版本,同時需要保留他們的已有資料。在升級產品的步驟中,我們清楚地知道客戶資料庫的當前版本,以及需要在該資料庫上執行哪些資料庫更新指令碼,來更新資料庫表結構與資料庫中已存在的資料。當升級完成時,資料庫表結構及資料應當與升級後的產品版本保持一致。
當升級失敗時(比如在升級過程中出現網路連線失敗),我們應當支援對失敗進行修復。
更多Flyway文章(參考以下文件)
二、Maven配置Flyway和jOOQ外掛
properties
<!-- 資料庫資訊 -->
<db.url>jdbc:mysql://127.0.0.1:33006/flyway_test?useUnicode=true&characterEncoding=UTF-8</db.url>
<db.username>root</db.username>
<db.password>123456</db.password>
plugins
<!-- Flyway外掛 -->
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>3.0</version>
<!-- 在程式碼生成時候執行Flyway外掛 -->
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>migrate</goal>
</goals>
</execution>
</executions>
<!-- 指定Flyway遷移指令碼檔案目錄 -->
<configuration>
<url>${db.url}</url>
<user>${db.username}</user>
<password>${db.password}</password>
<locations>
<location>filesystem:src/main/resources/db/migration</location>
</locations>
</configuration>
</plugin>
<!-- jOOQ程式碼生成外掛 -->
<plugin>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen-maven</artifactId>
<version>${jooq.version}</version>
<executions>
<execution>
<id>java-generator</id>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
<configuration>
<jdbc>
<url>${db.url}</url>
<user>${db.username}</user>
<password>${db.password}</password>
</jdbc>
<generator>
<database>
<name>org.jooq.util.mysql.MySQLDatabase</name>
<includes>.*</includes>
<inputSchema>flyway_test</inputSchema>
</database>
<target>
<packageName>test.generated</packageName>
<directory>target/generated-sources/jooq-mysql-java</directory>
</target>
</generator>
</configuration>
</execution>
</executions>
</plugin>
三、資料庫增量指令碼
假設已經外掛MySQL資料庫:flyway_test
在src/main/resources/db/migration
目錄(Flyway外掛指定的location目錄)下外掛建立指令碼檔案:
V1__create_author.sql
V2__create_book.sql
這三個指令碼按照版本V[1,2,3...]建立,這是指令碼的內容:
# V1__create_author.sql
CREATE TABLE `author` (
`id` int NOT NULL,
`first_name` varchar(255) DEFAULT NULL,
`last_name` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO author VALUES (1, 'George', 'Orwell');
INSERT INTO author VALUES (2, 'Paulo', 'Coelho');
# V2__create_book.sql
CREATE TABLE `book` (
`id` int NOT NULL,
`author_id` int NOT NULL,
`title` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO book VALUES (1, 1, '1984');
INSERT INTO book VALUES (2, 1, 'Animal Farm');
INSERT INTO book VALUES (3, 2, 'O Alquimista');
INSERT INTO book VALUES (4, 2, 'Brida');
四、資料庫遷移 & jOOQ程式碼生成
執行Maven構建程式碼時候,上一步的V1
/V2
指令碼Flyway會按照版本順序去執行,並由jOOQ生成Java程式碼:
mvn clean install
成功後,新建測試用例測試:
import org.jooq.DSLContext;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.SQLDialect;
import org.jooq.impl.DSL;
import java.sql.Connection;
import java.sql.DriverManager;
import static test.generated.tables.Author.AUTHOR;
/**
* 測試類
*/
public class Main {
public static void main(String[] args) {
// 使用者名稱
String userName = "root";
// 密碼
String password = "123456";
// mysql連線url
String url = "jdbc:mysql://127.0.0.1:33006/flyway_test?useUnicode=true&characterEncoding=UTF-8";
// Connection is the only JDBC resource that we need
// PreparedStatement and ResultSet are handled by jOOQ, internally
try (Connection conn = DriverManager.getConnection(url, userName, password)) {
DSLContext create = DSL.using(conn, SQLDialect.MYSQL);
Result<Record> result = create.select().from(AUTHOR).fetch();
for (Record r : result) {
Integer id = r.getValue(AUTHOR.ID);
String firstName = r.getValue(AUTHOR.FIRST_NAME);
String lastName = r.getValue(AUTHOR.LAST_NAME);
/**
* 控制檯輸出
* ID: 1 first name: George last name: Orwell
* ID: 2 first name: Paulo last name: Coelho
*/
System.out.println("ID: " + id + " first name: " + firstName + " last name: " + lastName);
}
// 關閉連線物件
conn.close();
}
// For the sake of this tutorial, let's keep exception handling simple
catch (Exception e) {
e.printStackTrace();
}
}
}
五、資料庫變動
近期書庫裡多了中文書籍,需要為book表新增語言種類欄位
在src/main/resources/db/migration
目錄下新增修改資料庫指令碼:
# V3__book_add_language.sql
ALTER TABLE `book` ADD COLUMN `language_id` int(7) DEFAULT '1' NOT NULL;
重新執行Maven構建:
mvn clean install
測試用例:
import org.jooq.DSLContext;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.SQLDialect;
import org.jooq.impl.DSL;
import java.sql.Connection;
import java.sql.DriverManager;
import static test.generated.tables.Author.AUTHOR;
import static test.generated.tables.Book.BOOK;
/**
* 測試類
*/
public class BookMain {
public static void main(String[] args) {
// 使用者名稱
String userName = "root";
// 密碼
String password = "123456";
// mysql連線url
String url = "jdbc:mysql://127.0.0.1:33006/flyway_test?useUnicode=true&characterEncoding=UTF-8";
// Connection is the only JDBC resource that we need
// PreparedStatement and ResultSet are handled by jOOQ, internally
try (Connection conn = DriverManager.getConnection(url, userName, password)) {
DSLContext create = DSL.using(conn, SQLDialect.MYSQL);
Result<Record> result = create.select().from(BOOK).fetch();
for (Record r : result) {
Integer id = r.getValue(BOOK.ID);
String firstName = r.getValue(BOOK.TITLE);
Integer languageId = r.getValue(BOOK.LANGUAGE_ID);
/**
* 控制檯輸出
* ID: 1 title: 1984 language: 英文
* ID: 2 title: Animal Farm language: 英文
* ID: 3 title: O Alquimista language: 英文
* ID: 4 title: Brida language: 英文
*/
System.out.println("ID: " + id + " title: "
+ firstName + " language: " + (languageId.intValue() == 1 ? "英文" : "中文"));
}
// 關閉連線物件
conn.close();
}
// For the sake of this tutorial, let's keep exception handling simple
catch (Exception e) {
e.printStackTrace();
}
}
}
【jOOQ中文】教程程式碼都會放在碼雲,希望多多宣傳給Star(^_−)☆。