51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

JPA 异常 “Could Not Determine Recommended JdbcType for Class”

1、简介 {#1简介}

本文将带你了解使用 Hibernate 时出现 "could not determine recommended JdbcType for class" 异常的原因,以及解决办法。

2、常见原因 {#2常见原因}

出现该异常,通常是因为 JPA(Java Persistence API)抛出了异常。

这种情况发生在 JPA 无法确定类的推荐 JDBC 类型时。

通常,这会 Hibernate 应用启动期间发生,当 Hibernate 尝试创建数据库 Schema 或验证映射时。

3、复合数据类型 {#3复合数据类型}

JPA 中出现 "could not determine recommended JdbcType for class " 错误的一个常见原因是,我们在实体类中使用了复合数据类型或其他非原始类型。一个典型的例子就是使用 java.util.Map 来存储动态键值对。

来看看下面这个实体类:

@Entity
public class Student {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    // 使用了 Map
    private Map<String, String> studentDetails;

    // Getter/Setter 省略
}

在上例中,studentDetails 字段是 Map<String, String>。JPA 可能会抛出错误,因为它无法自动为这种复杂的数据类型确定推荐的 JDBC 类型。

测试,尝试用这个实体类建立一个 EntityManagerFactory

PersistenceException exception = assertThrows(PersistenceException.class,
  () -> createEntityManagerFactory("jpa-undeterminejdbctype-complextype"));
assertThat(exception)
  .hasMessage("Could not determine recommended JdbcType for Java type 'java.util.Map<java.lang.String, java.lang.String>'");

要这个问题,可以使用 @JdbcTypeCode 注解来指定 Hibernate 应为 studentDetails 字段使用的 JDBC 类型。在本例中,我们可以使用 JSON 数据类型将 Map 存储为 JSON 对象。

首先,需要在 pom.xml 文件中添加 Jackson 依赖:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.17.0</version>
</dependency>

Jackson 是 Java 中处理 JSON 的常用库,它提供了映射 JSON 数据类型的必要工具。接下来,使用 @JdbcTypeCode 注解告诉 Hibernate 在 studentDetails 字段中使用 JSON 数据类型。下面是更新后的实体类:

@Entity
public class StudentWithJson {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @JdbcTypeCode(SqlTypes.JSON)
    private Map<String, String> studentDetails;

    // Getter / Setter 省略
}

通过添加 @JdbcTypeCode 注解,明确指示 Hibernate 该如何处理复杂数据类型。SqlTypes.JSON 常量用于指示 JDBC 类型为 JSON 。现在,Hibernate 将 studentDetails 字段作为 JSON 对象存储在数据库中,从而可以将 Java Map 正确映射到数据库中的 JSON 列:

EntityManagerFactory factory = createEntityManagerFactory("jpa-undeterminejdbctype-complextypewithJSON");
EntityManager entityManager = factory.createEntityManager();

entityManager.getTransaction().begin();

StudentWithJson student = new StudentWithJson();
Map<String, String> details = new HashMap<>();
details.put("course", "Computer Science");
details.put("year", "2024");
student.setStudentDetails(details);

entityManager.persist(student);
entityManager.getTransaction().commit();

StudentWithJson retrievedStudent = entityManager.find(StudentWithJson.class, student.getId());
assertEquals(student.getStudentDetails(), retrievedStudent.getStudentDetails());

4、JPA 关系映射 {#4jpa-关系映射}

JPA 依靠注解来理解实体与其字段类型之间的关系。省略这些注解会导致 Hibernate 无法为相关字段确定推荐的 JDBC 类型,从而导致错误。

来看看 @OneToOne@OneToMany 这两种常见的关系映射,缺少注解会导致什么问题。

4.1、一对一映射 {#41一对一映射}

考虑一个场景:Employee(雇员)实体与 Address(地址)实体之间是一对一的关系。如果我们忘记注解这种关系,Hibernate 可能会抛出 PersistenceException(持久化异常),因为它无法为 Address 字段确定适当的 JDBC 类型:

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private Address address;

    // Getter / Setter 方法省略
}

在这种情况下,当我们尝试初始化 EntityManagerFactory 时,会遇到以下异常:

PersistenceException exception = assertThrows(PersistenceException.class,
  () -> createEntityManagerFactory("jpa-undeterminejdbctype-relationship"));
assertThat(exception)
  .hasMessage("Could not determine recommended JdbcType for Java type 'com.baeldung.jpa.undeterminejdbctype.Address'");

出现异常的原因是 Hibernate 不知道如何将 address 字段映射到数据库。要解决这个问题,我们需要正确注解一对一关系。

Employee 实体添加必要的注解:

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToOne
    @JoinColumn(name = "address_id", referencedColumnName = "id")
    private Address address;

    // Getter / Setter 方法省略
}

通过添加 @OneToOne 注解,我们指示 Hibernate 处理 EmployeeAddress 之间的关系。现在,Hibernate 知道 Employee 实体中的 address 字段映射到了 Address 实体,并使用 Employee 表中的 address_id 列来建立这种关系。

4.2、一对多映射 {#42一对多映射}

与一对一映射类似,在处理 JPA 中的一对多关系时,正确的注解是至关重要的,以避免异常。以 Department(部门)和 Employee(雇员)实体之间的一对多映射为例。

在这种情况下,一个 Department 可以有多个 Employee。如果我们忘记正确注解这种关系,Hibernate 可能会抛出 PersistenceException,因为它无法为 employees 字段确定适当的 JDBC 类型。

下面初始设置,没有适当的注解:

@Entity
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private List<Employee> employees = new ArrayList<>();

    // Getter / Setter 方法省略
}

这会在初始化 EntityManagerFactory 时抛出异常:

PersistenceException exception = assertThrows(PersistenceException.class,
  () -> createEntityManagerFactory("jpa-undeterminejdbctype-relationship"));
assertThat(exception)
  .hasMessage("Could not determine recommended JdbcType for Java type 'java.util.List<com.baeldung.jpa.undeterminejdbctype.Employee>'");

出现异常的原因是 Hibernate 不知道如何将 employees 字段映射到数据库。要解决这个问题,需要正确注解 Department 实体中的一对多关系。

Department 实体添加必要的注解:

@Entity
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany(mappedBy = "department", cascade = CascadeType.ALL)
    private List<Employee> employees = new ArrayList<>();

    // Getter / Setter 方法省略
}

我们经常在 Department 类中添加 @OneToMany 关系,但忘记在 Employee 类中用 @ManyToOne 关系进行注解,导致此类错误。以下是如何正确注解关系的双方:

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ManyToOne
    @JoinColumn(name = "department_id")
    private Department department;

    // Getter / Setter 方法省略
}

这种设置可确保 Hibernate 能正确映射 Department 实体中的 employees 字段和 Employee 实体中的 department 字段,从而避免发生 PersistenceException 并实现正确的数据库交互。

5、总结 {#5总结}

本文介绍了 Hibernate 中出现 "could not determine recommended JdbcType for class" 异常的原因,以及如何解决该异常。


Ref:https://www.baeldung.com/jpa-could-not-determine-recommended-jdbctype-for-class

赞(2)
未经允许不得转载:工具盒子 » JPA 异常 “Could Not Determine Recommended JdbcType for Class”