Spring Data JPA返回VO列表抛出LazyInitializationException



我有一个像这样的区域实体

@Entity
class Region {
private Long id;
private String name;
private String type;
private String state;
@JsonIgnore
@OneToMany(mappedBy = "parent", fetch = FetchType.LAZY)
private List<Region> children;
@JsonIgnore
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id", referencedColumnName = "id", foreignKey =   @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Region parent;
// some other fields are ignore
}

和像这样的存储库,因为我需要提供一些字段来搜索前端(可能为null),所以我扩展了JpaSpecificationExecutor

public interface RegionRepository extends JpaRepository<Region, String>, JpaSpecificationExecutor<Region> {}

因为该区域有很多记录,所以我需要它可分页和可排序。我像这样定义一个RegionVO

@Getter
@Setter
@ToString
@AllArgsConstructor
@NoArgsConstructor
public class RegionVO {
private Long id;
private String name;
private Long parentId;
private Long parentName;
private String type;
private String state;

public RegionVO(Region region) {
id = region.getId();
name = region.getName();
parentId = region.getParent().getId(); // this line will throw LazyInitializationException
parentName = region.getParent().getName(); // this line will throw LazyInitializationException
}
}

我希望返回结果看起来像RegionVO列表和页面,排序信息。在服务类中,我编写了如下的搜索方法(有些类如Sorter,PageResult是我自己定义的,对这个问题没有影响)

private Specification<Region> createSpecification(String name, String type, String parentName, String  state) {
return (root, query, criteriaBuilder) -> {
List<Predicate> ps = new ArrayList<>();
if (StringUtils.hasText(name)) {
ps.add(criteriaBuilder.like(root.get("name"), "%" + name + "%"));
}
if (type != null) {
ps.add(criteriaBuilder.equal(root.get("type"), type));
}
if (StringUtils.hasText(parentName)) {
ps.add(criteriaBuilder.like(root.join("parent").get("name"), "%" + parentName + "%"));
}
if (state != null) {
ps.add(criteriaBuilder.equal(root.get("state"), state));
}
int size = ps.size();
return query.where(ps.toArray(new Predicate[size])).getRestriction();
};
}
@Transactional(rollbackFor = Exception.class)
public ResponseEntity<RestResult> getRegions(RegionQueryRequest request, int pageNum, int pageSize) {
pageSize = Math.min(pageSize, PAGE_MAX_SIZE);
pageNum = pageNum - 1;
Pageable pageable = PageRequest.of(pageNum, pageSize);
Specification<Region> regionSpecification = createSpecification(null, null, null, null);
List<Sorter> sorters = new ArrayList<>();
if (request != null) {
sorters = request.getSort();
Sort sort = Sort.unsorted();
for (Sorter s : sorters) {
sort = sort.and(Sort.by(s.getDirection(), s.getField()));
}
pageable = PageRequest.of(pageNum, pageSize, sort);
regionSpecification = createSpecification(request.getName(), request.getType(), request.getParentName(), request.getState());
}
Page<Region> regions = regionRepository.findAll(regionSpecification, pageable);
regions.getContent().get(0).getParent().getName(); // this line will throw LazyInitializationException
List<RegionVO> voList = regions.getContent().stream().map(RegionVO::new).toList(); // this line will throw LazyInitializationException
PageResult data = new PageResult(voList, regions, sorters);
return ResponseEntity.ok(RestResult.success("success", data));
}

我注释了会抛出LazyInitializationException的代码

如何解决这个问题?在Region Entity中,我使用LAZY fetch,我不希望它是EAGER,因为性能问题。

因为RegionVO不是一个实体,所以我不能定义像regionvorerepository这样的存储库。

我建议您覆盖RegionRepository中的findAll方法,并用@EntityGraph注释它,以配置结果查询的获取计划,如下所示:

public interface RegionRepository extends JpaRepository<Region, String>, JpaSpecificationExecutor<Region> {
@Override
@EntityGraph(attributePaths = {"children", "parent"})
Page<Region> findAll(Specification<Region> spec, Pageable pageable);
}

通过提供attributePaths,定义了需要急切获取的字段。

我认为您的代码中的问题是,您试图返回一个单独的对象RegionVO,其中所有字段已经在控制器级别提取和扁平,但仍然希望获得延迟抓取的好处(延迟抓取到序列化阶段)。

您需要重新考虑您真正需要首先返回的信息是什么,并控制它的序列化。你可以使用Jackson注释:https://www.baeldung.com/jackson-annotations

  • 如果你需要Region信息和它的直接父节点(没有祖父节点),尝试使用@JsonSerialize来控制父节点如何被获取/序列化。这将有助于避免获取循环引用或过多的父级。
  • 如果你根本不需要父属性,使用@JsonView从序列化中删除该属性。

如果你弄清楚了,然后返回List<Region>而不是List<RegionVO>,并保持延迟初始化。

相关内容

  • 没有找到相关文章

最新更新