Spring Data JDBC - 如何維護您的資料庫架構

工程 | Jens Schauder | 2023 年 8 月 29 日 | ...

這是關於如何解決在使用 Spring Data JDBC 時可能遇到的各種挑戰的系列文章的第五篇。該系列文章包括

  1. Spring Data JDBC - 如何使用自定義 ID 生成?

  2. Spring Data JDBC - 如何建立雙向關係?.

  3. Spring Data JDBC - 如何實現快取?

  4. Spring Data JDBC - 如何對聚合根進行區域性更新?

  5. Spring Data JDBC - 如何為我的域模型生成架構?(本文)

如果您是 Spring Data JDBC 的新手,建議您先閱讀其簡介這篇解釋聚合在 Spring Data JDBC 上下文中的相關性的文章,以理解基本概念。

使用任何物件關係對映器 (ORM),您都必須建立兩樣東西,並且它們必須相互匹配:

  1. 以 Java 類形式存在的領域模型。
  2. 由表、列、索引和約束組成的資料庫模式。

3.2.0-M1 版本開始,Spring Data Relational 將幫助您完成此操作。本文將向您展示如何使其工作。

建立初始模式

首先要做的是找到一個放置模式生成程式碼的地方。我們建議為此使用測試。您可以從它那裡使用主應用程式的配置,並且它不會在生產環境中意外執行。

接下來要做的是獲取一個 RelationalMappingContext。這是 Spring Data Relational 的核心類,它是 Spring Data JDBC 和 Spring Data R2DBC 的父級。一旦完全初始化,這個類將儲存所有關於您的聚合的對映元資訊。但是這個初始化是惰性發生的,所以您必須自己註冊您的聚合根。

然後,您需要從它建立一個 LiquibaseChangeSetWriter,並使用它來寫入 Liquibase 變更集。

// context is a RelationalMappingContext that you autowire in your test.
context.setInitialEntitySet(Collections.singleton(Minion.class));
LiquibaseChangeSetWriter writer = new LiquibaseChangeSetWriter(context);

writer.writeChangeSet(new FileSystemResource("cs-minimum.yaml"));

為此,您需要在依賴中新增 Liquibase:

<dependency>
    <groupId>org.liquibase</groupId>
    <artifactId>liquibase-core</artifactId>
</dependency>

注意:如果您使用 Spring Boot,Liquibase 依賴將觸發使用 Liquibase 的模式初始化,但會失敗,因為它找不到任何變更集。您可以透過在您的 application.properties 中新增此行來輕鬆停用它。

spring.liquibase.enabled=false

如果執行此測試,您應該在專案根資料夾中找到一個名為 cs-minimum.yaml 的檔案:

databaseChangeLog:
- changeSet:
    id: '1692728224754'
    author: Spring Data Relational
    objectQuotingStrategy: LEGACY
    changes:
    - createTable:
        columns:
        - column:
            autoIncrement: true
            constraints:
              nullable: true
              primaryKey: true
            name: id
            type: BIGINT
        - column:
            constraints:
              nullable: true
            name: name
            type: VARCHAR(255 BYTE)
        tableName: minion

您應該檢視此檔案,根據需要修改它,並將其放置在 Liquibase 可以拾取的位置。如果您之前已停用它,現在請啟用 Liquibase 的模式初始化以實際使用此變更集。

建立更新模式

對於您的應用程式的第二個版本,您可能需要對資料庫模式進行一些更新。Spring Data JDBC 也可以幫助您完成這些工作。

為了建立這種增量模式更新,我們需要提供資料庫的當前狀態。這透過 liquibase.database.Database 例項完成,您可以從 DataSource 建立它。

@Autowired
DataSource ds;

// ...

context.setInitialEntitySet(Collections.singleton(Minion.class));
LiquibaseChangeSetWriter writer = new LiquibaseChangeSetWriter(context);

try (Database db = new HsqlDatabase()) {

	db.setConnection(new JdbcConnection(ds.getConnection()));

	writer.writeChangeSet(new FileSystemResource("cs-diff.yaml"), db);

} catch (IOException | SQLException | LiquibaseException e) {
	throw new RuntimeException("Changeset generation failed", e);
}

上面的例子使用了 HsqlDatabase。您將使用與您的實際資料庫匹配的實現。

