获取全部公司时触发LazyInitializationException的解决咨询
问题描述
调试模式下调用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所需的字段,避免加载整个实体和不必要的关联数据,同时从根源避免延迟加载问题。
- 定义投影接口:
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(); } }
- 在Repository中添加投影查询方法:
public interface CompanyRepository extends JpaRepository<Company, Long> { @EntityGraph(attributePaths = {"addresses", "interests", "companyLicences"}) Page<CompanyDetailProjection> findAllProjectedBy(Pageable pageable); }
- 修改服务层和接口:
// 服务层 @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

