在执行数据库迁移时,我们的Data Geekery建议将jOOQ与Flyway结合使用-轻松实现数据库迁移 。 在本文中,我们将研究一种简单的方法来开始使用这两个框架。
哲学
在各种开发设置中,jOOQ和Flyway可以通过多种方式相互交互。 在本教程中,我们将仅展示这种框架团队的一种变体-对于大多数用例来说,这种变体特别引人注目。
以下方法背后的一般原理和工作流程可总结为:
- 1.数据库增量
- 2.数据库迁移
- 3.代码重新生成
- 4.发展
每当您需要修改数据库中的某些内容时,可以一次又一次地重复上述四个步骤。 更具体地说,让我们考虑:
- 1.数据库增量 – 数据库中需要一个新列,因此您需要在Flyway脚本中编写必要的DDL
- 2.数据库迁移 –该Flyway脚本现在已成为可交付成果的一部分,您可以与所有可以使用其迁移数据库的开发人员共享,下次他们检查您的更改时
- 3.代码重新生成 –迁移数据库后,您将在本地重新生成所有jOOQ工件(请参阅代码生成 )。
- 4.开发 –您将继续开发业务逻辑,针对经过修改的生成的数据库模式编写代码
Maven项目配置–属性
在我们的pom.xml中定义了以下属性,以便能够在插件配置之间重用它们:
<properties>
<db.url>jdbc:h2:~/flyway-test</db.url>
<db.username>sa</db.username>
</properties>
Maven项目配置–依赖关系
尽管可以在独立的迁移脚本中使用jOOQ和Flyway,但在本教程中,我们将使用Maven进行标准项目设置。 您还将在GitHub上找到本教程的源代码,并在此处 找到完整的pom.xml文件 。
这些是我们在Maven配置中使用的依赖项:
<!-- We'll add the latest version of jOOQ
and our JDBC driver - in this case H2 -->
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>1.4.177</version>
</dependency>
<!-- For improved logging, we'll be using
log4j via slf4j to see what's going
on during migration and code generation -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.16</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
</dependency>
<!-- To esnure our code is working, we're
using JUnit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
Maven项目配置–插件
在依赖关系之后,让我们简单地像这样添加Flyway和jOOQ Maven插件。 Flyway插件:
<plugin>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-maven-plugin</artifactId>
<version>3.0</version>
<!-- Note that we're executing the Flyway
plugin in the "generate-sources" phase -->
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>migrate</goal>
</goals>
</execution>
</executions>
<!-- Note that we need to prefix the db/migration
path with filesystem: to prevent Flyway
from looking for our migration scripts
only on the classpath -->
<configuration>
<url>${db.url}</url>
<user>${db.username}</user>
<locations>
<location>filesystem:src/main/resources/db/migration</location>
</locations>
</configuration>
</plugin>
上面的Flyway Maven插件配置将在编译Java源代码之前从src/main/resources/db/migration
读取并执行所有数据库迁移脚本。 尽管Flyway 官方文档建议在compile
阶段进行迁移,但是jOOQ代码生成器依赖于在代码生成之前完成的此类迁移。
在Flyway插件之后,我们将添加jOOQ Maven插件。 有关更多详细信息,请参阅手册中有关代码生成配置的部分 。
<plugin>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen-maven</artifactId>
<version>${org.jooq.version}</version>
<!-- The jOOQ code generation plugin is also
executed in the generate-sources phase,
prior to compilation -->
<executions>
<execution>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<!-- This is a minimal working configuration.
See the manual's section about the code
generator for more details -->
<configuration>
<jdbc>
<url>${db.url}</url>
<user>${db.username}</user>
</jdbc>
<generator>
<database>
<includes>.*</includes>
<inputSchema>FLYWAY_TEST</inputSchema>
</database>
<target>
<packageName>org.jooq.example.flyway.db.h2</packageName>
<directory>target/generated-sources/jooq-h2</directory>
</target>
</generator>
</configuration>
</plugin>
现在,此配置将读取FLYWAY_TEST
模式,并将其反向工程到target/generated-sources/jooq-h2
目录中,并在其中进入org.jooq.example.flyway.db.h2
包。
1.数据库增量
现在,当我们开始开发数据库时。 为此,我们将创建数据库增量脚本,并将其放入src/main/resources/db/migration
目录中,如先前为Flyway插件配置的那样。 我们将添加以下文件:
- V1__initialise_database.sql
- V2__create_author_table.sql
- V3__create_book_table_and_records.sql
这三个脚本为我们的架构版本1-3(请注意大写的V!)建模。 这是脚本的内容
-- V1__initialise_database.sql
DROP SCHEMA flyway_test IF EXISTS;
CREATE SCHEMA flyway_test;
-- V2__create_author_table.sql
CREATE SEQUENCE flyway_test.s_author_id START WITH 1;
CREATE TABLE flyway_test.author (
id INT NOT NULL,
first_name VARCHAR(50),
last_name VARCHAR(50) NOT NULL,
date_of_birth DATE,
year_of_birth INT,
address VARCHAR(50),
CONSTRAINT pk_t_author PRIMARY KEY (ID)
);
-- V3__create_book_table_and_records.sql
CREATE TABLE flyway_test.book (
id INT NOT NULL,
author_id INT NOT NULL,
title VARCHAR(400) NOT NULL,
CONSTRAINT pk_t_book PRIMARY KEY (id),
CONSTRAINT fk_t_book_author_id FOREIGN KEY (author_id) REFERENCES flyway_test.author(id)
);
INSERT INTO flyway_test.author VALUES (next value for flyway_test.s_author_id, 'George', 'Orwell', '1903-06-25', 1903, null);
INSERT INTO flyway_test.author VALUES (next value for flyway_test.s_author_id, 'Paulo', 'Coelho', '1947-08-24', 1947, null);
INSERT INTO flyway_test.book VALUES (1, 1, '1984');
INSERT INTO flyway_test.book VALUES (2, 1, 'Animal Farm');
INSERT INTO flyway_test.book VALUES (3, 2, 'O Alquimista');
INSERT INTO flyway_test.book VALUES (4, 2, 'Brida');
2.数据库迁移和3.代码重新生成
以上三个脚本由Flyway拾取,并按版本顺序执行。 这可以通过执行以下命令非常简单地看到:
mvn clean install
然后观察Flyway的日志输出...
[INFO] --- flyway-maven-plugin:3.0:migrate (default) @ jooq-flyway-example ---
[INFO] Database: jdbc:h2:~/flyway-test (H2 1.4)
[INFO] Validated 3 migrations (execution time 00:00.004s)
[INFO] Creating Metadata table: "PUBLIC"."schema_version"
[INFO] Current version of schema "PUBLIC": <>
[INFO] Migrating schema "PUBLIC" to version 1
[INFO] Migrating schema "PUBLIC" to version 2
[INFO] Migrating schema "PUBLIC" to version 3
[INFO] Successfully applied 3 migrations to schema "PUBLIC" (execution time 00:00.073s).
…以及从控制台上的jOOQ:
[INFO] --- jooq-codegen-maven:3.5.0-SNAPSHOT:generate (default) @ jooq-flyway-example ---
[INFO] Using this configuration:
...
[INFO] Generating schemata : Total: 1
[INFO] Generating schema : FlywayTest.java
[INFO] ----------------------------------------------------------
[....]
[INFO] GENERATION FINISHED! : Total: 337.576ms, +4.299ms
4.发展
请注意,每次有人将新的迁移脚本添加到Maven模块时,上述所有步骤都会自动执行。 例如,一个团队成员可能已经提交了一个新的迁移脚本,您可以将其检出,重建并为您自己的开发或集成测试数据库获取jOOQ生成的最新资源。
现在,这些步骤已完成,您可以继续编写数据库查询。 想象以下测试案例
import org.jooq.Result;
import org.jooq.impl.DSL;
import org.junit.Test;
import java.sql.DriverManager;
import static java.util.Arrays.asList;
import static org.jooq.example.flyway.db.h2.Tables.*;
import static org.junit.Assert.assertEquals;
public class AfterMigrationTest {
@Test
public void testQueryingAfterMigration() throws Exception {
try (Connection c = DriverManager.getConnection("jdbc:h2:~/flyway-test", "sa", "")) {
Result<?> result =
DSL.using(c)
.select(
AUTHOR.FIRST_NAME,
AUTHOR.LAST_NAME,
BOOK.ID,
BOOK.TITLE
)
.from(AUTHOR)
.join(BOOK)
.on(AUTHOR.ID.eq(BOOK.AUTHOR_ID))
.orderBy(BOOK.ID.asc())
.fetch();
assertEquals(4, result.size());
assertEquals(asList(1, 2, 3, 4), result.getValues(BOOK.ID));
}
}
}
如果再次运行mvn clean install
,上述集成测试现在将编译并通过!
重申
一旦您开始以这种方式执行数据库修改,这种方法的力量就会变得清晰。 假设我们团队中的法国人更喜欢以自己的方式做事(无意冒犯):
-- V4__le_french.sql
ALTER TABLE flyway_test.book
ALTER COLUMN title RENAME TO le_titre;
他们签入,您签出新的数据库迁移脚本,运行
mvn clean install
然后观察日志输出:
[INFO] --- flyway-maven-plugin:3.0:migrate (default) @ jooq-flyway-example ---
[INFO] --- flyway-maven-plugin:3.0:migrate (default) @ jooq-flyway-example ---
[INFO] Database: jdbc:h2:~/flyway-test (H2 1.4)
[INFO] Validated 4 migrations (execution time 00:00.005s)
[INFO] Current version of schema "PUBLIC": 3
[INFO] Migrating schema "PUBLIC" to version 4
[INFO] Successfully applied 1 migration to schema "PUBLIC" (execution time 00:00.016s).
到目前为止一切顺利,但随后:
[ERROR] COMPILATION ERROR :
[INFO] -------------------------------------------------------------
[ERROR] C:\...\AfterMigrationTest.java:[24,19] error: cannot find symbol
[INFO] 1 error
回到Java集成测试时,我们可以立即看到TITLE列仍在被引用,但不再存在:
public class AfterMigrationTest {
@Test
public void testQueryingAfterMigration() throws Exception {
try (Connection c = DriverManager.getConnection("jdbc:h2:~/flyway-test", "sa", "")) {
Result<?> result =
DSL.using(c)
.select(
AUTHOR.FIRST_NAME,
AUTHOR.LAST_NAME,
BOOK.ID,
BOOK.TITLE
// ^^^^^ This column no longer exists.
// We'll have to rename it to LE_TITRE
)
.from(AUTHOR)
.join(BOOK)
.on(AUTHOR.ID.eq(BOOK.AUTHOR_ID))
.orderBy(BOOK.ID.asc())
.fetch();
assertEquals(4, result.size());
assertEquals(asList(1, 2, 3, 4), result.getValues(BOOK.ID));
}
}
}
结论
本教程非常轻松地展示了如何使用Flyway和jOOQ构建坚如磐石的开发流程,以在开发生命周期的早期(即在编译时而不是在生产时)防止与SQL有关的错误!