取得領先
VMware 提供培訓和認證,以加速您的進度。
了解更多Spring Data Moore 包含 16 個模組和超過 700 個已完成的工單。它包括整個產品組合的許多改進和新功能,並且主要關注三個主題:反應式、Kotlin 和效能。此版本新增了諸如宣告式反應式交易和 Coroutines/Flow 支援等功能,並且 finder 方法的速度最多提高 60%*。
讓我們先來看看 Moore 的一些反應式功能。
Lovelace 版本 引入了對反應式交易的早期支援,其風格類似於閉包,留下了一些改進空間。以下列表顯示了該風格
Lovelace 中的反應式交易 (使用 MongoDB)
public Mono<Process> doSomething(Long id) {
return template.inTransaction().execute(txTemplate -> {
return txTemplate.findById(id)
.flatMap(it -> start(txTemplate, it))
.flatMap(it -> verify(it))
.flatMap(it -> finish(txTemplate, it));
}).next();
}
在前面的程式碼片段中,必須透過在使用交易感知範本的閉包內顯式呼叫 inTransaction()
來啟動交易,並在結尾呼叫 next()
以將傳回的 Flux
轉換為 Mono
以滿足方法簽章,即使 findById(…)
已經只發出單個元素。
顯然,這不是執行反應式交易最直觀的方式。因此,讓我們看看使用宣告式反應式交易支援的相同流程。與 Spring 的 交易支援一樣,您需要一個元件來為您處理交易。對於反應式交易,目前由 MongoDB 和 R2DBC 模組提供 ReactiveTransactionManager
。以下列表顯示了這樣一個元件
@EnableTransactionManagement
class Config extends AbstractReactiveMongoConfiguration {
// …
@Bean
ReactiveTransactionManager mgr(ReactiveMongoDatabaseFactory f) {
return new ReactiveMongoTransactionManager(f);
}
}
從那裡,您可以使用 @Transactional
註釋方法,並依靠基礎結構來啟動、提交和回滾交易流程,以透過 Reactor Context 處理生命週期。這讓您可以將 Lovelace 中的程式碼轉換為以下列表,從而無需使用範圍範本的閉包和多餘的 Flux
到 Mono
轉換
Moore 中的宣告式反應式交易 (使用 MongoDB)
@Transactional
public Mono<Process> doSomething(Long id) {
return template.findById(id)
.flatMap(it -> start(template, it))
.flatMap(it -> verify(it))
.flatMap(it -> finish(template, it));
}
反應式系列的另一個值得注意的補充可以在社群模組之一中找到,Spring Data Elasticsearch 現在提供基於完全反應式的 Elasticsearch REST 用戶端的反應式範本和儲存庫支援,而該用戶端又基於 Spring 的 WebClient
。
該用戶端透過公開一個熟悉的 API (接近 *Java High-Level REST Client*),為日常搜尋操作提供一流的支援,並在需要時進行必要的削減。範本和儲存庫 API 的組合讓您可以在需要時無縫轉換到反應式,而不會迷失方向。以下列表顯示了如何配置 Elasticsearch 以使用反應式用戶端
反應式 Elasticsearch
class Config extends AbstractReactiveElasticsearchConfiguration {
// …
@Bean
public ReactiveElasticsearchClient reactiveClient() {
return ReactiveRestClients.create(localhost());
}
}
@Autowired
ReactiveElasticsearchTemplate template;
//…
Criteria criteria = new Criteria("topics").contains("spring")
.and("date").greaterThanEqual(today())
Flux<Conference> result = template.find(new CriteriaQuery(criteria), Conference.class);
說到在轉換中迷失方向:Querydsl (← plain HTTP / 無 HTTPS) 提供了一種卓越的方式,可以為多個資料儲存定義型別安全的查詢,並且已經支援非反應式資料存取相當長的時間。為了在反應式情境中支援它,我們新增了一個反應式執行層,讓您可以執行由 Predicate
支援的查詢。當新增到儲存庫介面時,ReactiveQuerydslPredicateExecutor
提供所有入口點,如以下範例所示
反應式 Querydsl
interface SampleRepository extends …, ReactiveQuerydslPredicateExecutor<…> {
// …
}
@Autowired
SampleRepository repository;
// …
Predicate predicate = QCustomer.customer.lastname.eq("Matthews");
Flux<Customer> result = repository.findAll(predicate);
沿著 Moore 中增強的反應式支援的路線,我們繼續了我們已經從 Lovelace 版本開始的 Kotlin 故事。特別是,我們為 Kotlin 協同程式和 Flows 提供了幾個擴充功能,例如提供諸如 awaitSingle()
和 asFlow()
等方法。以下方法使用 awaitSingle()
方法
Kotlin 協同程式支援
val result = runBlocking {
operations.query<Person>()
.matching(query(where("lastname").isEqualTo("Matthews")))
.awaitSingle()
}
另一個使用 Kotlin 語言特性的重大增強功能是由社群貢獻的,它為 Spring Data MongoDB criteria API 新增了一個型別安全的查詢 DSL。這讓您可以將諸如 query(where("lastname").isEqualTo("Matthews"))
等程式碼轉換為以下表示法
Kotlin 型別安全查詢
val people = operations.query<Person>()
.matching(query(Person::lastname isEqualTo "Matthews"))
.all()
在製作所有這些新功能的同時,我們還花了一些時間來調查目前實作的潛在瓶頸,並找到了一些可以改進的領域。這包括擺脫 Optional
,捕獲許多地方的 lambda 和串流執行,新增快取,以及避免不必要的查閱操作。最後,基準測試顯示,JPA 單一屬性 finder 方法 (例如 findByTitle(…)
) 的輸送量提高了近 60%。
這很棒,而且值得花費的時間!但是,我想明確說明的是,所有基準測試都使用乾淨的房間情境,避免任何形式的額外負荷。如果您將它們移到更真實的情境中 (例如,透過將記憶體中的 H2 資料庫替換為實際可投入生產的資料庫),結果看起來會大不相同,因為效能瓶頸會轉移到網路互動、查詢執行和結果傳輸。改進仍然可見,但通常會降至個位數百分比。基準測試可以在這個 GitHub 儲存庫 中找到。
我們還改進了現有的掛鉤,透過從目前基於 ApplicationEvent
的方法轉移到更直接的互動模型,來攔截實體在持續性操作期間的生命週期。EntityCallback
API 引入了對不可變型別的更好支援,提供了執行時間保證,並且還無縫整合到反應式流程中。當然,我們仍然支援並發布 ApplicationEvents
,但我們強烈建議在應該對已處理的實體進行變更時切換到 EntityCallbacks
。
在以下範例中,BeforeConvertCallback
透過使用將 id
指派給實體副本的 wither
方法來修改給定的不可變實體,然後傳回該副本,並在下一步中將其轉換為儲存區特定的表示法
EntityCallback API
@Bean
BeforeConvertCallback<Person> beforeConvert() {
return (entity, collection) -> {
return entity.withId(…);
}
}
與 ApplicationEvents
(可以使用 AsyncTaskExecutor
進行配置,幾乎可以隨時執行該動作) 不同,EntityCallback
API 保證在觸發實際事件之前立即被呼叫。即使在反應式串流中也是如此。以下列表顯示了它的運作方式
反應式 EntityCallback API
@Bean
ReactiveBeforeConvertCallback<Person> beforeConvert() {
return (entity, collection) -> {
return Mono.just(entity.withId(…));
}
}
說到串流,Spring Data Redis 現在支援 Redis Streams,它與反應式串流幾乎沒有關係,但它是一種新的 Redis 僅附加資料結構,用於模擬日誌,其中每個條目都包含一個 id (通常是時間戳記加上一個序列號) 和多個鍵/值對。除了常見的嫌疑人 (例如新增到日誌和從日誌讀取) 之外,Spring Data Redis 還提供了容器,允許無限偵聽和處理新增到日誌的條目。它的工作方式類似於 tail -f
,但適用於 Redis Stream。以下範例顯示了 Redis 串流接聽器
Redis Streams 接聽器
@Autowired
RedisConnectionFactory factory;
StreamListener<String, MapRecord<…>> listener =
(msg) -> {
// … msg.getId()
// … msg.getStream()
// … msg.getValue()
};
StreamMessageListenerContainer container = StreamMessageListenerContainer.create(factory));
container.receive(StreamOffset.fromStart("my-stream"), listener);
前面的範例中的 StreamMessageListenerContainer
讀取 my-stream
的所有現有條目,並收到有關新新增條目的通知。對於收到的每條訊息,都會呼叫 StreamListener
。單個容器可以接收來自多個串流的訊息。
當然,串流式結構最適合由反應式基礎設施使用,如下列範例所示
StreamReceiver receiver = // …
receiver.receive(StreamOffset.fromStart("my-stream"))
.doOnNext(msg -> {
// …
})
.subscribe();
在 JPA 方面,現在有一個小改進,讓您可以為預存程序提供多個 OUT
參數,這些參數會以 Map
的形式傳回。 以下範例展示了如何操作
具有 JPA 預存程序的 Out 參數
@NamedStoredProcedureQuery(name = "User.s1p", procedureName = "s1p",
parameters = {
@StoredProcedureParameter(mode = IN, name = "in_1", type = …),
@StoredProcedureParameter(mode = OUT, name = "out_1", type = …),
@StoredProcedureParameter(mode = OUT, name = "out_2", type = …)})
@Table(name = "SD_User")
class User { … }
interface UserRepository extends JpaRepository<…> {
@Procedure(name = "User.s1p")
Map<String, Integer> callS1P(@Param("in_1") Integer arg);
}
JPA 的 @StoredProcedureParameter
註解中宣告的所有 out 參數最終都可以在儲存庫查詢方法傳回的 Map
中使用。
對於 MongoDB,複雜的資料處理是使用聚合來完成的,Spring Data 為此提供了一個特定的(流暢)API,其中包含操作和表達式的抽象。 然而,Stackoverflow 告訴我們,人們傾向於在命令列上建立他們的聚合,然後再將它們轉換為 Java 程式碼。 事實證明,這種轉換是一個主要的痛點。
因此,我們藉此機會引入 @Aggregation
,作為在儲存庫方法中執行聚合的直接方式。 以下範例展示了如何操作
宣告式 MongoDB 聚合
interface OrderRepository extends CrudRepository<Order, Long> {
@Aggregation("{ $group : { _id : '$cust_id', total : { $sum : '$amount' }}}")
List<TotalByCustomer> totalByCustomer(Sort sort);
@Aggregation(pipeline = {
"{ $match : { customerId : ?0 }}",
"{ $count : total }"
})
Long totalOrdersForCustomer(String customerId);
}
與其親戚 @Query
註解一樣,@Aggregation
支援參數替換,如果查詢方法引數提供排序,則會將排序新增至聚合,如前面的範例所示。 我們甚至更進一步,提取用於傳回簡單類型的方法的單一屬性文件值,例如前面範例中的 *totalOrdersForCustomer* 方法。 在這種情況下,$count
階段傳回一個類似 {"total" : 101 }
的文件,通常需要對應到 plain org.bson.Document
或相應的網域類型。 但是,由於該方法宣告 Long
作為其傳回類型,因此我們會檢查結果文件並從那裡提取/轉換該值,從而消除了對專用類型的需求。
為了現在總結一下,我想提及其他模組的一些其他功能。 如果您對所有這些功能都感興趣,請查看我們的版本 wiki 或參閱個別模組參考文件中「新功能」部分。 因此,事不宜遲,以下是此版本提供的更多改進
Gemfire/Apache Geode:改進的 SSL 支援和動態埠設定
JDBC:唯讀屬性、SQL 產生和可嵌入載入選項
REST:利用 HATEOAS 1.0 以及其中的所有酷炫功能!
MongoDB:反應式 GridFS、宣告式排序規則支援和 JSON 結構描述產生器
neo4j:空間類型和 exists 投影
Apache Cassandra:範圍查詢、樂觀鎖定和稽核支援
Redis:叢集快取和非阻塞連線方法
Elasticsearch:高階 REST 用戶端支援和非 Jackson 基於實體映射
如果您想了解更多資訊,請這裡是在德克薩斯州奧斯丁舉行的 SpringOne 2019 上錄製的 30 分鐘簡報。