Java Spring Boot - JPA : StackOverflowError with a @ManyToMany relation

问题: I am currently developing an application with the API in JavaSpringBoot and I need to add relationships between users (friends). For this I made a many-to-many relation whi...

问题:

I am currently developing an application with the API in JavaSpringBoot and I need to add relationships between users (friends). For this I made a many-to-many relation which contains two fields:

CREATE TABLE friend_relation
(
    fk_id_friend   integer REFERENCES users (id) NOT NULL,
    fk_id_user     integer REFERENCES users (id) NOT NULL,
    PRIMARY KEY (fk_id_friend, fk_id_user)
);

When I connect with a user and add relationships : everything is fine, but if I connect to one of the accounts added as a friend by the other user, there is a StackOverflowError.

I know it's because of the almost identical entries in the database, but I have no idea how to get my entity to be correct. Currently each user must add the other individually, I imagine that I have to make a friend request system but again I am blocked. Do I have to make an "effective" field in my friend_relation table. If so, how do I use it? Should I create a specific entity for this table or leave it in the User entity?

Currently, this is what my user entity looks like:

@Entity
@Table(name = "users")
@Data
@Accessors(chain = true)
public class UserEntity {

    @Id
    @SequenceGenerator(name = "users_generator", sequenceName = "users_sequence", allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "users_generator")
    @Column(name = "id")
    private Integer id;

    [...]

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
            name = "friend_relation",
            joinColumns = @JoinColumn(name = "fk_id_user", referencedColumnName = "id", nullable = false),
            inverseJoinColumns = @JoinColumn(name = "fk_id_friend", referencedColumnName = "id", nullable = false)
    )
    private List<UserEntity> friends = new ArrayList<>();

}

When trying to modify my entity to avoid the error:

    @ManyToMany
    @JoinTable(name="friend_relation",
            joinColumns=@JoinColumn(name="fk_id_user"),
            inverseJoinColumns=@JoinColumn(name="fk_id_friend")
    )
    private List<UserEntity> friends;

    @ManyToMany
    @JoinTable(name="friend_relation",
            joinColumns=@JoinColumn(name="fk_id_friend"),
            inverseJoinColumns=@JoinColumn(name="fk_id_user")
    )
    private List<UserEntity> friendOf;

I looked for resources on the internet but I did not find it, I may have searched poorly and I apologize in advance if the answer has already been given.

If you can help me it is with great pleasure, thank you in advance! (And sorry for the Google Translate I preferred not to use my rough English)✌


Ok sorry update, i don't post the stake trace : https://pastebin.com/Ls2qRpU4

It happens when I get my user on the front side. I am trying to connect but I cannot because this error occurs.


回答1:

First of, I've noticed an @Data on your entity, which I suppose is from Project Lombok? Just be careful, Lombok generated methods can trigger lazy loading and there are problems with equals/hashCode for auto-generated IDs. (see here and here)

Now to your problem:

It's indeed a JSON serialization issue. You have circular references and Jackson runs in circles bc. it doesn't know where to stop.

You can either use a DTO projection where you resolve this circle yourself, like:

public class UserDto {
    public Integer id;
    public List<Integer> friends; // List of IDs
}

You can also use Jackson annotations to achieve almost the same thing, like @JsonIdentityInfo. Here's an article about it.

@JsonIdentityInfo(
    generator = ObjectIdGenerators.PropertyGenerator.class, 
    property = "id")
@Entity
@Table(name = "users")
@Data
@Accessors(chain = true)
public class UserEntity {

    @Id
    @SequenceGenerator(name = "users_generator", sequenceName = "users_sequence", allocationSize = 1)
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "users_generator")
    @Column(name = "id")
    private Integer id;

    [...]

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(
            name = "friend_relation",
            joinColumns = @JoinColumn(name = "fk_id_user", referencedColumnName = "id", nullable = false),
            inverseJoinColumns = @JoinColumn(name = "fk_id_friend", referencedColumnName = "id", nullable = false)
    )
    private List<UserEntity> friends = new ArrayList<>();

}

When you have complex Entities with circular references or very large object trees, you often have to think hard about how to serialize them - especially when taking LazyLoading into account. I have spent a lot of time on this in complex professional projects. Simple automatically generated DTOs and serialization sometimes don't cut it.


回答2:

I'm not an expert on this, so I'm sure others will give more accurate answers. However, the problem seems to occur when mapping the entity to JSON. You can't map to JSON a user with a collection of friend users that also have their friends in a fully mapped collection since that causes infinite recursion. I would try to use Jackson annotations to make the serialization of the friends list produce a list of ids instead of a list of complete users.

  • 发表于 2020-06-27 19:26
  • 阅读 ( 150 )
  • 分类:sof

条评论

请先 登录 后评论
不写代码的码农
小编

篇文章

作家榜 »

  1. 小编 文章
返回顶部
部分文章转自于网络,若有侵权请联系我们删除