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

Hibernate LazyInitializationException问题排查与解决(序列化场景)

Hibernate LazyInitializationException 异常原因及解决方案

问题描述

在自定义MachineGroupSerializer序列化器中,尝试通过ObjectMapper获取MachineGroup实体的多对多关联字段versions时,持续抛出以下异常:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.altair.autoTester.entities.machinegroup.MachineGroup.versions, could not initialize proxy - no Session

相关代码如下:

MachineGroup 实体

@Entity
@Table(name="machine_groups")
@Getter
@Setter
@AllArgsConstructor(access = AccessLevel.PUBLIC)
@NoArgsConstructor
public class MachineGroup {
    @Id
    @GeneratedValue(strategy= GenerationType.AUTO, generator = "machine_groups_seq")
    @SequenceGenerator(name = "machine_groups_seq", allocationSize = 1, initialValue = 2)
    @Column(name = "id")
    private long id;

    @ManyToMany(cascade = CascadeType.MERGE)
    @JoinTable(name = "machine_groups_to_versions",
            joinColumns = @JoinColumn(name = "machine_group_id"),
            inverseJoinColumns = @JoinColumn(name = "version_id"))
    @JsonManagedReference(value="machineGroups-versions")
    private Set<Version> versions = new HashSet<>();

    @ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    @JoinTable(name = "machine_groups_to_tr_types",
            joinColumns = @JoinColumn(name = "machine_group_id"),
            inverseJoinColumns = @JoinColumn(name = "tr_type_id"))
    private Set<TrType> trTypes = new HashSet<>();
}

Version 实体

@Entity
@Table(name="versions")
@Getter
@Setter
public class Version {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "versions_seq")
    @SequenceGenerator(name = "versions_seq", allocationSize = 1)
    @Column(name = "id")
    private long id;
    @Column(name = "name", unique = true)
    private String name;
    @Column(name = "creation_time")
    private Date creationTime;
    @Column(name = "exe_file")
    @Lob
    private Blob exeFile;

    @JsonBackReference(value="machineGroups-versions")
    @ManyToMany(mappedBy = "versions", cascade = CascadeType.MERGE)
    private Set<MachineGroup> machineGroups = new HashSet<>();

    public Version(){};

    public Version(String name) {
        this.name = name;
    }
}

MachineGroupSerializer 序列化器

public class MachineGroupSerializer extends StdSerializer<MachineGroup> {

    public MachineGroupSerializer() {
        this(null);
    }

    public MachineGroupSerializer(Class<MachineGroup> t) {
        super(t);
    }

    @Transactional
    @Override
    public void serialize(MachineGroup machineGroup,
                          JsonGenerator jsonGenerator,
                          SerializerProvider serializerProvider) throws IOException {

        jsonGenerator.writeStartObject();
        jsonGenerator.writeNumberField("id",machineGroup.getId());
        jsonGenerator.writeObjectField("versions", machineGroup.getVersions().stream().map(Version::getId).collect(Collectors.toSet()));
        jsonGenerator.writeObjectField("trTypes", machineGroup.getTrTypes().stream().map(TrType::getId).collect(Collectors.toSet()));

        jsonGenerator.writeEndObject();
    }
}

TrType 实体

@Table(name = "tr_types", indexes = {
        @Index(name = "tr_types_type_name_uindex", columnList = "type_name", unique = true)
})
@Entity
@AllArgsConstructor(access = AccessLevel.PUBLIC)
@NoArgsConstructor
public class TrType {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

    @Column(name = "type_name", nullable = false)
    private String typeName;
}

已通过@JsonManagedReference和@JsonBackReference避免循环引用,trTypes字段使用FetchType.EAGER可正常获取数据,但不想给versions添加该属性。调用序列化器的MachineGroupService方法已标注@Transactional,怀疑Hibernate Session在序列化阶段已关闭。


异常原因

  1. Session生命周期不匹配:Spring的@Transactional注解会在Service方法执行完毕后自动提交事务并关闭Hibernate Session。而序列化操作是在Service返回实体后才执行的,此时Session已关闭,懒加载的versions集合无法触发数据库查询完成初始化。
  2. 序列化器的@Transactional无效:MachineGroupSerializer不是Spring管理的Bean,Spring无法为其创建事务代理,因此序列化方法上的@Transactional注解不会生效,无法重新打开Session。
  3. trTypes正常的核心原因:该字段配置了FetchType.EAGER,会在查询MachineGroup实体时直接从数据库加载关联数据,无需依赖后续的Session。

解决方案

方案1:在Service层提前初始化懒加载集合

在Service的事务方法内,主动触发versions集合的加载,确保数据在Session关闭前完成初始化。可以使用Hibernate.initialize()方法或者调用集合的size()方法触发加载:

@Service
public class MachineGroupService {
    @Autowired
    private MachineGroupRepository machineGroupRepository;

    @Transactional
    public MachineGroup getMachineGroupById(long id) {
        MachineGroup machineGroup = machineGroupRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("MachineGroup not found"));
        
        // 主动初始化versions集合
        Hibernate.initialize(machineGroup.getVersions());
        // 或者调用size()方法触发加载
        // machineGroup.getVersions().size();
        
        return machineGroup;
    }
}

方案2:使用DTO传输数据

创建与MachineGroup对应的DTO类,在Service层的事务内将实体数据转换为DTO(包括提取versions的ID集合),序列化时直接操作DTO,完全规避懒加载问题:

定义MachineGroupDTO

@Getter
@Setter
public class MachineGroupDTO {
    private long id;
    private Set<Long> versionIds;
    private Set<Long> trTypeIds;

    public MachineGroupDTO(MachineGroup machineGroup) {
        this.id = machineGroup.getId();
        this.versionIds = machineGroup.getVersions().stream()
                .map(Version::getId)
                .collect(Collectors.toSet());
        this.trTypeIds = machineGroup.getTrTypes().stream()
                .map(TrType::getId)
                .collect(Collectors.toSet());
    }
}

Service层返回DTO

@Service
public class MachineGroupService {
    @Autowired
    private MachineGroupRepository machineGroupRepository;

    @Transactional
    public MachineGroupDTO getMachineGroupDTOById(long id) {
        MachineGroup machineGroup = machineGroupRepository.findById(id)
                .orElseThrow(() -> new RuntimeException("MachineGroup not found"));
        
        return new MachineGroupDTO(machineGroup);
    }
}

此时序列化器可以直接针对DTO编写,或者使用默认序列化即可,无需处理懒加载问题。

方案3:配置Open Session In View(OSIV)

通过配置OSIV过滤器,让Hibernate Session在整个HTTP请求周期内保持打开,直到响应序列化完成。这种方式无需修改业务代码,但要注意长期打开Session可能带来的性能问题和连接占用风险。

在Spring Boot中,只需添加以下配置即可启用OSIV:

spring.jpa.open-in-view=true

方案4:批量抓取优化懒加载

如果不想使用EAGER加载,也可以通过@BatchSize注解配置批量抓取,减少N+1查询问题,同时保持懒加载特性。需要确保在Session存在时访问集合:

@ManyToMany(cascade = CascadeType.MERGE)
@JoinTable(name = "machine_groups_to_versions",
        joinColumns = @JoinColumn(name = "machine_group_id"),
        inverseJoinColumns = @JoinColumn(name = "version_id"))
@JsonManagedReference(value="machineGroups-versions")
@BatchSize(size = 10) // 批量加载10个集合
private Set<Version> versions = new HashSet<>();

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 09:20:41