0

I've created 2 entities in Spring with JPA annotations:

Project:

package com.example.technologyradar.model;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Project {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
    @GenericGenerator(name="native", strategy = "native")
    private Long id;

    private String name;
    @ManyToMany(mappedBy = "projects")
    private Set<Technology> assignedTechnologies = new HashSet<Technology>();
}

Technology:

package com.example.technologyradar.model;

import com.example.technologyradar.dto.constant.TechnologyStatus;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.*;
import java.util.HashSet;
import java.util.Set;

@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Technology {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
    @GenericGenerator(name="native", strategy = "native")
    private Long id;

    private String name;

    @Enumerated(EnumType.STRING)
    private TechnologyStatus technologyStatus;

    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST, targetEntity = Category.class)
    @JoinColumn(name="category_id", referencedColumnName = "id", nullable = false)
    private Category category;

    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.PERSIST, targetEntity = Coordinate.class)
    @JoinColumn(name="coordinate_id", referencedColumnName = "id", nullable = false)
    private Coordinate coordinate;

    @ManyToMany
    @JoinTable(
            name = "projects_technologies",
            joinColumns = @JoinColumn(name="technology_id"),
            inverseJoinColumns = @JoinColumn(name="project_id")
    )
    private Set<Project> projects = new HashSet<Project>();

}

My goal is to get List of projects with technologies usage list with ignoring Coordinate and Category from Technology Entity. When I perform simply findAll():

public List<Project> getProjectsWithTechnologyUsage() {
        return (List<Project>) projectRepository.findAll();
    }

then I'm obtaining famous Infinite Recursion error:

Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: java.util.ArrayList[0]->com.example.technologyradar.model.Project["assignedTechnologies"])]

I know that one of the solutions is to add @JsonManagedReference and @JsonBackRerefence annotations but I don't know how to do it correctly for my particular case. I would be grateful for any suggestions. Thanks!

Krzysztof Michalski
  • 791
  • 1
  • 9
  • 25
  • Does this answer your question? [Infinite Recursion with Jackson JSON and Hibernate JPA issue](https://stackoverflow.com/questions/3325387/infinite-recursion-with-jackson-json-and-hibernate-jpa-issue) – Toni Nov 30 '22 at 13:55

2 Answers2

0

If you use json serialization you can

https://github.com/mikebski/jackson-circular-reference

but anyway add to Project entity

@EqualsAndHashCode(of = "id") @ToString(of = "id")

Shakirov Ramil
  • 1,381
  • 9
  • 13
0

Actually, for your scenario, you probably just want @JsonIgnore on top of Technology.projects.

Alternatively, if you sometimes want to print Technology.projects, you can use @JsonView on top of Project.technologies instead, to modify the behavior just for this one scenario where Project is the top level object to serialize.

crizzis
  • 9,978
  • 2
  • 28
  • 47
  • Where shoud I exactyl add `@JsonView(View.Base.class)` annotation? Could you clarify more deeply? – Krzysztof Michalski Nov 30 '22 at 14:12
  • I'm obtaining the same exception after adding: `@JsonView(View.Base.class) private Set assignedTechnologies = new HashSet();` – Krzysztof Michalski Nov 30 '22 at 14:23
  • You also need to put `@JsonView(View.Base.class)` on all the fields inside `Technology` you want `project.technologies[]` to include. I suppose this means, in your case, all the properties of `Technology` *except* for `projects`. [Here's a nice tutorial](https://dev.to/markbdsouza/jacksons-jsonview-with-springboot-tutorial-56ba) explaining it a little deeper. – crizzis Nov 30 '22 at 14:36
  • Add `@JsonIgnore` above `private Set projects = new HashSet();` hasn't helped. Still I'm receiving Infinite recursion. – Krzysztof Michalski Nov 30 '22 at 14:41
  • I've added: `@JsonView(View.Base.class) private Set assignedTechnologies = new HashSet();` in Project entity. Additionally for properties: `@JsonView(View.Base.class) private String name;` and `@Id @GeneratedValue(strategy = GenerationType.AUTO, generator = "native") @GenericGenerator(name="native", strategy = "native") @JsonView(View.Base.class) private Long id;`. It still dosen't work. – Krzysztof Michalski Nov 30 '22 at 14:45
  • Could you please post your Jackson configuration? I'm specifically interested in autodetection options. While it might be tricky to get JSON views right, `@JsonIgnore` should just work – crizzis Nov 30 '22 at 14:47
  • I have no additional Jackson config. Maybe should I add some settings with `@Configuration` annotation? I'm for sure that `@JsonView(View.Base.class)` works when I mark only let's say fields `id`, `name` from Project entity. – Krzysztof Michalski Nov 30 '22 at 14:51
  • Ok, I've solved the issue with `@JsonView` the problem was casued by `@Data` annotation which is not recommended in Entities. – Krzysztof Michalski Nov 30 '22 at 15:19
  • Another solution would be to map your entity into a DTO, which would allow you to control the serialized properties more easily – Marc Bannout Nov 30 '22 at 16:32
  • Maybe it's better approacch because I can actually see on loga chat there are joins to coordinate entity and category. Marc how would you write query? By using queryDsl? – Krzysztof Michalski Nov 30 '22 at 19:08
  • You can keep your query as is. What I would do is map the properties you want to render to the DTO. In the method that maps your entity to a DTO, you can choose to ignore the properties you don't want to render. I think it is a good solution to solve your problem but there might be a better way and I would be happy to learn it. – Marc Bannout Dec 01 '22 at 12:12
  • Here's [an example](https://www.baeldung.com/java-dto-pattern#2-connecting-both-sides) on how to do it. In this case, the mapper is a `Component` bean but it doesn't need to be. You can make a `static` method that you call in your controller. – Marc Bannout Dec 01 '22 at 12:19