51工具盒子

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

解决 Spring Data JPA ConverterNotFoundException: No converter found

1、概览 {#1概览}

在使用 Spring Data JPA 时,我们经常会利用派生和自定义查询,以我们喜欢的格式返回结果。一个典型的例子就是 DTO 投影,它提供了一种只 SELECT 某些特定列以减少不必要数据开销的好方法。

然而,DTO 投影并不总是那么容易,如果实现不当,可能会导致 ConverterNotFoundException 异常。本文将带你了解 ConverterNotFoundException 异常出现的原因,以及如何在使用 Spring Data JPA 时避免 ConverterNotFoundException 异常。

2、在实践中理解异常 {#2在实践中理解异常}

通过一个实际例子来理解异常。

为了简单起见,使用 H2 数据库。首先,在 pom.xml 文件中添加其依赖:

<dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <version>2.2.224</version>
</dependency>

2.1、H2 配置 {#21h2-配置}

Spring Boot 提供了对 H2 嵌入式数据库的支持。默认情况下,它会配置应用使用用户名 sa 和空密码连接到 H2。

将数据库连接凭证添加到 application.properties 文件中:

spring.datasource.url=jdbc:h2:mem:mydb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

如上就是使用 Spring Boot 设置 H2 配置所需的全部内容。

2.2、Entity 类 {#22entity-类}

们定义一个 JPA 实体类 Employee

@Entity
public class Employee {

    @Id
    private int id;
    @Column
    private String firstName;
    @Column
    private String lastName;
    @Column
    private double salary;

    // Getter/Setter 方法省略

}

如上,员工类(Employee)定义了 idfirstNamelastNamesalary 属性。

@Entity 注解来表示 Employee 类是一个 JPA Entity。@Id 标记主键字段,@Column 用于映射数据列和实体字段。

2.3、JPA Repository {#23jpa-repository}

接下来,创建一个 Spring Data JPA Repository 来处理存储和检索员工的逻辑:

@Repository
public interface EmployeeRepository extends JpaRepository<Employee, Integer> {
}

在这里,假设只需要显示员工的全名。因此,使用 DTO 投影来只 SELECT firstNamelastName 字段。

由于 Employee 类包含了额外的字段,因此创建一个名为 EmployeeFullName 的新类,其中只包含 firstNamelastName 字段:

public class EmployeeFullName {

    private String firstName;
    private String lastName;

    // get/set 方法省略

    public String fullName() {
        return getFirstName()
          .concat(" ")
          .concat(getLastName());
    }

}

如上,创建了一个自定义方法 fullName() 来显示员工的全名。现在,向 EmployeeRepository 添加一个派生查询,返回员工的全名:

EmployeeFullName findEmployeeFullNameById(int id);

最后,进行测试,:

@Test
void givenEmployee_whenGettingFullName_thenThrowException() {
    Employee emp = new Employee();
    emp.setId(1);
    emp.setFirstName("Adrien");
    emp.setLastName("Juguet");
    emp.setSalary(4000);

    employeeRepository.save(emp);

    assertThatThrownBy(() -> employeeRepository
      .findEmployeeFullNameById(1))
      .isInstanceOfAny(ConverterNotFoundException.class)
      .hasMessageContaining("No converter found capable of converting from type" 
        + "[com.baeldung.spring.data.noconverterfound.models.Employe");
}

如上所示,测试失败,出现 ConverterNotFoundException

该异常的根本原因是 JpaRepository 期望其派生查询返回 Employee 实体类的实例。由于该方法返回 EmployeeFullName 对象,Spring Data JPA 无法找到合适的 Converter(转换器)将预期的 Employee 对象转换为新的 EmployeeFullName 对象。

3、解决办法 {#3解决办法}

在使用类实现 DTO 投影时,Spring Data JPA 默认使用构造函数来确定要检索的字段。因此,这里 最简单的解决方案是为 EmployeeFullName 类添加一个带参数的构造函数

public EmployeeFullName(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
}

这等于告诉了 Spring Data JPA 只 SELECT firstNamelastName。现在,添加另一个测试来测试解决方案:

@Test
void givenEmployee_whenGettingFullNameUsingClass_thenReturnFullName() {
    Employee emp = new Employee();
    emp.setId(2);
    emp.setFirstName("Azhrioun");
    emp.setLastName("Abderrahim");
    emp.setSalary(3500);

    employeeRepository.save(emp);

    assertThat(employeeRepository.findEmployeeFullNameById(2).fullName())
      .isEqualTo("Azhrioun Abderrahim");
}

不出所料,测试成功通过。

另一种解决方案是使用基于接口的投影。这样就不必担心构造函数了。因此,可以使用一个接口来公开要读取字段的 getter 方法:

public interface IEmployeeFullName {
    String getFirstName();

    String getLastName();

    default String fullName() {
        return getFirstName().concat(" ")
          .concat(getLastName());
    }
}

如上,使用 default 方法来显示全名。接下来,创建另一个派生查询,返回 IEmployeeFullName 类型的实例:

IEmployeeFullName findIEmployeeFullNameById(int id);

最后,再添加一个测试来验证第二个解决方案:

@Test
void givenEmployee_whenGettingFullNameUsingInterface_thenReturnFullName() {
    Employee emp = new Employee();
    emp.setId(3);
    emp.setFirstName("Eva");
    emp.setLastName("Smith");
    emp.setSalary(6500);

    employeeRepository.save(emp);

    assertThat(employeeRepository.findIEmployeeFullNameById(3).fullName())
      .isEqualTo("Eva Smith");
}

不出所料,基于接口的解决方案行之有效。

4、总结 {#4总结}

本文介绍了 Spring Data JPA 出现 ConverterNotFoundException 异常的原因,以及解决该异常的两种办法。


Ref:https://www.baeldung.com/spring-jpa-converter-exception

赞(3)
未经允许不得转载:工具盒子 » 解决 Spring Data JPA ConverterNotFoundException: No converter found