Чтобы избежать бесконечной рекурсии, вы должны просто использовать аннотацию @JsonIgnoreProperties
, но с массивом всех вложенных полей многие-ко-многим, например:
@JsonIgnoreProperties({"friends", "friendsOf", "invitedFriends", "invitedFriendsOf"})
@ManyToMany
@JoinTable(...)
private Set<Person> friends;
Затем, чтобы избежать исключения com.fasterxml.jackson.databind.JsonMappingException: failed to lazily initialize a collection of role...
который будет увеличиваться при попытке получить данные Person в вашем контроллере, вы можете использовать @EntityGraph
(в методе запроса вашего хранилища) с параметром attributePaths
, который также установлен в массив этих имен полей, чтобы заполнить их значения в одном запросе:
@Transactional(readOnly = true)
public interface PersonRepo extends JpaRepository<Person, Long> {
@EntityGraph(attributePaths = {"friends", "friendsOf", "invitedFriends", "invitedFriendsOf"})
Optional<Person> getById(Long aLong);
}
В этом случае будут установлены все значения полей, рекурсия будет исключена, и вы сможете получить правильный результат в вашем контроллере:
@GetMapping("/{id}")
public Person get(@PathVariable Long id) {
return personRepo.getById(id)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "Person not found"));
}
Тогда вы можете собрать всех людей. Учитывая, что данные одного человека достаточно велики, неправильно объединять всех людей со всеми связанными друзьями в один список. Лучше получать только базовые c поля каждого человека. В этом случае вы можете использовать простой DTO:
@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();
}
}
и сопоставить с ним Person:
@GetMapping
public List<PersonDto> getAll() {
return personRepo.findAll().stream().map(PersonDto::new).collect(Collectors.toList());
}
Благодаря этому сопоставлению вы также избежите исключения com.fasterxml.jackson.databind.JsonMappingException
.
Entity Person, который используется в этом ответе:
@Data
@EqualsAndHashCode(of = "email")
@ToString(of = {"id", "name", "email"})
@Entity
@Table(name = "people")
public class Person {
@Id
@GeneratedValue
private Long id;
@Column(nullable = false, length = 32)
private String name;
@NaturalId
@Column(nullable = false, length = 32)
private String email;
@JsonIgnoreProperties({"friends", "friendsOf", "invitedFriends", "invitedFriendsOf"})
@ManyToMany
@JoinTable(name = "friends", joinColumns = @JoinColumn(name = "person_id"), inverseJoinColumns = @JoinColumn(name = "friend_id"))
private Set<Person> friends;
@JsonIgnoreProperties({"friends", "friendsOf", "invitedFriends", "invitedFriendsOf"})
@ManyToMany
@JoinTable(name = "friends", joinColumns = @JoinColumn(name = "friend_id"), inverseJoinColumns = @JoinColumn(name = "person_id"))
private Set<Person> friendsOf;
@JsonIgnoreProperties({"friends", "friendsOf", "invitedFriends", "invitedFriendsOf"})
@ManyToMany
@JoinTable(name = "invited_friends", joinColumns = @JoinColumn(name = "person_id"), inverseJoinColumns = @JoinColumn(name = "friend_id"))
private Set<Person> invitedFriends;
@JsonIgnoreProperties({"friends", "friendsOf", "invitedFriends", "invitedFriendsOf"})
@ManyToMany
@JoinTable(name = "invited_friends", joinColumns = @JoinColumn(name = "friend_id"), inverseJoinColumns = @JoinColumn(name = "person_id"))
private Set<Person> invitedFriendsOf;
}
Моя рабочая демонстрация - вы можете запустить в вашей среде IDE подключитесь к базе данных H2 (используя этот подход ), чтобы увидеть ее данные. Если вашей IDE является IntelliJ IDEA, вы можете запускать демонстрационные запросы прямо из файла demo.http . А благодаря log4jdb c -spring-boot-starter вы можете увидеть все SQL запросы в журнале приложений.