当我有用户并且他们有其他用户作为朋友时,我想使应用程序类似于facebook。因此,我创建了一个实体User
,该实体与其自身具有ManyToMany关系,而且它们可以互相邀请加入好友列表。不幸的是,当我想获取邀请好友的用户时出现此错误:
Request processing failed; nested exception is org.springframework.http.converter.HttpMessageNotWritableException: 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.pk.thesis.devbook.models.dto.UserDTO["invitedFriends"]->java.util.ArrayList[0]->com.pk.thesis.devbook.models.dto.UserDTO["invitedFriends"]->java.util.ArrayList[0]-
... (it goes forever)
>com.pk.thesis.devbook.models.dto.UserDTO["invitedFriends"]->java.util.ArrayList[0]with root cause
我缩短的用户实体类:
@Data
@Entity
@Table( name = "users",
uniqueConstraints = {
@UniqueConstraint(columnNames = "username"),
@UniqueConstraint(columnNames = "email")
})
@JsonIdentityInfo(generator= ObjectIdGenerators.UUIDGenerator.class, property="@id")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotBlank
@Size(max = 40)
private String username;
//other things...
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name="tbl_friends",
joinColumns=@JoinColumn(name="personId"),
inverseJoinColumns=@JoinColumn(name="friendId")
)
private List<User> friends;
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@JoinTable(name="tbl_friends",
joinColumns=@JoinColumn(name="friendId"),
inverseJoinColumns=@JoinColumn(name="personId")
)
private List<User> friendOf;
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@JoinTable(name="tbl_invites_to_friends",
joinColumns=@JoinColumn(name="personId"),
inverseJoinColumns=@JoinColumn(name="invited_personId")
)
@JsonIgnoreProperties("invitationsToFriends")
private List<User> invitedFriends;
@JsonIgnore
@ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
@JoinTable(name="tbl_invites_to_friends",
joinColumns=@JoinColumn(name="invited_personId"),
inverseJoinColumns=@JoinColumn(name="personId")
)
@JsonIgnoreProperties("invitedFriends")
private List<User> invitationsToFriends;
}
如您所见,我尝试将其设置为惰性,也尝试了@JsonIgnore注释,但没有任何效果。有什么建议吗?
我的方法返回UserDTO(将用户映射到UserDTO)
public UserDTO getUserDTO(String username) {
return userRepository.findByUsername(username)
.map(u -> modelMapper.map(u, UserDTO.class))
.orElseThrow(() -> new UsernameNotFoundException("User not
found"));
}
UserDTO通过org.modelmapper.ModelMapper映射
public class UserDTO {
private String username;
private String firstname;
private String lastname;
private String email;
private List<UserDTO> invitedFriends;
private List<UserDTO> invitationsToFriends;
}
答案 0 :(得分:5)
为避免无限递归,您应该只使用@JsonIgnoreProperties
批注,但应使用所有嵌套的多对多字段的数组,例如:
@JsonIgnoreProperties({"friends", "friendsOf", "invitedFriends", "invitedFriendsOf"})
@ManyToMany
@JoinTable(...)
private Set<Person> friends;
然后,为避免尝试在控制器中获取Person数据时出现的com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role...
异常,可以将@EntityGraph
(在存储库的查询方法上)与参数{{1 }}也设置为这些字段名称的数组,以在一个查询中填充其值:
attributePaths
在这种情况下,将设置所有字段值,避免递归,并且您将能够在控制器中获得正确的结果:
@Transactional(readOnly = true)
public interface PersonRepo extends JpaRepository<Person, Long> {
@EntityGraph(attributePaths = {"friends", "friendsOf", "invitedFriends", "invitedFriendsOf"})
Optional<Person> getById(Long aLong);
}
那么您可能想让所有人都得到。考虑到一个人的数据很大,因此将所有拥有所有相关朋友的人放在一个列表中是不正确的。最好只获取每个人的基本字段。在这种情况下,您可以使用简单的DTO:
@GetMapping("/{id}")
public Person get(@PathVariable Long id) {
return personRepo.getById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Person not found"));
}
然后将Person映射到它:
@Value
public class PersonDto {
private long id;
private String name;
private String email;
public PersonDto(Person person) {
this.id = person.getId();
this.name = person.getName();
this.email = person.getEmail();
}
}
由于此映射,您还将避免出现@GetMapping
public List<PersonDto> getAll() {
return personRepo.findAll().stream().map(PersonDto::new).collect(Collectors.toList());
}
异常。
此答案中使用的实体人:
com.fasterxml.jackson.databind.JsonMappingException
My working demo -您可以在IDE中运行它,连接到H2数据库(使用this approach)以查看其数据。如果您的IDE是IntelliJ IDEA,则可以直接从文件demo.http运行演示请求。借助log4jdbc-spring-boot-starter,您可以在应用程序日志中看到所有SQL查询。
答案 1 :(得分:1)
感谢评论部分的答案,我找到了一种方法。我用另一个日期字段创建了另一个实体InvitationsToFriends,并通过OneToMany关系将其与我的User实体联系起来。我还使用所需的字段(用户名,名字,姓氏)创建了ReducedUserDTO和ReducedInvitationsToFriendsDTO。
我的用户类别:
@Entity
public class User implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Size(max = 40)
private String username;
@Size(max = 120)
private String password;
@Column
private String firstname;
@Column
private String lastname;
@OneToMany(mappedBy="to")
private List<InvitationsToFriends> invitationsToFriends;
@OneToMany(mappedBy="from")
private List<InvitationsToFriends> invitedFriends;
}
邀请朋友:
@Entity
public class InvitationsToFriends implements Serializable{
@Id
@GeneratedValue(strategy= GenerationType.AUTO)
private Long id;
@ManyToOne( fetch = FetchType.LAZY)
@JoinColumn(name="from_user_fk")
private User from;
@ManyToOne( fetch = FetchType.LAZY)
@JoinColumn(name="to_user_fk")
private User to;
@Column(name = "invitation_date")
private Date invitationDate;
}
UserDTO:
@Data
public class UserDTO {
private String username;
private String firstname;
private String lastname;
private List<ReducedInvitationsToFriendsDTO> invitedFriends;
private List<ReducedInvitationsToFriendsDTO> invitationsToFriends;
}
ReducedInvitationsToFriendsDTO和ReducedUserDTO:
@Data
public class ReducedInvitationsToFriendsDTO {
private ReducedUserDTO from;
private ReducedUserDTO to;
}
@Data
public class ReducedUserDTO {
private String username;
private String firstname;
private String lastname;
}
现在,响应json如下所示:
username: "username"
firstname: "firstname"
lastname: "lastname"
email: "email@email.com"
invitedFriends: [
from: {username: "username", firstname: "firstname", lastname: "lastname"}
to: {username: "invitedUsername", firstname: "invitedFirstname", lastname: "invitedLastName"}]