持久化實體
R2dbcEntityTemplate 是 Spring Data R2DBC 的核心入口。它提供直接面向實體的方法,以及針對典型臨時用例(如查詢、插入、更新和刪除資料)的更簡潔、流暢的介面。
入口點(insert()、select()、update() 等)遵循基於要執行的操作的自然命名方案。從入口點開始,API 旨在僅提供上下文相關的方法,這些方法最終會產生一個建立並執行 SQL 語句的終止方法。Spring Data R2DBC 使用 R2dbcDialect 抽象來確定繫結標記、分頁支援以及底層驅動程式原生支援的資料型別。
所有終端方法始終返回一個表示所需操作的 Publisher 型別。實際語句在訂閱時傳送到資料庫。 |
插入和更新實體的方法
R2dbcEntityTemplate 上有幾個方便的方法用於儲存和插入物件。為了更精細地控制轉換過程,您可以使用 R2dbcCustomConversions 註冊 Spring 轉換器——例如 Converter<Person, OutboundRow> 和 Converter<Row, Person>。
使用儲存操作的簡單情況是儲存一個 POJO。在這種情況下,表名由類的名稱(非完全限定)決定。您也可以使用特定的集合名稱呼叫儲存操作。您可以使用對映元資料覆蓋儲存物件的集合。
在插入或儲存時,如果未設定 Id 屬性,則假定其值將由資料庫自動生成。因此,對於自動生成,類中 Id 屬性或欄位的型別必須是 Long 或 Integer。
以下示例展示瞭如何插入一行並檢索其內容
R2dbcEntityTemplate 插入和檢索實體Person person = new Person("John", "Doe");
Mono<Person> saved = template.insert(person);
Mono<Person> loaded = template.selectOne(query(where("firstname").is("John")),
Person.class);
以下插入和更新操作可用
還提供了一組類似的插入操作
-
Mono<T>insert(T objectToSave):將物件插入到預設表中。 -
Mono<T>update(T objectToSave):將物件插入到預設表中。
可以使用流暢的 API 自定義表名。
選擇資料
R2dbcEntityTemplate 上的 select(…) 和 selectOne(…) 方法用於從表中選擇資料。這兩個方法都接受一個 Query 物件,該物件定義欄位投影、WHERE 子句、ORDER BY 子句和 limit/offset 分頁。無論底層資料庫如何,limit/offset 功能對應用程式都是透明的。此功能由 R2dbcDialect 抽象支援,以適應不同 SQL 方言之間的差異。
R2dbcEntityTemplate 選擇實體Flux<Person> loaded = template.select(query(where("firstname").is("John")),
Person.class);
流暢 API
本節解釋流暢 API 的使用。考慮以下簡單查詢
Flux<Person> people = template.select(Person.class) (1)
.all(); (2)
| 1 | 使用 Person 和 select(…) 方法將表格結果對映到 Person 結果物件。 |
| 2 | 獲取 all() 行將返回一個 Flux<Person>,而不限制結果。 |
以下示例聲明瞭一個更復雜的查詢,透過名稱指定表名、WHERE 條件和 ORDER BY 子句
Mono<Person> first = template.select(Person.class) (1)
.from("other_person")
.matching(query(where("firstname").is("John") (2)
.and("lastname").in("Doe", "White"))
.sort(by(desc("id")))) (3)
.one(); (4)
| 1 | 按名稱從表中選擇將使用給定域型別返回行結果。 |
| 2 | 發出的查詢聲明瞭 firstname 和 lastname 列上的 WHERE 條件以過濾結果。 |
| 3 | 結果可以按單獨的列名排序,從而形成 ORDER BY 子句。 |
| 4 | 選擇一個結果只獲取一行。這種消費行的方式要求查詢返回一個精確的單一結果。如果查詢返回多個結果,Mono 將發出 IncorrectResultSizeDataAccessException。 |
您可以透過 select(Class<?>) 提供目標型別,直接將 投影 應用於結果。 |
您可以透過以下終止方法在檢索單個實體和檢索多個實體之間切換
-
first():只消費第一行,返回一個Mono。如果查詢沒有返回結果,返回的Mono將在不發出物件的情況下完成。 -
one():精確消費一行,返回一個Mono。如果查詢沒有返回結果,返回的Mono將在不發出物件的情況下完成。如果查詢返回多於一行,Mono將異常完成,發出IncorrectResultSizeDataAccessException。 -
all():消費所有返回的行,返回一個Flux。 -
count():應用計數投影,返回Mono<Long>。 -
exists():透過返回Mono<Boolean>來判斷查詢是否返回任何行。
您可以使用 select() 入口點來表達您的 SELECT 查詢。生成的 SELECT 查詢支援常用的子句(WHERE 和 ORDER BY)並支援分頁。流暢的 API 風格允許您將多個方法連結在一起,同時擁有易於理解的程式碼。為了提高可讀性,您可以使用靜態匯入,這樣可以避免使用 'new' 關鍵字建立 Criteria 例項。
Criteria 類的方法
Criteria 類提供以下方法,所有這些方法都對應於 SQL 運算子
-
Criteriaand(String column):將帶有指定property的鏈式Criteria新增到當前Criteria並返回新建立的Criteria。 -
Criteriaor(String column):將帶有指定property的鏈式Criteria新增到當前Criteria並返回新建立的Criteria。 -
CriteriagreaterThan(Object o):使用>運算子建立條件。 -
CriteriagreaterThanOrEquals(Object o):使用>=運算子建立條件。 -
Criteriain(Object… o):為可變引數建立使用IN運算子的條件。 -
Criteriain(Collection<?> collection):使用集合建立使用IN運算子的條件。 -
Criteriais(Object o):使用列匹配(property = value)建立條件。 -
CriteriaisNull():使用IS NULL運算子建立條件。 -
CriteriaisNotNull():使用IS NOT NULL運算子建立條件。 -
CriterialessThan(Object o):使用<運算子建立條件。 -
CriterialessThanOrEquals(Object o):使用⇐運算子建立條件。 -
Criterialike(Object o):使用LIKE運算子建立條件,不進行跳脫字元處理。 -
Criterianot(Object o):使用!=運算子建立條件。 -
CriterianotIn(Object… o):為可變引數建立使用NOT IN運算子的條件。 -
CriterianotIn(Collection<?> collection):使用集合建立使用NOT IN運算子的條件。您可以在SELECT、UPDATE和DELETE查詢中使用Criteria。
插入資料
您可以使用 insert() 入口點插入資料。
考慮以下簡單的型別化插入操作
Mono<Person> insert = template.insert(Person.class) (1)
.using(new Person("John", "Doe")); (2)
| 1 | 將 Person 與 into(…) 方法一起使用,根據對映元資料設定 INTO 表。它還準備插入語句以接受 Person 物件進行插入。 |
| 2 | 提供一個標量 Person 物件。或者,您可以提供一個 Publisher 來執行 INSERT 語句流。此方法提取所有非 null 值並插入它們。 |
更新資料
您可以使用 update() 入口點更新行。更新資料從指定要更新的表開始,接受指定賦值的 Update。它還接受 Query 來建立 WHERE 子句。
考慮以下簡單的型別化更新操作
Person modified = …
Mono<Long> update = template.update(Person.class) (1)
.inTable("other_table") (2)
.matching(query(where("firstname").is("John"))) (3)
.apply(update("age", 42)); (4)
| 1 | 更新 Person 物件並根據對映元資料應用對映。 |
| 2 | 透過呼叫 inTable(…) 方法設定不同的表名。 |
| 3 | 指定一個轉換為 WHERE 子句的查詢。 |
| 4 | 應用 Update 物件。在此例中將 age 設定為 42 並返回受影響的行數。 |
刪除資料
您可以使用 delete() 入口點刪除行。刪除資料從指定要刪除的表開始,並且可選地接受 Criteria 來建立 WHERE 子句。
考慮以下簡單的插入操作
Mono<Long> delete = template.delete(Person.class) (1)
.from("other_table") (2)
.matching(query(where("firstname").is("John"))) (3)
.all(); (4)
| 1 | 刪除 Person 物件並根據對映元資料應用對映。 |
| 2 | 透過呼叫 from(…) 方法設定不同的表名。 |
| 3 | 指定一個轉換為 WHERE 子句的查詢。 |
| 4 | 應用刪除操作並返回受影響的行數。 |
使用 Repositories,儲存實體可以透過 ReactiveCrudRepository.save(…) 方法執行。如果實體是新的,這將導致對實體進行插入。
如果實體不是新的,它將被更新。請注意,例項是否是新的是例項狀態的一部分。
| 這種方法有一些明顯的缺點。如果只有少數引用的實體實際發生了更改,那麼刪除和插入是浪費的。雖然這個過程可以並且可能會得到改進,但 Spring Data R2DBC 能提供的功能是有限的。它不知道聚合的先前狀態。因此,任何更新過程都必須始終獲取資料庫中找到的任何內容,並確保將其轉換為傳遞給 save 方法的實體狀態。 |
ID 生成
Spring Data 使用識別符號屬性來識別實體。即,查詢它們或建立針對特定行的語句。實體的 ID 必須使用 Spring Data 的 @Id 註解進行標註。
當您的資料庫為 ID 列具有自增列時,在將實體插入資料庫後,生成的值將設定在實體中。
如果您還使用 @Sequence 註解識別符號屬性,如果底層 Dialect 支援序列,將使用資料庫序列獲取 ID 的值。
否則,當實體是新實體且識別符號值預設為其初始值時,Spring Data 不會嘗試插入識別符號列的值。即,對於原始型別為 0,如果識別符號屬性使用數字包裝型別(如 Long),則為 null。
實體狀態檢測 詳細解釋了檢測實體是新實體還是預期存在於資料庫中的策略。
一個重要的限制是,儲存實體後,實體不能再是新的。請注意,實體是否是新的是實體狀態的一部分。對於自增列,這會自動發生,因為 ID 會被 Spring Data 用 ID 列的值設定。
樂觀鎖
Spring Data 透過聚合根上用 @Version 註解的數字屬性支援樂觀鎖。每當 Spring Data 儲存具有此類版本屬性的聚合時,會發生兩件事
-
聚合根的更新語句將包含一個 where 子句,檢查資料庫中儲存的版本是否確實未更改。
-
如果不是這種情況,將丟擲
OptimisticLockingFailureException。
此外,版本屬性在實體和資料庫中都會增加,因此併發操作會注意到更改,並在適用時(如上所述)丟擲 OptimisticLockingFailureException。
此過程也適用於插入新的聚合,其中 null 或 0 版本表示新例項,之後增加的例項將例項標記為不再是新的,這使得它在 ID 在物件構建期間生成(例如使用 UUID)的情況下工作得很好。
在刪除過程中,版本檢查也適用,但版本不會增加。
@Table
class Person {
@Id Long id;
String firstname;
String lastname;
@Version Long version;
}
R2dbcEntityTemplate template = …;
Mono<Person> daenerys = template.insert(new Person("Daenerys")); (1)
Person other = template.select(Person.class)
.matching(query(where("id").is(daenerys.getId())))
.first().block(); (2)
daenerys.setLastname("Targaryen");
template.update(daenerys); (3)
template.update(other).subscribe(); // emits OptimisticLockingFailureException (4)
| 1 | 初始插入行。version 設定為 0。 |
| 2 | 載入剛剛插入的行。version 仍然是 0。 |
| 3 | 使用 version = 0 更新行。設定 lastname 並將 version 提升到 1。 |
| 4 | 嘗試更新先前載入的仍然具有 version = 0 的行。操作因 OptimisticLockingFailureException 而失敗,因為當前 version 是 1。 |