使用 Spring Data JPA 查詢 JSONB 列
一、簡介
Spring Data JPA 提供了一個強大的抽象層用於與關聯式資料庫互動。然而,傳統的關係表可能不適合儲存複雜的半結構化數據,例如產品詳細資訊或使用者偏好。這就是 JSONB 資料類型的用武之地。在本教程中,我們將探索使用 Spring Data JPA 查詢 JSONB 列的各種方法。
2.JSONB 列
JSONB(資料庫的 JavaScript 物件表示法)是一種專門設計用於在 PostgreSQL 等關聯式資料庫中儲存 JSON 資料的資料類型。它允許我們在單一列中使用鍵值對和嵌套物件來表示複雜的資料結構。與 JPA 提供者(例如 Hibernate)結合使用,Spring Data JPA 允許我們將這些 JSONB 欄位對應到實體類別中的屬性。
3. 映射 JSONB 列
我們可以使用@Column
註解和columnDefinition
屬性來明確定義實體類別中的欄位類型:
@Column(columnDefinition = "jsonb")
private String attributes;
這種方法主要與 PostgreSQL 相關,它本身支援jsonb
資料類型。透過將此註解新增至實體類別中的對應屬性中,我們可以向資料庫提供有關所需列類型的提示。 Spring Data JPA 通常會根據資料庫列定義自動偵測jsonb
資料類型,使得此註解在許多情況下是可選的。
4. 設定項目依賴關係和測試數據
我們將建立一個基本的 Spring Boot 項目,其中包含用於測試 JSONB 查詢的必要依賴項和測試資料。
4.1.項目設定
首先,我們需要將必要的依賴項新增到 Maven pom.xml
檔案中:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
4.2.實體類別
接下來,讓我們建立一個名為Product
的 Java 類別來表示我們的實體:
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Column(columnDefinition = "jsonb")
private String attributes;
// Getters and Setters
}
此類定義具有id
、 name
和attributes
欄位的Product
實體。 attributes
欄位是一個String
,它將保存產品詳細資訊的序列化 JSON 資料。我們使用@Column(columnDefinition = “jsonb”)
提示資料庫建立一個 JSONB 類型的attributes
。
定義Product
實體類別後,Hibernate將在應用程式啟動並初始化資料庫模式時產生下列SQL:
CREATE TABLE Product (
id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR(255),
attributes JSONB
);
4.3.準備測試數據
以下是我們在執行測試案例之前用來準備資料庫的 SQL 腳本。我們可以將此腳本保存在.sql
檔案中,並將其放置在專案的src/test/resources
目錄中:
DELETE FROM product;
INSERT INTO product (name, attributes)
VALUES ('Laptop', '{"color": "red", "size": "15 inch"}');
INSERT INTO product (name, attributes)
VALUES ('Phone', '{"color": "blue", "size": "6 inch"}');
INSERT INTO product (name, attributes)
VALUES ('Headphones', '{"brand": "Sony", "details": {"category": "electronics", "model": "WH-1000XM4"}}');
INSERT INTO product (name, attributes)
VALUES ('Laptop', '{"brand": "Dell", "details": {"category": "computers", "model": "XPS 13"}}');
然後,我們在測試類別中使用@Sql
註解並將executionPhase
屬性設為BEFORE_TEST_METHOD
,在每個測試方法執行之前將測試資料插入資料庫:
@Sql(scripts = "/testdata.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
5.透過@Query
使用本機查詢
在本部分中,我們將利用 PostgreSQL JSONB 運算子的本機 SQL 查詢,根據 JSONB 欄位中的值過濾和檢索資料。我們使用@Query
註釋在 Spring Data JPA 儲存庫介面中定義這些本機查詢。
5.1.使用 JSONB 運算子進行查詢
@Query
註解允許我們使用本機 SQL 語法在儲存庫介面中定義自訂查詢。這種方法在使用 JSONB 列時特別有用,因為它使我們能夠直接在查詢中利用 PostgreSQL 的本機 JSONB 運算子。
讓我們使用 JSONB 運算子編寫一個查詢來尋找具有特定顏色值的所有產品:
public interface ProductRepository extends JpaRepository<Product, Long> {
@Query(value = "SELECT * FROM product WHERE attributes ->> ?1 = ?2", nativeQuery = true)
List<Product> findByAttribute(String key, String value);
}
findByAttribute()
方法接受兩個表示 JSON 屬性鍵(例如「顏色」)的參數,並按指示該屬性所需值的值進行篩選。 @Query
註解定義了一個本機 SQL 查詢,該查詢利用->>
運算子來存取attributes
JSONB 欄位中的鍵。
然後,查詢中的佔位符?1
和?2
將替換為執行期間作為方法參數提供的鍵和值的實際名稱。接下來,讓我們建立一個測試案例來驗證findByAttribute()
方法的功能:
List<Product> redProducts = productRepository.findByAttribute("color", "red");
assertEquals(1, redProducts.size());
assertEquals("Laptop", redProducts.get(0).getName());
5.2.使用 JSONB 運算子巢狀查詢
我們利用 PostgreSQL 的->
和->>
運算子來查詢巢狀 JSONB 資料:
-
->
運算子用於存取 JSONB 物件中的特定鍵。 -
->>
運算子用於以文字形式存取與 JSONB 物件中的特定鍵對應的值。
讓我們編寫另一個查詢來處理巢狀鍵:
@Query(value = "SELECT * FROM product WHERE attributes -> ?1 ->> ?2 = ?3", nativeQuery = true)
List<Product> findByNestedAttribute(String key1, String key2, String value);
此查詢允許我們搜尋特定嵌套屬性與給定值相符的實體。例如,如果我們想要尋找「 details.category”
為「 electronics”
的產品,我們可以使用「 details”
、「 category”
和「 `electronics` “
作為參數來呼叫此方法:
List<Product> electronicProducts = productRepository.findByNestedAttribute("details", "category", "electronics");
assertEquals(1, electronicProducts.size());
assertEquals("Headphones", electronicProducts.get(0).getName());
5.3.使用jsonb_extract_path_text
函數進行查詢
我們也可以在本機 SQL 查詢中利用jsonb_extract_path_text
函數從 JSONB 資料中提取特定值。 jsonb_extract_path_text
是一個 PostgreSQL 函數,用於根據給定路徑從 JSONB 欄位中提取特定值。
jsonb_extract_path_text
函數以文字形式傳回提取的值。如果 JSONB 結構中不存在指定的路徑,則函數傳回NULL
。
讓我們來看一個使用jsonb_extract_path_text
的範例:
public interface ProductRepository extends JpaRepository<Product, Long> {
@Query(value = "SELECT * FROM product WHERE jsonb_extract_path_text(attributes, ?1) = ?2", nativeQuery = true)
List<Product> findByJsonPath(String path, String value);
}
findByJsonPath()
方法接受兩個參數。第一個參數是路徑字串,指示要提取的 JSONB 物件中的特定鍵。第二個參數表示提取的屬性的預期值:
List<Product> redProducts = productRepository.findByJsonPath("color", "red");
assertEquals(1, redProducts.size());
assertEquals("Laptop", redProducts.get(0).getName());
5.4.使用jsonb_extract_path_text
函數巢狀查詢
讓我們看看如何使用jsonb_extract_path_text
函數調整查詢以處理巢狀鍵:
@Query(value = "SELECT * FROM product WHERE jsonb_extract_path_text(attributes, ?1, ?2) = ?3", nativeQuery = true)
List<Product> findByNestedJsonPath(String key1, String key2, String value);
我們提供key1
和key2
作為路徑元素來遍歷嵌套的 JSONB 結構:
List<Product> electronicProducts = productService.findByNestedJsonPath("details", "category", "electronics");
assertEquals(1, electronicProducts.size());
assertEquals("Headphones", electronicProducts.get(0).getName());
在此範例中, findByNestedJsonPath(“details”, “category”, “electronics”)
將定位嵌套「 details.category
」值為「 electronics
」的產品。
6. 使用自訂 JPA 規範方法
JPA 規範是封裝用於過濾資料的標準的介面。它們根據特定條件定義需要檢索哪些資料。此介面由Spring Data JPA 提供,允許儲存庫實作接受Specification<T>
實例作為參數的自訂查詢方法。
我們可以建立一個實作Specification<T>
介面的類別。此類別定義了toPredicate()
方法,該方法利用CriteriaBuilder
根據提供的條件(用於過濾的key
和value
)來建構實際的查詢謂詞:
public class ProductSpecification implements Specification<Product> {
private final String key;
private final String value;
public ProductSpecification(String key, String value) {
this.key = key;
this.value = value;
}
@Override
public Predicate toPredicate(Root root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.equal(
cb.function("jsonb_extract_path_text", String.class, root.get("attributes"), cb.literal(key)),
value
);
}
}
toPredicate()
方法定義如何使用作為參數提供的CriteriaBuilder
建構過濾謂詞。在此範例中,我們假設金鑰位於 JSONB 資料的頂層。
要使用此自訂規範, ProductRepository
需要擴充JpaSpecificationExecutor<Product>
:
public interface ProductRepository extends JpaRepository<Product, Long>, JpaSpecificationExecutor<Product> {
}
以下是用於根據 JSONB 列中的屬性過濾產品的自訂規格的基本範例:
ProductSpecification spec = new ProductSpecification("color", "red");
Page<Product> redProducts = productRepository.findAll(spec, Pageable.unpaged());
assertEquals(1, redProducts.getContent().size());
assertEquals("Laptop", redProducts.getContent().get(0).getName());
七、結論
在本文中,我們探討了使用 Spring Data JPA 查詢 JSONB 欄位的各種方法。對於基本的篩選標準,本機 SQL 查詢可能提供簡單的解決方案。然而,在處理複雜的過濾邏輯或可重複使用性需求時,JPA 規範提供了一個令人信服的替代方案。
與往常一樣,範例的原始程式碼可在 GitHub 上取得。