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

1、简介

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

2、常见原因

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

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

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

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 关系映射

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

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

4.1、一对一映射

考虑一个场景: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、一对多映射

与一对一映射类似,在处理 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、总结

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


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