hibernatejpaspring-data-jpanhibernate-mappinghibernate-onetomany

Bidirectional relationships lead to a stack overflow error


@Entity
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Portfolio {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;
    
    @OneToMany(mappedBy="parent" ,fetch = FetchType.EAGER, cascade = CascadeType.ALL, orphanRemoval = true)
    @JsonManagedReference
    private Set<PortfolioChildMap> parents;
    
}
@Entity
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class PortfolioChildMap {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id")
    private Integer id;

    @ManyToOne
    @JsonBackReference
    @JoinColumn(name = "parent_id")
    private Portfolio parent;

    @ManyToOne
    @JoinColumn(name = "child_id")
    private Portfolio child;
}

@Data
@Builder
public class PortfolioResponseDto {
    private Integer id;
    private List<PortfolioResponseDto> children;

    public static PortfolioResponseDto toDTO(Portfolio entity) {
        return PortfolioResponseDto.builder()
                .id(entity.getId())
                .children(entity.getParents().stream().map(parent-> toDTO(parent.getChild())).collect(Collectors.toList()))
                .build();
    }
}
public class PortfolioService  {

    private final PortfolioRepository portfolioRepository;

    @Override
    public List<PortfolioResponseDto> getAllPortfolios() {
        return portfolioRepository.findAll().stream()
                .map(PortfolioResponseDto::toDTO).collect(Collectors.toList());
    }

}

Table portfolio_child_map

enter image description here

As you can see there is a circle between Portfolio with id 33 and 34. Parent Portfolio.id=33 has childe Portfolio.id=34 and also Parent Portfolio.id=34 has childe Portfolio.id=33 and this is a circle

When i call PortfolioService.getAllPortfolios() it raise error in PortfolioResponseDto.toDTO method.

Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Handler dispatch failed; nested exception is java.lang.StackOverflowError] with root cause java.lang.StackOverflowError: null

I am pretty sure that is common mistake but i don't know how to correct fix that. So i have a two questions:

  1. What is the correct fix of this issue how to avoid that kind of error.

  2. In my case it's ok that i got a first child from parent and stop. I dont know how to do that. Should i implement custom findAll method in repository ?


Solution

  • Fundamentally, this isn't intrinsic to your entity mapping. It is simply a case of an infinite recursion. This means that you have to prevent the infinite recursion from happening. There are a few options... You can either...

    1. Decide that this is not a valid use-case and correct your data.
    2. Detect infinite recursion. If this is a valid use-case, abort your recursive algorithm when you detect a cycle. (For example, you can pass a set into your recursive method. Start with an empty set for the initial call, and then check to see whether your have already processed the current element. If not, add to set and continue. If already processed, abort).
    3. Modify your use-case. Instead of converting the entire depth into a DTO, only map one level. Then if the client/caller requires information about the next level, they can request it via another call. This places the responsibility of recursion-detection on the caller.