在 Hibernate 中使用 Array/JSON/XML 類型儲存基本陣列和集合
1. 概述
當我們想要在實體中儲存非關聯式資料時,在 SQL 表格欄位中對應資料集合是一種常見方法。在 Hibernate 6 中,預設映射機制發生了變化,使得在資料庫端儲存此類資料更加有效率。
在本文中,我們將回顧這些變更。此外,我們將討論使用 Hibernate 5 遷移持久性資料的可能方法。
2. Hibernate 6.x 中新的基本陣列/集合映射
在 Hibernate 6 之前,我們對集合進行無條件映射,預設使用類型代碼SqlTypes.VARBINARY
。在幕後,我們使用 Java 序列化來序列化內容。現在,由於映射的變化,我們可以將集合映射為本機數組實作、JSON 或 XML 。
讓我們回顧一下一些流行的 SQL 方言,看看它們如何對應集合類型欄位。首先,讓我們新增最新的 Spring Data JPA依賴項,該依賴項已經包含 Hibernate 6.x:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
另外,讓我們新增 H2 資料庫依賴項,因為我們將能夠切換方言和模式並使用它來檢查不同資料庫的行為:
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
現在讓我們建立將在所有情況下使用的實體:
public class User {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
Long id;
List<String> tags;
//getters and setters
}
我們建立了一個帶有id
和使用者標籤清單的User
實體。
2.1. PostgreSQL 方言
在PostgreSQLDialect
中,我們重寫了supportsStandardArrays()
方法,而該驅動程式支援集合的本機陣列實作。
要檢查此行為,讓我們配置我們的資料庫:
spring:
datasource:
url: jdbc:h2:mem:mydb;MODE=PostgreSQL
username: sa
password: password
driverClassName: org.h2.Driver
jpa:
database-platform: org.hibernate.dialect.PostgreSQLDialect
show-sql: true
我們已經在 PostgreSQL 模式下設定了 H2。 此外,我們也指定了PostgreSQLDialect
類別作為資料庫平台。我們啟用了 SQL 腳本日誌記錄來查看表格列的類型定義。
現在讓我們取得User
實體的對應並檢查tags
欄位的 SQL 類型:
static int ARRAY_TYPE_CODE = 2003;
@PersistenceContext
EntityManager entityManager;
@Test
void givenPostgresDialect_whenGetUserEntityFieldsTypes_thenExpectedTypeShouldBePresent() {
MappingMetamodelImpl mapping = (MappingMetamodelImpl) entityManager.getMetamodel();
EntityMappingType entityMappingType = mapping
.getEntityDescriptor(User.class.getName())
.getEntityMappingType();
entityMappingType.getAttributeMappings()
.forEach(attributeMapping -> {
if (attributeMapping.getAttributeName().equals("tags")) {
JdbcType jdbcType = attributeMapping.getSingleJdbcMapping().getJdbcType();
assertEquals(ARRAY_TYPE_CODE, jdbcType.getJdbcTypeCode());
}
});
}
我們已經獲得了 JDBC 映射並檢查了tags
字段是否具有數組 JDBC 類型代碼。除此之外,如果我們檢查日誌,我們將看到該列選擇varchar array
作為 SQL 類型:
Hibernate:
create table users (
id bigint not null,
tags varchar(255) array,
primary key (id)
)
2.2.甲骨文方言
在OracleDialect
中,我們沒有覆寫supportsStandardArrays()
方法。儘管如此,在getPreferredSqlTypeCodeForArray()
內部,我們無條件支援集合的陣列類型。
讓我們配置資料庫來測試 Oracle 行為:
spring:
datasource:
url: jdbc:h2:mem:mydb;MODE=Oracle
username: sa
password: password
driverClassName: org.h2.Driver
jpa:
database-platform: org.hibernate.dialect.OracleDialect
show-sql: true
我們將資料庫切換到 Oracle 模式並指定OracleDialect
。現在,讓我們對User
實體執行類型檢查:
@Test
void givenOracleDialect_whenGetUserEntityFieldsTypes_thenExpectedTypeShouldBePresent() {
MappingMetamodelImpl mapping = (MappingMetamodelImpl) entityManager.getMetamodel();
EntityMappingType entityMappingType = mapping
.getEntityDescriptor(User.class.getName())
.getEntityMappingType();
entityMappingType.getAttributeMappings()
.forEach(attributeMapping -> {
if (attributeMapping.getAttributeName().equals("tags")) {
JdbcType jdbcType = attributeMapping.getSingleJdbcMapping().getJdbcType();
assertEquals(ARRAY_TYPE_CODE, jdbcType.getJdbcTypeCode());
}
});
}
正如預期的那樣,我們在tags
欄位中有一個陣列 JDBC 類型代碼。我們來看看日誌中顯示的內容:
Hibernate:
create table users (
id number(19,0) not null,
tags StringArray,
primary key (id)
)
正如我們所看到的, StringArray
SQL 類型用於tags
列。
2.3.自訂方言
預設情況下,沒有用於將集合對應為 JSON 或 XML 的方言。讓我們建立一個自訂方言,使用 JSON 作為集合類型欄位的預設類型:
public class CustomDialect extends Dialect {
@Override
public int getPreferredSqlTypeCodeForArray() {
return supportsStandardArrays() ? ARRAY : JSON;
}
@Override
protected void registerColumnTypes(TypeContributions typeContributions, ServiceRegistry serviceRegistry) {
super.registerColumnTypes( typeContributions, serviceRegistry );
final DdlTypeRegistry ddlTypeRegistry =
typeContributions.getTypeConfiguration().getDdlTypeRegistry();
ddlTypeRegistry.addDescriptor( new DdlTypeImpl( JSON, "jsonb", this ) );
}
}
我們已經註冊了對 JSON 類型的支持,並將其新增為集合映射的預設類型。現在讓我們來配置我們的資料庫:
spring:
datasource:
url: jdbc:h2:mem:mydb;MODE=PostgreSQL
username: sa
password: password
driverClassName: org.h2.Driver
jpa:
database-platform: com.baeldung.arrayscollections.dialects.CustomDialect
我們已將資料庫切換到PostgreSQL
模式,因為它支援jsonb
類型。此外,我們開始使用CustomDialect
類別。
現在我們將再次檢查類型映射:
static int JSON_TYPE_CODE = 3001;
@Test
void givenCustomDialect_whenGetUserEntityFieldsTypes_thenExpectedTypeShouldBePresent() {
MappingMetamodelImpl mapping = (MappingMetamodelImpl) entityManager.getMetamodel();
EntityMappingType entityMappingType = mapping
.getEntityDescriptor(User.class.getName())
.getEntityMappingType();
entityMappingType.getAttributeMappings()
.forEach(attributeMapping -> {
if (attributeMapping.getAttributeName().equals("tags")) {
JdbcType jdbcType = attributeMapping.getSingleJdbcMapping().getJdbcType();
assertEquals(JSON_TYPE_CODE, jdbcType.getJdbcTypeCode());
}
});
}
我們可以看到tags
欄位被對應為 JSON 類型。 我們來檢查一下日誌:
Hibernate:
create table users (
id bigint not null,
tags jsonb,
primary key (id)
)
正如預期的那樣, jsonb
列類型用於tags
。
3. 從 Hibernate 5.x 遷移到 Hibernate 6.x
Hibernate 5.x 和 Hibernate 6.x 中的集合映射使用不同的預設類型。要遷移到本機陣列或 JSON/XML 類型,我們必須透過 Java 序列化機制讀取現有資料。然後,我們需要透過該類型各自的 JDBC 方法將其寫回。
為了演示它,讓我們建立預計要遷移的實體:
@Entity
@Table(name = "migrating_users")
public class MigratingUser {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
@JdbcTypeCode(SqlTypes.VARBINARY)
private List<String> tags;
private List<String> newTags;
// getters, setters
}
我們創建了一個newTags
字段,預設情況下該字段映射為數組類型,並明確使用SqlTypes.VARBINARY
作為現有的 Tags 字段,因為它是 Hibernate 版本 5.x 中的預設映射類型。
現在,讓我們為我們的實體建立一個儲存庫:
public interface MigratingUserRepository extends JpaRepository<MigratingUser, Long> {
}
最後,我們來執行遷移邏輯:
@Autowired
MigratingUserRepository migratingUserRepository;
@Test
void givenMigratingUserRepository_whenMigrateTheUsers_thenAllTheUsersShouldBeSavedInDatabase() {
prepareData();
migratingUserRepository
.findAll()
.stream()
.peek(u -> u.setNewTags(u.getTags()))
.forEach(u -> migratingUserRepository.save(u));
}
我們已經從資料庫中讀取了所有項目,將它們的值複製到新字段,並將它們保存回資料庫中。為了控制記憶體消耗,我們可以考慮在讀取過程中進行分頁。為了提高持久化速度,我們可以使用批次機制。遷移後,我們可以從表中刪除舊列。
4. 結論
在本教程中,我們回顧了 Hibernate 6.x 中的新集合映射。我們探索如何使用內部數組類型和 JSON 欄位來儲存序列化集合。此外,我們也實作了新資料庫模式的遷移機制。使用新的映射,我們不再需要自己實現它來為我們的集合支援更有效的資料類型。
與往常一樣,程式碼可以在 GitHub 上取得。