Spring Data 2021.0 有哪些新特性?

工程 | Mark Paluch | 2021年4月21日 | ...

Spring Data 2021.0,代號 Pascal,是採用新的六個月釋出週期後的第二個版本。它對許多現有介面和程式設計模型進行了改進。這篇博文解釋了以下主題

CrudRepositoryReactiveCrudRepository 引入 deleteAllById

自誕生以來,CrudRepository 就定義了根據識別符號刪除單個實體的方法。在 1.x 開發線中,delete(…) 方法被過載以接受多種引數型別,例如 delete(ID id)delete(Iterable<? extends T> entities)

隨著 Spring Data 2.0 版本的釋出,我們重新命名了 CrudRepository 方法,以表明特定方法接受的引數。重新命名後,方法看起來像 deleteById(ID id)deleteAll(Iterable<? extends T> entities)。這種改進的命名約定為引入一個根據識別符號刪除實體的方法留出了一些空間。從這個版本開始,CrudRepositoryReactiveCrudRepository 都定義了 deleteAllById(Iterable<? extends ID> ids) 來刪除多個實體。

根據實際的資料儲存模組,如果資料儲存支援,這可以是一個批次刪除(透過查詢刪除)。例如,JPA 實現仍然會先例項化所有實體,然後立即刪除它們,這樣對即將被刪除的例項仍然會呼叫生命週期回撥。在 JpaRepoository 中還引入了一個額外的 deleteAllByIdInBatch(…) 方法,以便使用批次查詢提供更快的執行變體。

支援 Spring Core Java Flight Recorder (JFR) 指標

Java Flight Recorder (JFR) 是一個用於收集、診斷和分析執行中的 Java 應用資料的工具。它與 Java 執行時緊密整合,可以在生產環境中以低開銷收集事件。

Spring Data repository 通常在應用啟動時引導,因此它們自然會影響啟動時間。Pascal 版本引入了與 Spring Framework 對捕獲啟動事件的支援的整合,該支援自版本 5.3 起可用。透過啟用 JFR 記錄,您可以收集和分析以下 repository 啟動事件

對於每個啟用的 Spring Data 模組 (@Enable…Repositories)

  • spring.data.repository.scanning: Repository 介面掃描

對於每個 Repository

  • spring.data.repository.init: Repository 初始化

  • spring.data.repository.metadata: 元資料檢索

  • spring.data.repository.composition: Repository 組合的組裝

  • spring.data.repository.target: Repository 目標建立

  • spring.data.repository.proxy: Repository 代理建立

  • spring.data.repository.postprocessors: Repository 代理後處理

您可以透過在所有 Java 9 或更新的執行時或 Java 8 update 262 或更高版本上使用 java -XX:StartFlightRecording:filename=recording.jfr,duration=10s -jar … 啟動應用程式來啟用 JFR 記錄。

為屬性路徑渲染啟用 KPropertyKPropertyPath 的型別安全和重構安全使用

Spring Data Kotlin 整合是我們語言特定擴充套件中“語法糖”增強的強大驅動力。Kotlin 允許將單個屬性作為屬性引用(data class Book(val title: String), Book::title)。由於 Kotlin 編譯器會立即拒絕無效引用,因此它們是重構和編譯安全的。現代 IDE 支援在重新命名屬性時考慮屬性引用,消除了純字串中殘留引用的風險。

Spring Data MongoDB 2.2 為其 Criteria API 引入了對 KPropertyKPropertyPath 的支援。

屬性的經典用法

val classic = Criteria("title").isEqualTo("Moby-Dick")
  .and("price").lt(950)

val typed = (Book::title isEqualTo "Moby-Dick")
  .and(Book::price).lt(950)

Spring Data Commons 2.5 將 KPropertyPath 提升為 Spring Data 中的頂級概念。為了不需要對接受屬性路徑的所有方法進行擴充套件或更改,您可以將 KPropertyPath 與現有的 Spring Data 工具一起使用,透過渲染屬性路徑來實現

// KPropertyPath variant
Sort.by((Book::author / Author::name).toDotPath())

// String-path equivalent
Sort.by("author.name")

// KPropertyPath variant
ExampleMatcher.matching()
  .withMatcher((Book::author / Author::name).toDotPath(), contains())

// String-path equivalent
ExampleMatcher.matching()
  .withMatcher("author.name", contains())

從釋出火車中移除 Spring Data for Apache Solr

此釋出火車不再包含 Spring Data for Apache Solr。繼 2020 年棄用 Spring Data Solr 後,團隊決定停止維護 Solr 模組。然而,我們將繼續為維護的 4.2 和 4.3 開發線釋出服務版本,直到它們分別在 2021 年 5 月和 2021 年 11 月達到生命週期結束。展望未來,我們建議使用 Spring Data Elasticsearch 作為全文搜尋安排的首選 Spring Data 模組。Spring Data Elasticsearch 是一個活躍維護的社群模組。

R2DBC 和 Oracle 支援 QueryByExample