預設情況下,變更集從不從您的模式中刪除列或表。僅僅因為它們沒有在領域模型中建模,並不意味著您不需要它們,對嗎?但是,如果您確實想刪除 Java 領域模型中不存在的部分或所有表和列,請註冊一個 DropTableFilterDropColumnFilter,如下面的示例所示,它會刪除所有未對映的列,除了名為 special 的列。

writer.setDropColumnFilter((table, column) -> !column.equalsIgnoreCase("special"));

定製模式生成

Spring Data JDBC 沒有用於為列指定確切資料庫型別的註解。但它提供了一個使用您想要的型別的鉤子。您可以向 LiquibaseChangeSetWriter 提供一個 SqlTypeMapping

writer.setSqlTypeMapping(((SqlTypeMapping) property -> {
	if (property.getName().equalsIgnoreCase("name")) {
		return "VARCHAR(500)";
	}
	return null;
}).and(new DefaultSqlTypeMapping()));

您只需實現該介面的一個方法:String getColumnType(RelationalPersistentProperty property)。在您只想修改某些情況下的型別時,您可以將其與 DefaultSqlTypeMapping 結合使用,後者將用於您的實現返回 null 的所有情況,如示例所示。

使用註解控制模式型別

RelationalPersistentProperty 包含一些非常有用的方法,例如 findAnnotation 用於訪問屬性或其擁有實體上的註解(包括元註解)。您可以使用此功能來使用您自己的註解和元註解來控制用於您的領域模型的資料庫型別。

例如,您可以建立一層註解來指定資料庫級別的型別,並使用第一層註解建立另一組特定於領域的註解,如以下程式碼片段所示:

@Retention(RetentionPolicy.RUNTIME)
public @interface Varchar {

	/**
	 * the size of the varchar.
	 */
	int value();
}
@Varchar(20)
@Retention(RetentionPolicy.RUNTIME)
public @interface Name {
}

然後,您可以使用此註解來註解您的領域模型中的屬性,並使用匹配的 SqlTypeMapping

@Name
String name;
writer.setSqlTypeMapping(((SqlTypeMapping) property -> {

  if (!property.getType().equals(String.class)) {
    return null;
  }

  // findAnnotation will find meta annotations
  Varchar varchar = property.findAnnotation(Varchar.class);
  int value = varchar.value();

  if (varchar == null) {
    return null;
  }
  return "VARCHAR(" +
      varchar.value() +
      ")";

}).and(new DefaultSqlTypeMapping()));

侷限性

模式生成目前不支援引用。這些將被靜默忽略。當然,我們將來會改進這一點。

為什麼如此複雜?

如果您來自 JPA/Hibernate,您習慣於透過簡單的配置直接在資料庫中生成模式,並且將模式資訊作為對映註解的一部分。很自然會問我們為什麼選擇不同的方式。

這個問題有多個答案:

  1. 模式更改可能很危險。

您很容易做一些只能透過應用資料庫備份才能恢復的操作。我們認為讓開發人員在不真正看到,更不用說思考他們所應用的更改的情況下做這種事情是不好的。這就是為什麼我們建立更改,但將應用更改作為單獨的步驟。

  1. 模式更改應由版本控制控制,並且需要由專用工具管理,因為它們不是冪等的。也就是說,您不能重新應用新增表或列的 SQL 指令碼來確保該列存在。

這就是我們選擇 Liquibase 來建立和管理更改的原因。

  1. 資料庫中使用的確切資料型別與物件關係對映器(例如 Spring Data JDBC)無關。

因此,這類資訊不應作為 Spring Data JDBC 使用的對映註解的一部分。相反,這類資訊應以真正獨立於 Spring Data JDBC 的方式從您的模型中派生出來。我們認為所示的元註解方法是實現這一目標的好方法。

結論

憑藉當前的里程碑和即將釋出的 GA 版本,Spring Data JDBC 提供了一種靈活而強大的方法,可以從您的領域模型生成資料庫遷移。我們期待聽到您對此的意見和經驗。

完整的示例程式碼可在 Spring Data 示例儲存庫中找到

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

VMware 提供培訓和認證,助您加速進步。

瞭解更多

獲得支援

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案,只需一份簡單的訂閱。

瞭解更多

即將舉行的活動

檢視 Spring 社群所有即將舉行的活動。

檢視所有