0

It is not possible to display apartments from the house entity as a whole list and separately by ID. When I go to the page, I get an error that an infinite loop

'(failed to write JSON: infinite recursion (stackoverflowerror); nested exception-com.fasterxml.Jackson.databind.JsonMappingException: infinite recursion (StackOverflowError) (via link chain: task.home pageproject.model.Home["city"]->task.home pageproject.model.City$HibernateProxy$ikPQOTJQ["home"])') '.

HouseRestController


@GetMapping("/{id}")
    @PreAuthorize("hasAuthority('user:read')")
    public House userPostInfo(@PathVariable(value = "id") Long id) {
        Optional<House> house = houseRepository.findById(id);
        List<House> res = new ArrayList<>();
        house.ifPresent(res::add);

        return res.stream().filter(houses -> houses.getId().equals(id))
                .findFirst()
                .orElse(null);
    }
Mario Codes
  • 689
  • 8
  • 15
Blacit
  • 203
  • 3
  • 12
  • 1
    use `@JsonIgnore` in the unnecessary inner list. Ex: if home has city list and city has home list, ignore one of them. – divilipir Sep 28 '20 at 10:53
  • 1
    @omer I installed this in the House and City class and it worked: `@JsonBackReference` `@JsonManagedReference` – Blacit Sep 28 '20 at 10:55
  • 1
    please, don't paste a link to your github, as the state of your repository may change in the future, you may move the repository or your whole repository may be deleted. Instead, post here the related parts. – Mario Codes Sep 28 '20 at 15:46

1 Answers1

2

This error occurs because of the way your entities are mapped. See that you have bidirectional relationships between:

House - City

House - Contract

User - Contract

Because you are using in those entities Lombok's annotation @Data this is what happens:

@Data generates all the boilerplate that is normally associated with simple POJOs (Plain Old Java Objects) and beans: getters for all fields, setters for all non-final fields, and appropriate toString, equals and hashCode implementations that involve the fields of the class

So because you are not excluding any data from any of the sides, whenever there's a need to calculate hashCode for your entity, to for example, store it inside any collection that uses hashTables, it will fail, because it will fall into an infinite loop. Same thing happens when Jackson is trying to serialize your entities. Because you have bidirectional mappings and you are not excluding any side of it, it will try recursively until StackOverflowError is thrown.

I'd advise not to use @Data and instead try to use annotations which you really need, like @Getter. However what you could do, to avoid this infinite loop is to exclude one side of the relationship from the equals and hashCode methods and also from the Jackson serialization/deserialization:

Contract.java

package task.homerent.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;

import javax.persistence.*;
import java.util.Date;

@Data
@Entity
@Table(name = "contract", schema = "public")
public class Contract {
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name = "id")
    private Long id;

    // ManyToMany к Дому
    @ManyToOne
    @JoinColumn(name = "id_house")
    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    @JsonIgnore
    private House house;

    // ManyToMany к Пользователю
    @ManyToOne
    @EqualsAndHashCode.Exclude
    @ToString.Exclude
    @JsonIgnore
    @JoinColumn(name = "id_tenant")
    private User user;


    @Column(name = "end_date", nullable = false)
    private Date endDate;
    @Column(name = "start_date", nullable = false)
    private Date id_house;
}

User.java

package task.homerent.model;

import lombok.Data;

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

@Data
@Entity
@Table(name = "users", schema = "public")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany(mappedBy = "user")
    private Set<Contract> contract;
    @Column(name = "email")
    private String email;
    @Column(name = "password")
    private String password;
    @Column(name = "first_name")
    private String firstName;
    @Column(name = "last_name")
    private String lastName;
    @Enumerated(value = EnumType.STRING)
    @Column(name = "role")
    private Role role;
    @Enumerated(value = EnumType.STRING)
    @Column(name = "status")
    private Status status;
}

City.java

package task.homerent.model;


import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;

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

@Data
@Entity
@Table(name = "city", schema = "public")
public class City {

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

    @OneToMany(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.PERSIST,
            CascadeType.REFRESH
    }, mappedBy = "city")
    @ToString.Exclude
    @EqualsAndHashCode.Exclude
    @JsonIgnore
    private Set<House> house;

    @Column(name = "id_region", nullable = false)
    private Integer id_region;
    @Column(name = "name", nullable = false)
    private String name;
}
Eulodos
  • 584
  • 1
  • 7
  • 10
  • Why did you delete the Role field in the User entity? – Blacit Sep 28 '20 at 11:01
  • Sorry, my mistake, during refactoring, I turned security off for convenience of testing. I will include it in the answer – Eulodos Sep 28 '20 at 11:03
  • Could you answer one more question, how is your version better than this solution, because it also helped me? I also added this above the House and City class: `@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})? public class City { @JsonBackReference private Set house; } public class House { @JsonManagedReference private City city; }` – Blacit Sep 28 '20 at 11:05
  • Link: https://ru.stackoverflow.com/questions/1183326/%d0%9d%d0%b5-%d1%83%d0%b4%d0%b0%d1%91%d1%82%d1%81%d1%8f-%d0%b2%d1%8b%d0%b2%d0%b5%d1%81%d1%82%d0%b8-%d0%b4%d0%b0%d0%bd%d0%bd%d1%8b%d0%b5-%d0%b8%d0%b7-%d1%81%d1%83%d1%89%d0%bd%d0%be%d1%81%d1%82%d0%b8 – Blacit Sep 28 '20 at 11:07
  • 1
    I wouldn't say that one solution is better than the other since they achieve the very same goal, see [documentation](https://fasterxml.github.io/jackson-annotations/javadoc/2.5/com/fasterxml/jackson/annotation/JsonBackReference.html), it states that: _Linkage is handled such that the property annotated with this annotation is not serialized; and during deserialization, its value is set to instance that has the "managed" (forward) link._ So in this use case both of the solutions simply exclude one side of the relationship from being serialized. Choose whatever is more readable for you – Eulodos Sep 28 '20 at 11:14
  • 1
    Note however that there is a difference when it comes to deserialization, please see this [SO entry](https://stackoverflow.com/questions/37392733/difference-between-jsonignore-and-jsonbackreference-jsonmanagedreference) for more information. – Eulodos Sep 28 '20 at 11:16