Home > Enterprise >  Spring Data JPA return VO list threw LazyInitializationException
Spring Data JPA return VO list threw LazyInitializationException

Time:01-06

I have a Region Entity like this

@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
}

and the repository like this, because I need to provide some field to search (maybe null) for the frontend, so I extends the JpaSpecificationExecutor

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

Cause the region has lots records, so I need it pageable and sortable. I define a RegionVO like this

@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
    }
}

I want the return result look like RegionVO List and with the page,sort info. In the service class I wrote the search method like below (Some classes eg Sorter,PageResult are defined by myself and have no effect on this problem)

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));
    }

I have comment the code which will throw LazyInitializationException

How can I fix this issue? In the Region Entity, I use the LAZY fectch, I don't want it to be EAGER because the performance problem.

because the RegionVO is not a Entity, so I can't define a repository like RegionVORepository.

CodePudding user response:

I think the issue in your code is that you are trying to return a separate object RegionVO with all the fields already fetched and flattened at the controller level, but still want the benefits of lazy fetching (deferring fetching to the serialization phase).

You need to rethink of what is the information you really need to return first and control the serialization of it. You can use Jackson annotations for this: https://www.baeldung.com/jackson-annotations

  • If you need the Region information and its direct parent (no grand parents), try using @JsonSerialize to control how the parents get fetched/serialized. This would be useful to avoid fetching circular references or too many levels of parents.
  • If you don't need the parent at all use @JsonView to remove the property from getting serialized.

One you figure this out then return List<Region> instead of List<RegionVO> and keep the lazy initialization.

CodePudding user response:

I suggest you override the findAll method in RegionRepository and annotate it with @EntityGraph to configure the fetch plan of the resulting query, as shown below:

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

By providing attributePaths, fields that should be fetched eagerly are defined.

  • Related