В ходе разработки REST-сервиса с использованием Spring MVC я столкнулся с некоторыми проблемами, своим решением которых я хочу поделиться в этой статье.
Рассмотрим следующую доменную модель:
@Data
@Entity
public class Comment {
@Id
@GeneratedValue
private Long id;
@NotNull
@Valid
@ManyToOne(fetch = FetchType.LAZY)
private User author;
@NotNull
@Valid
@ManyToOne(fetch = FetchType.LAZY)
private Post post;
@NotBlank
private String content;
@NotNull
private Date date;
}
public class User {
@Id
@GeneratedValue
private Long id;
@NotBlank
@Column(unique = true)
private String username;
@NotBlank
private String firstName;
private String lastName;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "author")
private List<Comment> comments = new ArrayList<>();
@OneToMany(fetch = FetchType.LAZY, mappedBy = "author")
private List<Post> posts = new ArrayList<>();
}
@Data
@Entity
public class Post {
@Id
@GeneratedValue
private Long id;
@NotBlank
private String content;
@NotNull
@ManyToOne
private User author;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "post")
private List<Comment> comments = new ArrayList<>();
@NotNull
private Date date;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<PostLink> links = new HashSet<>();
}
@Data
@Entity
public class PostLink {
@Id
@GeneratedValue
private Long id;
private String name;
private String url;
@ManyToOne(fetch = FetchType.LAZY)
private Post post;
}
Существуют пользователи (User), записи в блоге (Post), связанные с записями ссылки (PostLink) и комментарии к записям (Comment). С записями и комментариями ассоциированы пользователи — авторы.
1. Lombok
Lombok — прекрасный инструмент, избавляющий программиста от написания однотипного кода, вроде get/set методов, equals, hashcode, toString и т.д. К сожалению, сокрытие этого кода может привести к потери бдительности и возникновению ошибок.
Основным источником проблем при работе с Lombok и JPA/Hibernate являются циклические ссылки в хранимых сущностях.
@RestController
@RequestMapping("/post")
public class PostController {
@Autowired
private CommentRepository commentRepository;
@Autowired
private PostRepository postRepository;
@Autowired
private UserRepository userRepository;
@PostMapping
public Long createPost(@RequestBody PostDTO postDTO) {
final Optional<User> userById = userRepository.findById(postDTO.getUserId());
if (!userById.isPresent()) {
return -1L;
}
final Post post = postDTO.toPost();
post.setDate(new Date());
post.setAuthor(userById.get());
final Post savedPost = postRepository.save(post);
return savedPost.getId();
}
@Data
static class PostDTO {
private Long userId;
private String content;
private Set<PostLinkDTO> postLinks;
Post toPost() {
final Post post = new Post();
post.setContent(content);
for (PostLinkDTO postLink : postLinks) {
final PostLink link = postLink.toPostLink();
link.setPost(post);
post.getLinks().add(link);
}
return post;
}
@Data
static class PostLinkDTO {
private String name;
private String url;
PostLink toPostLink() {
final PostLink postLink = new PostLink();
postLink.setName(name);
postLink.setUrl(url);
return postLink;
}
}
}
}
Попытка создания записи с помощью данного контроллера приведет к печальному результату:
Caused by: java.lang.StackOverflowError
at com.test.restdemo.domain.User.hashCode(User.java:19)
at com.test.restdemo.domain.Post.hashCode(Post.java:20)
at com.test.restdemo.domain.PostLink.hashCode(PostLink.java:11)
at java.util.AbstractSet.hashCode(AbstractSet.java:126)
at com.test.restdemo.domain.Post.hashCode(Post.java:20)
at com.test.restdemo.domain.PostLink.hashCode(PostLink.java:11)
at java.util.AbstractSet.hashCode(AbstractSet.java:126)
...............
Причина проста — при добавлении элемента в HashSet вычисляется его хэшкод посредством вызова соответствующего метода; реализация метода hashCode в Lombok вычисляет хэшкод для всех его полей, так что вызов PostLink#hashCode приводит к вызову Post#hashCode; он, в свою очередь вызывает Post.links#hashCode... который вызывает PostLink#hashCode. Вскоре это приведет к переполнению стека.
Решение проблемы — исключение циклических ссылок из методов hashCode, equals и toString. Это может быть реализовано средствами Lombok'а:
@Data
@EqualsAndHashCode(exclude = {"comments", "links"})
@ToString(exclude = {"comments", "links"})
@Entity
public class Post {
Или можно не лениться и написать реализацию hashCode и equals вручную.
2. Jackson
2.1 Циклические ссылки
Проблема с циклическими ссылками справедлива и для другой части приложения — сериализатора объектов в JSON.
@GetMapping("/{id}")
public Post getPost(@PathVariable("id") Long postId) {
return postRepository.findById(postId).orElse(null);
}
Как вы уже догадались, внешне безобидный код приводит к ошибке:
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"]->org.hibernate.collection.internal.PersistentBag[0]->com.test.restdemo.domain.Comment["author"]->com.test.restdemo.domain.User["comments"])
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:705)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
at com.fasterxml.jackson.databind.ser.BeanPropertyWriter.serializeAsField(BeanPropertyWriter.java:704)
at com.fasterxml.jackson.databind.ser.std.BeanSerializerBase.serializeFields(BeanSerializerBase.java:689)
at com.fasterxml.jackson.databind.ser.BeanSerializer.serialize(BeanSerializer.java:155)
.......................................
Сообщение об ошибке говорит само за себя — во время сериализации была обнаружена бесконечная рекурсия User.comments -> Comment.author -> User.comments...
Если вы не хотите создавать проекции, содержащие только необходимую информацию, то остаются следующие пути решения проблемы:
- @JsonIgnore — просто исключить одну из сторон циклической ссылки из результирующего JSON-объекта.
- @JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id") — позволит заменить повторяющиеся объекты "ссылками" на них (в данном случае — значением поля id).
2.2 FetchType.LAZY
Если внимательно вчитаться в предыдущий пункт, можно заметить, что Jackson попытался сериализовать свойства с FetchType.LAZY. Очевидно, что это не то поведение, которого мы ожидаем. Благо, Jackson можно сконфигурировать необходимым для нас образом, используя Hibernate4Module:
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate4</artifactId>
</dependency>
@Configuration
public class WebConfig {
@Bean
public Jackson2ObjectMapperBuilderCustomizer customJacksonBuilder() {
return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder
.modulesToInstall(new Hibernate4Module());
}
}
3. EntityGraph
Решив одну проблему, мы тут же столкнулись с другой. Как нам быть, если необходимо получить не только запись в блоге, но и комментарии к ней?
Можно, например, пройтись циклом for по всей коллекции комментариев, что приведет к их инициализации, а Jackson + Hibernate4Module успешно сериализует проинициализированные свойства.
@GetMapping("/{id}")
public Post getPost(@PathVariable("id") Long postId) {
final Optional<Post> postById = postRepository.findById(postId);
if (!postById.isPresent()) {
return null;
}
final Post post = postById.get();
for (Comment comment : post.getComments()) {
//fetch comment
}
return post;
}
Недостаток такого подхода очевиден — для инициализации коллекции будет создано N запросов к базе данных. Избежать этого можно с помощью Entity Graph, который сразу проинициализирует необходимые нам свойства.
@NamedEntityGraphs(
@NamedEntityGraph(name = "Post.withComments",
attributeNodes = @NamedAttributeNode("comments")
)
)
@Data
@EqualsAndHashCode(exclude = {"comments", "links"})
@ToString(exclude = {"comments", "links"})
@Entity
@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "id")
public class Post
В этом примере для сущности Post будет создан граф с названием "Post.withComments", содержащий узел comments.
Использование таких графов вместе с Spring Data JPA Repository предельно просто:
public interface PostRepository extends CrudRepository<Post, Long> {
@EntityGraph("Post.withComments")
Optional<Post> findById(Long id);
}