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

获取全部公司时触发LazyInitializationException的解决咨询

解决Hibernate LazyInitializationException问题(无需全局EAGER加载)

问题描述

调试模式下调用findAll方法可正常获取所有Company实体的详细信息,但执行mvn install生成jar包并通过docker compose运行程序后,调用对应接口会抛出如下异常:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: tr.gov.cbfo.domain.entity.company.Company.addresses, could not initialize proxy - no Session

将addresses的fetch type改为EAGER后,interests和companyLicences集合又出现相同问题。不想将所有关联集合设为EAGER加载,需寻找替代解决方案。

相关代码

Company实体

@Entity
@Table
@AllArgsConstructor
@NoArgsConstructor
@SuperBuilder
@Getter
@Setter
public class Company extends BaseDatedEntity {

    @Column(unique = true, nullable = false)
    private String name;

    @Column(unique = true, nullable = false)
    private String email;

    @Column(unique = true, nullable = false)
    private String phoneNumber;

    private boolean active;

    @OneToMany(
            fetch = FetchType.LAZY,
            cascade = {
                    CascadeType.MERGE,
                    CascadeType.PERSIST,
                    CascadeType.REFRESH,
                    CascadeType.REMOVE
            }
    )
    @JoinColumn(name = "company_id")
    @JsonIgnore
    private List<Address> addresses = new ArrayList<>();


    @ManyToMany(
            fetch = FetchType.LAZY,
            cascade = {CascadeType.PERSIST,CascadeType.MERGE,CascadeType.REFRESH}
    )
    @JoinTable(
            name = "company_interest",
            joinColumns = {@JoinColumn(name = "company_id")},
            inverseJoinColumns = {@JoinColumn(name = "interest_id")}
    )
    private List<Interest> interests = new ArrayList<>();

    @OneToMany(fetch = FetchType.LAZY,cascade = {CascadeType.PERSIST,CascadeType.MERGE,CascadeType.REFRESH})
    @JoinColumn(name = "company_id")
    @JsonIgnore
    private List<CompanyLicence> companyLicences = new ArrayList<>();

} 

服务方法

@Override
public Page<Company> findAll(Pageable pageable) {
    return companyRepository.findAll(pageable);
}

DTO

@SuperBuilder(toBuilder = true)
@Getter
@Setter
@NoArgsConstructor
public class CompanyDetailDto extends BaseCompanyDto{
    private List<AddressDto> addresses;
    private List<InterestDto> interests;
    private List<CompanyLicenceDto> companyLicences;
}

接口代码

@GetMapping(Apis.Company.COMPANY_DETAILED)
public List<CompanyDetailDto> getCompaniesDetailed(@ParameterObject Pageable pageable) {
    return companyService.findAll(pageable).stream().map(companyMapper::fromCompany).collect(Collectors.toList());
}

解决方案

方案1:使用@EntityGraph预加载关联集合

在Repository层通过@EntityGraph指定需要预加载的关联属性,替代默认的延迟加载,确保在Session关闭前完成所有需要的数据加载。

修改CompanyRepository:

public interface CompanyRepository extends JpaRepository<Company, Long> {
    @EntityGraph(attributePaths = {"addresses", "interests", "companyLicences"})
    Page<Company> findAll(Pageable pageable);
}

此方式无需修改服务层代码,Spring Data会自动生成包含关联集合的查询,避免LazyInitializationException。

方案2:在服务层主动初始化关联集合

通过Hibernate的initialize方法,在服务层(Session仍处于打开状态)主动触发延迟加载集合的初始化:

修改服务层方法:

import org.hibernate.Hibernate;

@Override
public Page<Company> findAll(Pageable pageable) {
    Page<Company> companies = companyRepository.findAll(pageable);
    companies.forEach(company -> {
        Hibernate.initialize(company.getAddresses());
        Hibernate.initialize(company.getInterests());
        Hibernate.initialize(company.getCompanyLicences());
    });
    return companies;
}

这种方式灵活可控,可根据业务需求选择性初始化关联集合。

方案3:启用Open Session in View(谨慎使用)

在Spring Boot中配置Open Session in View,让Hibernate Session在整个HTTP请求周期内保持打开,直到DTO转换完成:

在application.yml中添加配置:

spring:
  jpa:
    open-in-view: true

注意:该方案会延长Session生命周期,可能导致连接池占用过高,不建议在高并发生产环境使用。

方案4:使用DTO投影查询(推荐)

通过Spring Data的投影功能,直接查询DTO所需的字段,避免加载整个实体和不必要的关联数据,同时从根源避免延迟加载问题。

  1. 定义投影接口:
public interface CompanyDetailProjection {
    String getName();
    String getEmail();
    String getPhoneNumber();
    boolean isActive();
    
    List<AddressProjection> getAddresses();
    List<InterestProjection> getInterests();
    List<CompanyLicenceProjection> getCompanyLicences();

    interface AddressProjection {
        // 定义Address需要的字段,如:
        String getStreet();
        String getCity();
    }

    interface InterestProjection {
        String getName();
    }

    interface CompanyLicenceProjection {
        String getLicenceNumber();
    }
}
  1. 在Repository中添加投影查询方法:
public interface CompanyRepository extends JpaRepository<Company, Long> {
    @EntityGraph(attributePaths = {"addresses", "interests", "companyLicences"})
    Page<CompanyDetailProjection> findAllProjectedBy(Pageable pageable);
}
  1. 修改服务层和接口:
// 服务层
@Override
public Page<CompanyDetailProjection> findAllProjected(Pageable pageable) {
    return companyRepository.findAllProjectedBy(pageable);
}

// 接口层
@GetMapping(Apis.Company.COMPANY_DETAILED)
public List<CompanyDetailDto> getCompaniesDetailed(@ParameterObject Pageable pageable) {
    return companyService.findAllProjected(pageable).stream()
            .map(projection -> {
                // 投影转DTO,可通过MapStruct或手动转换
                CompanyDetailDto dto = CompanyDetailDto.builder()
                        .name(projection.getName())
                        .email(projection.getEmail())
                        .phoneNumber(projection.getPhoneNumber())
                        .active(projection.isActive())
                        // 转换关联集合
                        .addresses(projection.getAddresses().stream().map(addr -> 
                                AddressDto.builder().street(addr.getStreet()).city(addr.getCity()).build()
                        ).collect(Collectors.toList()))
                        .build();
                return dto;
            })
            .collect(Collectors.toList());
}

此方式只加载业务所需的数据,性能最优,同时彻底避免延迟加载问题。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 19:15:07