You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Spring Data JPA投影接口在WebTestClient中序列化失败问题求助

问题描述

定义了Spring Data JPA投影接口:

public interface BloodUnits {
    BloodType getBloodType();
    Integer getTotalUnits();
}

仓库中实现查询方法:

@Repository
public interface BloodDonationsRepository extends JpaRepository<BloodDonations, Long> {
    @Query(value = "SELECT u.bloodType AS bloodType, SUM(bd.units) AS totalUnits "
            + "FROM blood_donations bd, users u WHERE bd.donor.id = u.id GROUP BY u.bloodType")
    List<BloodUnits> countAvailableUnitsByBloodType();
}

Postman调用对应接口端点正常,但用WebTestClient测试时:

@SpringBootTest(webEnvironment = RANDOM_PORT)
public class BloodDonationsControllerTest {

    @Autowired
    WebTestClient webTestClient;
    // ...省略其他代码
    List<BloodUnits> availableBloodUnits = webTestClient.get()
            .uri(bloodDonationURI + "/admin/available_blood_units")
            .accept(MediaType.APPLICATION_JSON)
            .headers(h -> h.setBearerAuth(jwt))
            .exchange()
            .expectStatus()
            .isOk()
            .expectBodyList(new ParameterizedTypeReference<BloodUnits>(){})
            .returnResult()
            .getResponseBody();
    // ...省略其他代码
}

抛出错误:

org.springframework.core.codec.CodecException: Type definition error: [simple type, class com.bds.dto.BloodUnits]
...
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.bds.dto.BloodUnits` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information...

尝试将投影接口移至实体目录未解决问题,疑惑为何Postman调用正常但WebTestClient测试无法创建实例。

问题原因
  • Postman调用时,后端返回的是JSON字符串,Postman仅负责展示内容,不需要把JSON反序列化成BloodUnits接口实例——服务端的Spring Data JPA确实会为投影接口生成代理实例,但这个实例序列化后就变成了普通JSON,Postman无需处理接口实例的构造逻辑。
  • WebTestClient测试时,是在客户端侧把响应的JSON反序列化成BloodUnits接口类型。Jackson无法直接构造抽象类型(接口)的实例,因为接口没有构造方法,而Spring Data生成投影代理的逻辑仅在服务端生效,客户端侧没有这个代理生成机制,因此报错。
解决方法

方法1:改用具体DTO类替代投影接口

创建普通POJO类替代投影接口,Spring Data JPA同样支持将查询结果映射到这类类上:

public class BloodUnitsDTO {
    private BloodType bloodType;
    private Integer totalUnits;

    // 必须提供与查询结果字段匹配的构造方法
    public BloodUnitsDTO(BloodType bloodType, Integer totalUnits) {
        this.bloodType = bloodType;
        this.totalUnits = totalUnits;
    }

    // getter方法
    public BloodType getBloodType() { return bloodType; }
    public Integer getTotalUnits() { return totalUnits; }
}

修改仓库方法的返回类型:

List<BloodUnitsDTO> countAvailableUnitsByBloodType();

测试代码中也改用BloodUnitsDTO:

List<BloodUnitsDTO> availableBloodUnits = webTestClient.get()
        .uri(bloodDonationURI + "/admin/available_blood_units")
        .accept(MediaType.APPLICATION_JSON)
        .headers(h -> h.setBearerAuth(jwt))
        .exchange()
        .expectStatus()
        .isOk()
        .expectBodyList(BloodUnitsDTO.class)
        .returnResult()
        .getResponseBody();

方法2:测试时用Map接收响应

如果不想修改投影接口,可以在测试中将响应反序列化成Map,再手动处理数据:

List<Map<String, Object>> responseMaps = webTestClient.get()
        .uri(bloodDonationURI + "/admin/available_blood_units")
        .accept(MediaType.APPLICATION_JSON)
        .headers(h -> h.setBearerAuth(jwt))
        .exchange()
        .expectStatus()
        .isOk()
        .expectBodyList(Map.class)
        .returnResult()
        .getResponseBody();

// 手动将Map转成需要的结构
List<BloodUnits> availableBloodUnits = responseMaps.stream()
        .map(map -> new BloodUnits() {
            @Override
            public BloodType getBloodType() {
                return BloodType.valueOf((String) map.get("bloodType"));
            }

            @Override
            public Integer getTotalUnits() {
                return ((Number) map.get("totalUnits")).intValue();
            }
        })
        .collect(Collectors.toList());

方法3:配置Jackson支持接口反序列化(不推荐)

可以给BloodUnits接口添加@JsonDeserialize注解指定自定义反序列化类,或者配置Spring消息转换器,但这种方式配置复杂,不如直接用DTO类简洁。

内容的提问来源于stack exchange,提问作者mls

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 00:17:43