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

Spring Boot测试中JsonPath获取嵌套对象返回null的原因与解决

问题原因分析

你遇到的cliente返回null的问题,大概率和**JPA懒加载(Lazy Loading)**与Jackson序列化的交互有关,咱们拆解下核心原因:

  1. JPA默认懒加载行为:@ManyToOne注解默认的FetchType是LAZY,也就是说当你从数据库获取Referente对象时,它关联的Cliente并不会立即被加载,而是生成一个Hibernate代理对象。如果你的initializeData()方法执行完后Session已经关闭,此时调用referente1.getCliente()无法初始化这个代理对象,Jackson序列化时就会把它转成null。

  2. 第二个测试正常的原因:你第二个测试是直接查询Cliente实体,它关联的Agente虽然也是懒加载,但可能在初始化测试数据时,你主动获取过agentePrimoContatto(触发了代理初始化),或者查询Cliente的语句用了关联加载,所以序列化时能拿到正常的Agente对象。


最优解决方案推荐

下面是从优到次的解决方式,你可以根据项目阶段和需求选择:

1. 使用DTO(数据传输对象)返回接口结果(最推荐)

直接返回JPA实体到接口/测试场景是非常不规范的做法,容易引发懒加载问题、暴露敏感字段、耦合数据库结构和接口返回结构。正确的姿势是定义DTO类,在Service层完成实体到DTO的转换:

// 定义ReferenteDTO
public class ReferenteDTO {
    private Integer idReferente;
    private ClienteDTO cliente;
    private String cognomeReferente;
    // 构造器、getter/setter
}

// 定义ClienteDTO
public class ClienteDTO {
    private Integer idCliente;
    private String nominativo;
    private LocalDateTime dataProssimoContatto;
    private AgenteDTO agentePrimoContatto;
    // 构造器、getter/setter
}

// 定义AgenteDTO
public class AgenteDTO {
    private Integer idAgente;
    private String nome;
    private String cognome;
    private String telefono;
    // 构造器、getter/setter
}

在Service层做转换(可以用MapStruct这类工具简化重复代码):

public List<ReferenteDTO> getAllReferenti() {
    return referenteRepository.findAll().stream()
        .map(referente -> {
            ReferenteDTO dto = new ReferenteDTO();
            dto.setIdReferente(referente.getIdReferente());
            dto.setCognomeReferente(referente.getCognomeReferente());
            
            // 手动初始化关联对象(或在查询时用JOIN FETCH批量加载)
            Cliente cliente = referente.getCliente();
            ClienteDTO clienteDTO = new ClienteDTO();
            clienteDTO.setIdCliente(cliente.getIdCliente());
            clienteDTO.setNominativo(cliente.getNominativo());
            clienteDTO.setDataProssimoContatto(cliente.getDataProssimoContatto());
            
            Agente agente = cliente.getAgentePrimoContatto();
            AgenteDTO agenteDTO = new AgenteDTO();
            agenteDTO.setIdAgente(agente.getIdAgente());
            agenteDTO.setNome(agente.getNome());
            agenteDTO.setCognome(agente.getCognome());
            agenteDTO.setTelefono(agente.getTelefono());
            
            clienteDTO.setAgentePrimoContatto(agenteDTO);
            dto.setCliente(clienteDTO);
            return dto;
        })
        .collect(Collectors.toList());
}

测试时直接断言DTO的字段即可:

@Test
public void getAllReferente_Test() throws Exception {
    initializeData();
    mvc.perform(get("/referente").contentType(MediaType.APPLICATION_JSON))
        .andExpect(status().isOk())
        .andExpect(jsonPath("$[0].idReferente").value(referente1.getIdReferente()))
        .andExpect(jsonPath("$[0].cliente.idCliente").value(referente1.getCliente().getIdCliente()))
        .andExpect(jsonPath("$[0].cliente.nominativo").value(referente1.getCliente().getNominativo()))
        .andExpect(jsonPath("$[0].cognomeReferente").value(referente1.getCognomeReferente()));
}

2. 配置Jackson Hibernate模块处理懒加载

如果暂时不想重构为DTO,可以添加Jackson的Hibernate模块自动处理懒加载代理。首先添加Maven依赖:

<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-hibernate5</artifactId>
</dependency>

然后配置Jackson注册该模块:

@Configuration
public class JacksonConfig {
    @Bean
    public Module hibernateModule() {
        return new Hibernate5Module();
    }
}

这个模块会在序列化时自动初始化懒加载代理,但需要确保控制器方法处于@Transactional注解下(保证Session在序列化时未关闭)。

3. 测试中手动初始化关联对象

如果只是为了快速让测试通过,可以在initializeData()里手动触发代理初始化:

private void initializeData() {
    // 创建并保存Agente、Cliente、Referente的代码...
    // 初始化Referente的Cliente关联
    Hibernate.initialize(referente1.getCliente());
    // 若需要,继续初始化Cliente的Agente关联
    Hibernate.initialize(referente1.getCliente().getAgentePrimoContatto());
}

注意:这种方法仅适合测试场景,线上环境使用会引发N+1查询性能问题。

4. 修改FetchType为EAGER(不推荐)

直接把@ManyToOne的加载策略改成EAGER:

@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name="idCliente")
private Cliente cliente;

这种方法虽然简单,但会导致每次查询Referente都强制加载Cliente及其关联对象,严重影响查询性能,生产环境绝对不推荐。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 08:14:18