Query by Example 是一種使用者友好的查詢技術,具有簡單的介面。它允許動態建立查詢,並且不需要編寫包含欄位名稱的查詢。實際上,Query by Example 根本不需要您使用 SQL 編寫查詢。它適用於多個 Spring Data 模組。從 Spring Data R2DBC 1.3 開始,您可以透過 Spring Data R2DBC 的 ReactiveQueryByExampleExecutor 實現使用示例來查詢關係資料。

PersonRepository people  = …;
DatabaseClient client = …;

var skyler = new Person(null, "Skyler", "White", 45);
var walter = new Person(null, "Walter", "White", 50);
var flynn = new Person(null, "Walter Jr. (Flynn)", "White", 17);
var marie = new Person(null, "Marie", "Schrader", 38);
var hank = new Person(null, "Hank", "Schrader", 43);

var example = Example.of(new Person(null, null, "White", null));

people.count(example).as(StepVerifier::create)
  .expectNext(3L)
  .verifyComplete();


var example = Example.of(new Person(null, "Walter", "WHITE", null), matching()
  .withIgnorePaths("age"). //
  .withMatcher("firstname", startsWith())
  .withMatcher("lastname", ignoreCase()));

people.findAll(example).collectList()
  .as(StepVerifier::create)
  .consumeNextWith(actual -> {
    assertThat(actual).containsExactlyInAnyOrder(flynn, walter);
  })
  .verifyComplete();

除了其他改進,您還可以將 Spring Framework 5.3.6 和 Spring Data R2DBC 1.3 與 Oracle 的 oracle-r2dbc (com.oracle.database.r2dbc:oracle-r2dbc) 驅動程式一起使用。使用 Oracle ConnectionFactory 建立 DatabaseClientR2dbcEntityTemplate 會選擇適當的繫結標記策略和方言。

為 Repository 和 CassandraTemplate 啟用 Cassandra Prepared Statements

Spring Data for Apache Cassandra 在可能的情況下會適應 Cassandra 特定的功能。自其 2.0 版本進行重大重寫以來,我們在 CqlTemplate 級別引入了 CachedPreparedStatementCreator 用於預處理語句快取,這允許使用純 CQL 的預處理語句。

透過此版本,我們在 CassandraTemplate 及其響應式和非同步變體上帶來了預處理語句支援。實際上,預處理語句預設啟用。CqlTemplateCassandraTemplate 之間的主要區別在於抽象級別和 CQL 語句建立的責任。CqlTemplate 需要 CQL 作為輸入。CassandraTemplate 使用實體作為輸入,並根據應使用實體執行的實際操作生成 CQL 語句。

提供預處理語句功能的變化在執行查詢時帶來了一些變化

  1. 當使用 StatementBuilder 時,引數按索引繫結。在構建用於實體相關操作的 CQL 查詢的所有安排中都使用了 StatementBuilder

  2. 當按索引繫結引數時,檢查 SimpleStatement 會在其 CQL 中渲染引數繫結標記。CqlTemplate 的 CQL 日誌也會受到此變化的影響:現在日誌中的 CQL 包含 ? 而不是文字值。

這些變化是允許對帶引數的語句進行語句準備所必需的。要執行的語句首先被準備好。然後,在第二步中,它與實際引數繫結,然後傳送到伺服器執行。

Cassandra 的 Java 驅動程式會跟蹤預處理語句快取,因此在 bean 設定方面不需要任何額外工作。通常,您應該會獲得更好的查詢效能。此外,請記住,預處理語句快取需要額外的記憶體來跟蹤預處理語句。

您可以在 CassandraTemplate 及其響應式和非同步變體上停用預處理語句的使用

var template = new CassandraTemplate(session);
template.setUsePreparedStatements(false);

您可以在 Spring Data for Apache Cassandra 參考文件中找到更多詳細資訊。

MongoDB 文件解包支援 & 寬鬆的聚合型別檢查

值物件和記錄型別有助於我們建立結構清晰、表達力最強的領域模型。然而,持久化這些精心設計的模型不一定能得到結構良好的資料庫文件。在 Java 或 Kotlin 中看起來不錯的東西,在 MongoDB 原生的 Document 格式中可能會導致不必要的屬性名重複和深度巢狀的結構,該格式將實體嵌入到其父結構中。考慮下面的簡單程式碼片段及其表示形式

class User {
  private String id;
  private Email email;
  // …
}

record Email (String email) {}

{
  "_id" : "9708-ac32-beb0",
  "email" : {
    "email" : "[email protected]"
  },
  // …
}

儘管這行得通,但這顯然不是文件儲存的慣用表示法,而這正是 @Unwrapped 發揮作用的地方。該註解允許您將屬性展平(解包)到其父級中。

 class User {
  private String id;
  @Unwrapped(onEmpty = OnEmpty.USE_NULL)
  private Email email;
  // …
}

@Unwrapped 強制您透過選擇 onEmpty(記錄表示的欄位均不存在)行為來決定如何處理不存在的值。對於喜歡更簡潔註解的開發者,可以隨意使用 @Unwrapped.Nullable 作為替代方案,或者使用 @Unwrapped 作為元註解建立自己的註解。無論哪種方式,生成的文件都會更具吸引力

{
  "_id" : "9708-ac32-beb0",
  "email" : "[email protected]",
  // …
}

