1. 概述
某些項目可能需要 JSON 對象持久化到關係型數據庫。
在本教程中,我們將學習如何將 JSON 對象持久化到關係型數據庫。
有幾個框架提供此功能,但我們將重點關注使用 Hibernate、Jackson 和 Hypersistence Utils 庫的幾個簡單、通用的選項。
2. 依賴項
我們將使用 基本 Hibernate Core 依賴項 用於本教程:
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-core</artifactId>
<version>5.4.24.Final</version>
</dependency>我們還將使用 Jackson 作為我們的 JSON 庫:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.13.3</version>
</dependency>請注意,這些技術並不限於這兩個庫。我們可以替換我們最喜歡的JPA提供者和JSON庫。
3. Serialize 和 Deserialize 方法
將 JSON 對象持久化到關係型數據庫的最基本方法是將對象轉換為 String 後再進行持久化。然後,在從數據庫檢索時,我們將它轉換回對象。
我們可以通過多種方式實現此操作。
首先我們將探討使用 自定義 Serialize 和 Deserialize 方法。
我們將從一個簡單的 Customer 實體開始,該實體存儲客户的姓名(名字和姓氏)以及關於客户的一些屬性。
一個標準的 JSON 對象將這些屬性表示為 HashMap,因此我們將在這裏使用它:
@Entity
@Table(name = "Customers")
public class Customer {
@Id
private int id;
private String firstName;
private String lastName;
private String customerAttributeJSON;
@Convert(converter = HashMapConverter.class)
private Map<String, Object> customerAttributes;
}與其將屬性保存在單獨的表中,我們將會將它們存儲為 JSON 字符串,存儲在 Customers 表中的一列中。 這有助於減少模式複雜性並提高查詢性能。
首先,我們將創建一個序列化方法,該方法將接受我們的 customerAttributes 並將其轉換為 JSON 字符串:
public void serializeCustomerAttributes() throws JsonProcessingException {
this.customerAttributeJSON = objectMapper.writeValueAsString(customerAttributes);
}我們可以手動調用此方法在持久化之前,或者從 setCustomerAttributes 方法中調用它,以便每次更新屬性時,JSON 字符串也會同時更新。
接下來,我們將創建一個方法來將 JSON 字符串反序列化為 HashMap 對象,當我們從數據庫中檢索 Customer 對象時。我們可以通過將 TypeReference 參數傳遞給 readValue() 來實現:
public void deserializeCustomerAttributes() throws IOException {
this.customerAttributes = objectMapper.readValue(customerAttributeJSON,
new TypeReference<Map<String, Object>>() {});
}再次強調,我們可以從幾個不同的地方調用這個方法,但在本示例中,我們將手動調用它。
因此,保存和檢索我們的 Customer 對象將類似於以下內容:
@Test
public void whenStoringAJsonColumn_thenDeserializedVersionMatches() {
Customer customer = new Customer();
customer.setFirstName("first name");
customer.setLastName("last name");
Map<String, Object> attributes = new HashMap<>();
attributes.put("address", "123 Main Street");
attributes.put("zipcode", 12345);
customer.setCustomerAttributes(attributes);
customer.serializeCustomerAttributes();
String serialized = customer.getCustomerAttributeJSON();
customer.setCustomerAttributeJSON(serialized);
customer.deserializeCustomerAttributes();
assertEquals(attributes, customer.getCustomerAttributes());
}4. 屬性轉換器
如果使用 JPA 2.1 或更高版本,我們可以利用 屬性轉換器 來簡化這個過程。
首先,我們將創建一個 屬性轉換器 的實現。我們將重用我們之前編寫的代碼:
public class HashMapConverter implements AttributeConverter<Map<String, Object>, String> {
@Override
public String convertToDatabaseColumn(Map<String, Object> customerInfo) {
String customerInfoJson = null;
try {
customerInfoJson = objectMapper.writeValueAsString(customerInfo);
} catch (final JsonProcessingException e) {
logger.error("JSON writing error", e);
}
return customerInfoJson;
}
@Override
public Map<String, Object> convertToEntityAttribute(String customerInfoJSON) {
Map<String, Object> customerInfo = null;
try {
customerInfo = objectMapper.readValue(customerInfoJSON,
new TypeReference<HashMap<String, Object>>() {});
} catch (final IOException e) {
logger.error("JSON reading error", e);
}
return customerInfo;
}
}接下來,我們告訴 Hibernate 使用我們新的 AttributeConverter 對 customerAttributes 字段進行轉換,就完成了:
@Convert(converter = HashMapConverter.class)
private Map<String, Object> customerAttributes;採用這種方法,我們不再需要手動調用序列化和反序列化方法,因為 Hibernate 會替我們處理這些操作。我們可以正常地保存和檢索 Customer 對象。
5. 使用 Hypersistence Utils 庫
Hypersistence 庫允許輕鬆地將 JSON 映射到數據庫系統,如 H2、MySQL、Oracle Database 等。這通過在實體類中定義和使用自定義類型來實現。
要使用該庫,我們需要將 hypersistence-utils-hibernate-55 依賴項添加到 pom.xml 中:
<dependency>
<groupId>io.hypersistence</groupId>
<artifactId>hypersistence-utils-hibernate-55</artifactId>
<version>3.8.1</version>
</dependency>
該版本與 Hibernate 5.5 和 5.6 兼容。對於 Hibernate 6.0 和 6.1,我們需要使用 hpersisitence-utils-hibernate-60 依賴項。對於 Hibernate 6.2 及更高版本,需要分別使用 hypersistence-utils-hibernate-62 依賴項和 hypersistence-utils-hibernate-63 依賴項。
接下來,我們創建一個名為 Warehouse 的實體類:
@Entity
@TypeDef(name = "json", typeClass = JsonType.class)
public class Warehouse {
@Id
@GeneratedValue
private Long id;
private String name;
private String location;
@Type(type = "json")
@Column(columnDefinition = "json")
private Map<String, String> attributes = new HashMap<>();
}在上述代碼中,我們使用 @TypeDef 註解來定義類型名稱“json”,該類型映射到 Hypersistence Utils 庫中的 JsonType.class。 這種映射允許我們在數據庫中序列化和反序列化 JSON 數據。 我們在需要應用此類型時引用它。
或者,我們也可以在 Hibernate 5 中指定類型的完全限定名:
@Type(type = "io.hypersistence.utils.hibernate.type.json.JsonType")
@Column(columnDefinition = "json")
private Map<String, String> attributes = new HashMap<>();在這種情況下,@TypeDef註解是不需要的,因為自定義類型已經明確定義。
但是,從 Hibernate 6 開始,@TypeDef已被棄用。我們可以直接應用自定義類型,通過在 @Type註解中指定它來實現。
@Type(JsonType.class)
private Map<String, String> attributes = new HashMap<>();JsonType.class負責將JSON映射到數據庫。
6. <em @JdbcTypeCode</em> 註解
Hibernate 6.0 及更高版本提供了一個名為 <em @JdbcTypeCode</em> 的註解,用於指定列映射的 JDBC 類型。
我們可以通過將 <em Map</em> 對象序列化或反序列化為 JSON,並使用<em SQLTypes.JSON作為<em @JdbcTypeCode` 註解的參數,將 SQL 類型設置為 JSON。 這種方法提供了在無需額外映射配置的情況下存儲結構化數據的靈活性。
讓我們定義一個名為 <em Store</em> 的實體類,並將 `<em attributes 對象映射為 JSON:
@Entity
public class Store {
@Id
@GeneratedValue
private Long id;
private String name;
private String location;
@JdbcTypeCode(SqlTypes.JSON)
private Map<String, String> attributes = new HashMap<>();
}在上述代碼中,我們通過顯式使用 @JdbcTypeCode 註解並將其 SQL 類型設置為 JSON,來定義 attributes 對象的 SQL 類型。 這樣,attributes 對象就可以在無需外部庫或自定義轉換的情況下,被序列化和反序列化為 JSON。
7. 將 JSON 文檔映射到可嵌入類
此外,從 Hibernate 6.0 開始,我們可以將 JSON 文檔映射到可嵌入類。 帶有 <em @Embeddable 註解的類可以作為值的類型包含在另一個類中,並作為包含類的值持久化到數據庫中。
Furthermore, <em @Embedded 註解用於包含類,以指示它包含一個嵌入對象。 要將嵌入類映射到 JSON 對象,必須使用 <em @JdbcTypeCode 註解並將其參數設置為 SQLTypes.JSON。
Notably, 此功能僅適用於 Oracle DB 和 PostgreSQL,因為其他數據庫尚未受支持。
讓我們定義一個可嵌入類:
@Embeddable
public class Specification implements Serializable {
private int ram;
private int internalMemory;
private String processor;
// standard constructor, getters and setters
}接下來,讓我們將該類嵌入到我們的實體類中,並將其映射為 JSON:
@Entity
public class Phone {
@Id
@GeneratedValue
private Long id;
private String name;
@Embedded
@JdbcTypeCode(SqlTypes.JSON)
private Specification specification;
}在上述代碼中,我們使用 @Embedded 註解對 Specification 類型進行標註,以指示它是一個可嵌入的類,並顯式指定 JDBC 類型代碼將列映射到 JSON 文檔。
8. 結論
在本文中,我們看到了使用 Hibernate 和 Jackson 保持 JSON 對象持久化的幾個例子。在第一個例子中,我們使用了簡單且兼容的方法,通過自定義序列化和反序列化方法來實現。然後,在第二個例子中,我們引入了 AttributeConverters 作為一種強大的方法,簡化了我們的代碼。此外,我們還查看了 Hypersistence Utils 庫中預定義的類型,以簡化 JSON 映射到數據庫列的過程。
最後,我們看到了如何使用 @JdbcTypeCode 註解,該註解在 Hibernate 6 中引入,以簡化 JSON 映射到數據庫列的過程,而無需自定義配置或使用外部庫。