To begin with, an observation.
The error occurs when there are cycles, by users following each other, as the OP noted.
I've written and tested three versions, the last of which correctly lists all followers and following for all users.
The last version appears to be essentially the same as what the OP has written. It has been tested with transitive and direct self-references.
The following two versions both work, without listing followings or followers.
The first uses annotation @JsonIgnore above each field, while the second uses annotation @JsonIgnoreProperties({"followers", "followings"}) above the class declaration.
@Entity
@Table(name = "userentity")
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class UserEntity {
@Id
private Long id;
@Column
private String userName;
@Column
private String password;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_followings",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "following_id")
)
@Builder.Default
@EqualsAndHashCode.Exclude
@JsonIgnore
private Set<UserEntity> followings = new HashSet<>();
@ManyToMany(mappedBy = "followings", fetch = FetchType.EAGER)
@Builder.Default
@EqualsAndHashCode.Exclude
@JsonIgnore
private Set<UserEntity> followers = new HashSet<>();
}
@Entity
@Table(name = "userentity")
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties({"followers", "followings"})
public class UserEntity {
@Id
private Long id;
@Column
private String userName;
@Column
private String password;
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(
name = "user_followings",
joinColumns = @JoinColumn(name = "user_id"),
inverseJoinColumns = @JoinColumn(name = "following_id")
)
@Builder.Default
@EqualsAndHashCode.Exclude
private Set<UserEntity> followings = new HashSet<>();
@ManyToMany(mappedBy = "followings", fetch = FetchType.EAGER)
@Builder.Default
@EqualsAndHashCode.Exclude
private Set<UserEntity> followers = new HashSet<>();
}
The following code works and lists the first level of followings. Note the use of @JsonIgnoreProperties({"followers", "followings"}), as the OP attempted:
@Entity
@Table(name = "userentity")
@Builder
@Data
@AllArgsConstructor
@NoArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class UserEntity {
@Id
private Long id;
@Column
private String userName;
@Column
private String password;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(
name = "user_followings",
joinColumns = @JoinColumn(name = "id"),
inverseJoinColumns = @JoinColumn(name = "following_id")
)
@Builder.Default
@EqualsAndHashCode.Exclude
@JsonIgnoreProperties({"followers", "followings"})
private Set<UserEntity> followings = new HashSet<>();
@ManyToMany(mappedBy = "followings", fetch = FetchType.LAZY)
@Builder.Default
@EqualsAndHashCode.Exclude
@JsonIgnoreProperties({"followers", "followings"})
private Set<UserEntity> followers = new HashSet<>();
}
Actual output:
[
{
"id": 1,
"userName": "u1",
"password": "pass1",
"followings": [
{
"id": 3,
"userName": "u1",
"password": "pass"
},
{
"id": 2,
"userName": "u2",
"password": "pass2"
}
],
"followers": [
{
"id": 3,
"userName": "u1",
"password": "pass"
},
{
"id": 2,
"userName": "u2",
"password": "pass2"
}
]
},
{
"id": 2,
"userName": "u2",
"password": "pass2",
"followings": [
{
"id": 3,
"userName": "u1",
"password": "pass"
},
{
"id": 1,
"userName": "u1",
"password": "pass1"
}
],
"followers": [
{
"id": 3,
"userName": "u1",
"password": "pass"
},
{
"id": 1,
"userName": "u1",
"password": "pass1"
}
]
},
{
"id": 3,
"userName": "u1",
"password": "pass",
"followings": [
{
"id": 2,
"userName": "u2",
"password": "pass2"
},
{
"id": 1,
"userName": "u1",
"password": "pass1"
}
],
"followers": [
{
"id": 2,
"userName": "u2",
"password": "pass2"
},
{
"id": 1,
"userName": "u1",
"password": "pass1"
}
]
}
]
Note some other minor fixes to get the code working on my setup.
Other issues, such as the eager loading have not been addressed.