Repository 和 MongoTemplate 都可以處理解包的屬性。有關更多資訊,請參閱參考文件。此外,還可以檢視@Unwrapped 的示例

支援 jMolecules

Spring Data repository 抽象一直是專案中的核心概念。它是領域驅動設計(DDD)中提出的一個架構概念的程式設計模型:repository 抽象了聚合的集合。實際上,Spring Framework 本身與 DDD 中起源的其他一些抽象(如服務)保持一致,並提供了註解來在使用者程式碼中表達這些概念。然而,使用者通常不喜歡使用特定於框架的註解和抽象來表達這些概念。

jMolecules 專案專注於提供基於註解和型別的抽象,以便不同的技術架構概念可以整合。它本質上顛倒了關係:使用者程式碼僅依賴於 jMolecules 註解和介面,然後技術整合 —— 在第二步中 —— 從廣泛的 jMolecules 整合庫或框架本身提供。

建模關聯

jMolecules 的領域驅動設計模組中的核心抽象之一是 Association 介面。它被型別化為 AggregateRoot 及其 Identifier,並用於領域模型中以強型別方式表達與聚合的關係。

class Order implements AggregateRoot<Order, OrderIdentifier> {

  OrderIdentifier id;
  Association<Customer, CustomerIdentifier> customer;
}

在此模型中,OrderCustomer 都是聚合,兩者之間的關聯透過 jMolecules Association 型別明確對映。Spring Data 2021.0.0 提供了對 Association 的對映支援。它們被正確地檢測為 Spring Data 關聯,並使用支援例項的識別符號進行轉換。

為了透明地啟用對這些抽象的支援,請將 org.jmolecules.integrations:jmolecules-spring 新增到您的 classpath。Spring Data 的對映基礎設施會檢測到這一點,並自動在我們的物件對映工具的轉換部分註冊必要的轉換器。

Association 例項的支援也提供給 JPA。但是,在這種情況下,Spring Data 不提供實際的轉換,而是透過與 jMolecules 本身提供的 AttributeConverter 實現整合來提供。使用其 ByteBuddy 擴充套件,您可以生成必要的 AttributeConverter 實現和註解配置。

識別符號和聚合例項之間的對映

jMolecules 的 Identifier 介面鼓勵對聚合使用專用的識別符號型別,就像前面示例中使用的 OrderIdentifierCustomerIdentifier 型別一樣。在序列化 Association 時,我們現在需要透過依次呼叫 Association.getId()Identifier.getId() 將例項有效地轉換為 CustomerIdentifier,以獲取實際持久化的值。要物化關聯,我們必須獲取原始持久化值,使用名為 ….of(…) 的暴露的靜態工廠方法建立一個 CustomerIdentifier 例項,最後再次呼叫 Association.of(…)

所有這些轉換步驟都在 jmolecules-integrations 中實現,並由 Spring Data 透明地新增到 Spring ConversionService 中供框架使用。假設 OrderIdentifierUUIDString 表示形式支援,這也意味著 Spring Data 的 DomainClassConverter 能夠自動將完全物化的聚合例項繫結到 Spring MVC 控制器方法

@RestController
class MyController {

  @GetMapping("/orders/{id}")
  HttpEntity<?> getOrders(@PathVariable("id") Order order) { /* … */ }
}

在此示例中,向 /orders/462a692d-… 發起的 GET 請求會首先使用 jMolecules 轉換器自動將 462a692d-… 轉換為 OrderIdentifier,然後使用為 Order 宣告的 repository 來查詢聚合例項。通用機制在 Spring Data 中已經存在了一段時間,而 2021.0.0 版本則增加了對 jMolecules Identifier 實現的必要附加整合。

Spring Data REST DTOs 的聚合引用對映

前面提到的 jMolecules Converter 實現也用於 Spring Data REST 需要獲取聚合識別符號並將其轉換為 URI 的所有地方。該模組還附帶了一個新的 Jackson 反序列化器,透過正確反序列化 URI,可以將 Spring Data REST 管理的聚合例項繫結到 DTO 中。假設您有一個由 Spring Data REST 管理並透過 /orders/… 暴露的 Order,以及一個客戶控制器安排如下:

@BasePathAwareController
class MyCustomController {

  @PostMapping("/orders")
  HttpEntity<?> postOrder(@RequestBody MyDto payload) {
    /* Process submission */
  }
}

@Data
class MyDto {
  List<Order> orders;
}

現在也假設為該請求提交了以下 payload

{
  "orders" : [
    "…/orders/462a692d-…"
  ]
}

儘管 MyDto 是一個普通的資料傳輸物件,但 payload 例項包含由 462a692d-… 標識的聚合例項,作為 orders 連結的一個元素。

獲取 Spring 新聞簡報

訂閱 Spring 新聞簡報,保持聯絡

訂閱

超越

VMware 提供培訓和認證,助您快速提升。

瞭解更多

獲取支援

Tanzu Spring 透過一個簡單的訂閱提供 OpenJDK™、Spring 和 Apache Tomcat® 的支援和二進位制檔案。

瞭解更多

即將舉辦的活動

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

檢視全部