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 儲存庫通常在應用程式啟動時引導,因此它們自然會貢獻啟動時間。Pascal 版本引入了與 Spring Framework 對捕獲 啟動事件的支援的整合,該支援自 5.3 版本起可用。透過啟用 JFR 錄製,您可以收集和分析以下儲存庫啟動事件:

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

  • spring.data.repository.scanning:儲存庫介面掃描

對於每個儲存庫

  • spring.data.repository.init:儲存庫初始化

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

  • spring.data.repository.composition:儲存庫組合的組裝

  • spring.data.repository.target:儲存庫目標建立

  • spring.data.repository.proxy:儲存庫代理建立

  • spring.data.repository.postprocessors:儲存庫代理後處理

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

啟用 Type- 和 Refactoring-Safe 的 KPropertyKPropertyPath 用於屬性路徑渲染

Spring Data Kotlin 整合是“語法糖”增強我們特定語言擴充套件的強大驅動力。Kotlin 允許將單個屬性引用為屬性引用(data class Book(val title: String), Book::title)。它們是 refactoring- 和 compile-safe 的,因為 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 會選擇適當的繫結標記策略和方言。

為 Repositories 和 CassandraTemplate 啟用 Cassandra 預處理語句

Spring Data for Apache Cassandra 儘可能地適應 Cassandra 的特定功能。自 2.0 版本的主要重寫以來,我們為 CqlTemplate 級別的預處理語句快取引入了 CachedPreparedStatementCreator,它允許使用純 CQL 的預處理語句。

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

提供預處理語句功能的變化會帶來一些查詢方面的改變:

  1. 使用 StatementBuilder 時,引數按索引繫結。StatementBuilder 在構建與實體相關的操作的 CQL 查詢時,在所有情況下都被使用。

  2. 當按索引繫結引數時,檢查 SimpleStatement 會在其中文 CQL 中顯示引數繫結標記。CqlTemplate 的 CQL 日誌也受此更改影響:記錄的 CQL 現在包含 ? 而不是字面值。

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

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

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

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

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

MongoDB 的 Document Unwrapping 支援和 Relaxed Aggregation Type Checks

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

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]",
  // …
}

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

支援 jMolecules

Spring Data 儲存庫抽象一直是專案中的核心概念。它是領域驅動設計 (DDD) 中提出的架構概念的程式設計模型:儲存庫抽象了一個聚合集合。事實上,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 新增到您的類路徑中。Spring Data 的對映基礎結構會檢測到這一點,並自動在我們的物件對映工具的轉換部分註冊必要的轉換器。

JPA 也提供了對 Association 例項的支援。但是,在這種情況下,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 中供框架使用。假設 OrderIdentifierUUID 的字串表示支援,這也意味著 Spring Data 的 DomainClassConverter 能夠自動將完全具體化的聚合例項繫結到 Spring MVC 控制器方法。

@RestController
class MyController {

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

在此示例中,對 /orders/462a692d-… 的 GET 請求會自動使用 jMolecules 轉換器將 462a692d-… 轉換為 OrderIdentifier,然後使用為 Order 宣告的儲存庫查詢聚合例項。雖然 通用機制在 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 管理的 Order,並透過 /orders/… 公開,還有一個如下所示的客戶控制器安排:

@BasePathAwareController
class MyCustomController {

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

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

現在,假設提交的請求負載如下:

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

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

獲取 Spring 新聞通訊

透過 Spring 新聞通訊保持聯絡

訂閱

領先一步

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

瞭解更多

獲得支援

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

瞭解更多

即將舉行的活動

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

檢視所有