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

Spring FetchType.Lazy结合Projection加载含Eager实体报错解决方案咨询

问题描述

数据库数据量达数百万级,已将所有配置改为FetchType.Lazy懒加载,并使用Spring Projection进行数据查询。在Projection中添加无Eager关联的Lugar实体时,Postman能正常返回数据;但添加包含多个Eager关联的ArticulosV3实体时,出现序列化错误。

推测报错原因是ArticulosV3中的Eager关联导致的,当前Projection代码如下:

@JsonIgnoreProperties("decoratedClass")
public interface UbicacionesLugaresProjection {
    Integer getId();

    int getTipolugar();

    Lugar getiDlugar();

    boolean getPordefecto();

    ArticulosV3 getArticuloOneway();
}

可正常加载的关联配置:

@JoinColumn(name = "ID_lugar", referencedColumnName = "ID")
@ManyToOne(optional = false)
@JsonView(JsonViews.Summary.class)
private Lugar iDlugar;

(Lugar无Eager关联)

无法加载的关联配置:

@JoinColumn(name = "articulo_oneway", referencedColumnName = "ID")
@NotFound(action = NotFoundAction.IGNORE)
@ManyToOne(optional = true)
@JsonView(JsonViews.Summary.class)
private ArticulosV3 articuloOneway;

(ArticulosV3包含多个Eager关联,且无法移除这些Eager配置)

报错信息:

2024-04-25 17:09:19 [http-nio-8080-exec-2] WARN  o.s.w.s.m.s.DefaultHandlerExceptionResolver - Failed to write HTTP message: org.springframework.http.converter.HttpMessageNotWritableException: Could not write content: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: java.util.ArrayList[0]->$Proxy349["articuloOneway"]->com.clickrent.backoffice.ws.entities.ArticulosV3["articulosUbicacion"]->org.hibernate.collection.internal.PersistentBag[0]->com.clickrent.backoffice.ws.entities.ArticulosUbicacion["idUbicaciones"]->com.clickrent.backoffice.ws.entities.Ubicaciones_$$_jvst1a7_9e["handler"]); nested exception is com.fasterxml.jackson.databind.JsonMappingException: No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer and no properties discovered to create BeanSerializer (to avoid exception, disable SerializationFeature.FAIL_ON_EMPTY_BEANS) ) (through reference chain: java.util.ArrayList[0]->$Proxy349["articuloOneway"]->com.clickrent.backoffice.ws.entities.ArticulosV3["articulosUbicacion"]->org.hibernate.collection.internal.PersistentBag[0]->com.clickrent.backoffice.ws.entities.ArticulosUbicacion["idUbicaciones"]->com.clickrent.backoffice.ws.entities.Ubicaciones_$$_jvst1a7_9e["handler"])

咨询:能否在Projection中嵌套另一个Projection来加载ArticulosV3,避开其Eager关联?或者有其他可行解决方案?


解决方案

1. 嵌套Projection(最优方案)

完全可以通过嵌套Projection解决问题,核心是用自定义Projection替代直接返回ArticulosV3实体,只选择业务需要的字段,避免触发其内部的Eager关联。

实现步骤:

  • 先创建ArticulosV3对应的Projection接口,仅定义需要的getter方法,排除会触发Eager关联的字段:
public interface ArticulosV3Projection {
    Integer getId();
    String getNombre(); // 替换为你实际需要的业务字段
    // 仅保留必要字段,不要添加关联其他Eager实体的方法
}
  • 修改原UbicacionesLugaresProjection,将返回ArticulosV3的方法改为返回新的Projection:
@JsonIgnoreProperties("decoratedClass")
public interface UbicacionesLugaresProjection {
    Integer getId();

    int getTipolugar();

    Lugar getiDlugar();

    boolean getPordefecto();

    ArticulosV3Projection getArticuloOneway(); // 替换为Projection类型
}

Spring Data JPA会自动只查询指定字段,不会加载ArticulosV3内部的Eager关联,从根源避免序列化时的懒加载代理问题。

2. 其他备选方案

方案A:禁用Jackson的FAIL_ON_EMPTY_BEANS特性

在Spring配置中添加以下代码,让Jackson忽略无法序列化的空bean,但这只是临时规避,可能返回不必要的代理字段,不推荐长期使用:

@Configuration
public class JacksonConfig {
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        return mapper;
    }
}

方案B:用@JsonIgnore屏蔽关联字段

在ArticulosV3实体中,对引发问题的Eager关联字段添加@JsonIgnore,但需注意如果该实体在其他业务场景需要这些字段,会产生影响,需谨慎使用:

@JsonIgnore
@ManyToOne(fetch = FetchType.EAGER)
private OtroEntity otroEntity; // 示例关联字段

方案C:自定义DTO+手动映射

如果Projection不够灵活,可以创建自定义DTO类,在Repository中查询指定字段后手动映射,完全控制返回的数据结构:

// 主DTO类
public class UbicacionesLugaresDTO {
    private Integer id;
    private int tipolugar;
    private Lugar lugar;
    private boolean pordefecto;
    private ArticulosV3DTO articuloOneway;

    // getter、setter、构造方法
}

// 关联DTO类
public class ArticulosV3DTO {
    private Integer id;
    private String nombre;
    // 其他需要的字段
}

在Repository中使用@Query查询指定字段,再通过Stream或工具类映射到DTO:

@Query("SELECT new com.example.dto.UbicacionesLugaresDTO(u.id, u.tipolugar, u.iDlugar, u.pordefecto, new com.example.dto.ArticulosV3DTO(a.id, a.nombre)) FROM Ubicaciones u LEFT JOIN u.articuloOneway a")
List<UbicacionesLugaresDTO> findAllWithProjection();

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.25 05:53:24