null

REST сервис с использованием Spring MVC

В ходе разработки 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...

Если вы не хотите создавать проекции, содержащие только необходимую информацию, то остаются следующие пути решения проблемы:

  1. @JsonIgnore — просто исключить одну из сторон циклической ссылки из результирующего JSON-объекта.
  2. @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);
}

 

Назад