解決 JPA 中的 PostgreSQL JSON 類型不符錯誤
一、簡介
在本教程中,我們將探討使用 JPA 與 PostgreSQL 互動時常見的PSQLException
錯誤:「 column is of type json but the expression is of type character varying
」。我們將探討發生此錯誤的原因,確定觸發該錯誤的常見情況,並示範如何解決該錯誤。
2. 常見原因
在 PostgreSQL 中, JSON
或JSONB
資料類型用於儲存 JSON 資料。但是,如果我們嘗試將字串(字元變更)插入到需要 JSON 的欄位中,PostgreSQL 會拋出「 column is of type json but expression is of type character varying
」錯誤。在使用 JPA 和 PostgreSQL 時,這種情況尤其常見,因為 JPA 可能會嘗試將字串儲存到 JSON 列,從而導致此錯誤。
3. 演示錯誤
我們將建立一個基本的 Spring Boot 項目,其中包含必要的依賴項和測試資料來示範錯誤。首先,我們需要將PostgreSQL依賴項新增到 Maven pom.xml
檔案中:
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.7.1</version>
<scope>runtime</scope>
</dependency>
接下來,我們建立一個映射到student
表的 JPA 實體類別:
@Entity
@Table(name = "student")
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String admitYear;
@Column(columnDefinition = "json")
private String address;
// getters and setters
}
在這個實體類別中, address
欄位會對應到student
表中的address
列。值得注意的是,我們已將columnDefinition
屬性指定為JSON
以指示該列的類型為JSON
。
現在,讓我們嘗試將Student
物件儲存到資料庫中:
Student student = new Student();
student.setAdmitYear("2024");
student.setAddress("{\"postCode\": \"TW9 2SF\", \"city\": \"London\"}");
Throwable throwable = assertThrows(Exception.class, () -> studentRepository.save(student));
assertTrue(ExceptionUtils.getRootCause(throwable) instanceof PSQLException);
在此程式碼中,我們建立了一個Student
物件並將address
欄位設為 JSON 字串。然後,我們使用studentRepository
物件的save()
方法將該物件儲存到資料庫中。
但是,這會導致PSQLException
:
Caused by: org.postgresql.util.PSQLException: ERROR: column "address" is of type json but expression is of type character varying
出現此錯誤的原因是 JPA 嘗試將字串儲存到 JSON 列,但這是不允許的。
4.使用@Type
註解
要修復此錯誤,我們需要正確處理 JSON 類型。我們可以使用hibernate-types函式庫提供的@Type
註解。首先,讓我們將hibernate-types
依賴項加入pom.xml
中:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>2.18.0</version>
</dependency>
接下來,我們更新實體以包含@TypeDef
和@Type
註解:
@Entity
@Table(name = "student_json")
@TypeDefs({
@TypeDef(name = "jsonb", typeClass = JsonBinaryType.class)
})
public class StudentWithTypeAnnotation {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String admitYear;
@Type(type = "jsonb")
@Column(columnDefinition = "json")
private String address;
// Getters and Setters
}
這裡, @TypeDef(name = “jsonb”, typeClass = JsonBinaryType.class)
註冊了一個名為JSONB
自訂類型,它使用 hibernate-types-52 庫中的JsonBinaryType
類別。 JsonBinaryType
處理 PostgreSQL 的JSONB
資料類型,允許 JSON 資料作為JSONB
有效地儲存和檢索。
@Type
註解用於指定欄位的自訂 Hibernate 類型。透過指定@Type(type = “jsonb”)
,我們告訴 Hibernate 使用透過@TypeDef
註冊的自訂類型JSONB
。此自訂類型處理 PostgreSQL 中 Java 物件和JSONB
資料之間的轉換。
此設定可確保使用JSONB
資料類型在 PostgreSQL 中有效地儲存和擷取 JSON 資料:
StudentWithTypeAnnotation student = new StudentWithJson();
student.setAdmitYear("2024");
student.setAddress("{\"postCode\": \"TW9 2SF\", \"city\": \"London\"}");
studentWithTypeAnnotationRepository.save(student);
StudentWithTypeAnnotation retrievedStudent = studentWithTypeAnnotationRepository.findById(student.getId()).orElse(null);
assertThat(retrievedStudent).isNotNull();
assertThat(retrievedStudent.getAddress()).isEqualTo("{\"postCode\":\"TW9 2SF\",\"city\":\"London\"}");
5. 原生查詢
此外,當我們使用@Query
註解和本機 SQL 查詢將 JSON 資料插入 PostgreSQL 表中時,我們會遇到相同的錯誤。讓我們透過建立一個插入本機查詢來示範此錯誤:
@Query(value = "INSERT INTO student (admit_year, address) VALUES (:admitYear, :address) RETURNING *", nativeQuery = true)
Student insertJsonData(@Param("admitYear") String admitYear, @Param("address") String address);
當我們使用 JSON 字串呼叫此方法時,我們預期會得到一個異常:
Throwable throwable = assertThrows(Exception.class, () ->
studentRepository.insertJsonData("2024","{\"postCode\": \"TW9 2SF\", \"city\": \"London\"}"));
assertTrue(ExceptionUtils.getRootCause(throwable) instanceof PSQLException);
為了解決這個問題,我們需要在插入之前將 JSON 字串轉換為JSONB
類型,以避免此錯誤。以下是如何執行此操作的範例:
public interface StudentWithTypeAnnotationRepository extends JpaRepository<StudentWithTypeAnnotation, Long> {
@Query(value = "INSERT INTO student (admit_year, address) VALUES (:admitYear, CAST(:address AS JSONB)) RETURNING *", nativeQuery = true)
StudentWithTypeAnnotation insertJsonData(@Param("admitYear") String admitYear, @Param("address") String address);
}
在上面的程式碼中,我們使用CAST(:address AS JSONB)
語法將:address
參數轉換為JSONB
類型。現在,我們來測試一下這個方法:
StudentWithTypeAnnotation student = studentWithJsonRepository.insertJsonData("2024","{\"postCode\": \"TW9 2SF\", \"city\": \"London\"}");
StudentWithTypeAnnotation retrievedStudent = studentWithJsonRepository.findById(student.getId()).orElse(null);
assertThat(retrievedStudent).isNotNull();
assertThat(retrievedStudent.getAddress()).isEqualTo("{\"city\": \"London\", \"postCode\": \"TW9 2SF\"}");
六,結論
在本文中,我們探討如何解決使用 JPA 將 Java 物件對應到 PostgreSQL JSON 欄位時出現的PSQLException
錯誤「 column is of type json but the expression is of type character varying
」。
透過在使用原生 SQL 查詢時使用@Type
註解並將 JSON 字串轉換為JSONB
類型,我們可以使用JSONB
資料類型在 PostgreSQL 中有效地儲存和檢索 JSON 資料。
與往常一樣,範例的原始程式碼可在 GitHub 上取得。