持久化實體
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
子句以及限制/偏移分頁。限制/偏移功能對應用程式是透明的,無論底層資料庫為何。此功能由 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 運算子
-
Criteria
and(String column)
:將具有指定property
的鏈結Criteria
新增至目前的Criteria
,並回傳新建立的Criteria
。 -
Criteria
or(String column)
:將具有指定property
的鏈結Criteria
新增至目前的Criteria
,並回傳新建立的Criteria
。 -
Criteria
greaterThan(Object o)
:使用>
運算子建立條件。 -
Criteria
greaterThanOrEquals(Object o)
:使用>=
運算子建立條件。 -
Criteria
in(Object… o)
:使用IN
運算子為可變參數引數建立條件。 -
Criteria
in(Collection<?> collection)
:使用IN
運算子,使用集合建立條件。 -
Criteria
is(Object o)
:使用欄位比對 (property = value
) 建立條件。 -
Criteria
isNull()
:使用IS NULL
運算子建立條件。 -
Criteria
isNotNull()
:使用IS NOT NULL
運算子建立條件。 -
Criteria
lessThan(Object o)
:使用<
運算子建立條件。 -
Criteria
lessThanOrEquals(Object o)
:使用⇐
運算子建立條件。 -
Criteria
like(Object o)
:使用LIKE
運算子建立條件,而不進行逸出字元處理。 -
Criteria
not(Object o)
:使用!=
運算子建立條件。 -
Criteria
notIn(Object… o)
:使用NOT IN
運算子為可變參數引數建立條件。 -
Criteria
notIn(Collection<?> collection)
:使用NOT IN
運算子,使用集合建立條件。您可以將Criteria
與SELECT
、UPDATE
和DELETE
查詢搭配使用。
插入資料
您可以使用 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 | 套用刪除操作並回傳受影響的列數。 |
使用 Repository,可以使用 ReactiveCrudRepository.save(…)
方法執行實體的儲存。如果實體是新的,則會導致實體的插入。
如果實體不是新的,則會更新它。請注意,實例是否為新的實例是實例狀態的一部分。
這種方法有一些明顯的缺點。如果只有少數參考實體實際已變更,則刪除和插入是浪費的。雖然這個過程可以而且可能會改進,但 Spring Data R2DBC 可以提供的功能存在某些限制。它不知道聚合的先前狀態。因此,任何更新過程都必須始終取得在資料庫中找到的任何內容,並確保將其轉換為傳遞至儲存方法的實體的狀態。 |
ID 產生
Spring Data 使用識別碼屬性來識別實體。實體的 ID 必須使用 Spring Data 的 @Id
註釋進行註釋。
當您的資料庫具有 ID 欄位的自動遞增欄位時,產生的值會在將其插入資料庫後設定在實體中。
當實體是新的且識別碼值預設為其初始值時,Spring Data 不會嘗試插入識別碼欄位的值。對於基本類型,它是 0
,如果識別碼屬性使用數值包裝類型(例如 Long
),則它是 null
。
實體狀態偵測 詳細說明了偵測實體是新的還是預期存在於資料庫中的策略。
一個重要的限制是,在儲存實體之後,實體不得再是新的。請注意,實體是否為新的實例是實體狀態的一部分。使用自動遞增欄位,這會自動發生,因為 ID 會由 Spring Data 設定為來自 ID 欄位的值。
樂觀鎖定
Spring Data 透過在聚合根上使用以 @Version
註釋的數值屬性來支援樂觀鎖定。每當 Spring Data 儲存具有此版本屬性的聚合時,都會發生兩件事
-
聚合根的更新陳述式將包含一個 where 子句,檢查資料庫中儲存的版本是否實際未變更。
-
如果不是這種情況,則會擲回
OptimisticLockingFailureException
。
此外,版本屬性會在實體和資料庫中都增加,因此並行動作將注意到變更,並在適用的情況下擲回 OptimisticLockingFailureException
,如上所述。
此過程也適用於插入新的聚合,其中 null
或 0
版本表示新實例,而之後增加的實例會將實例標記為不再是新的,這使得它在例如使用 UUID 時在物件建構期間產生 id 的情況下相當順利地運作。
在刪除期間,版本檢查也適用,但不會增加版本。
@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 。 |