Spring 引导中双向一对多关系的 LazyInitializationException

LazyInitializationException on bi-directional one-to-many relationship in Spring Boot

以下 Spring 引导服务方法在尝试将 Comment 添加到 post.addComment(comment) 中的 Post 时抛出 Hibernate 的 LazyInitializationException :

    @Service
    public class CommentService {

        @Autowired
        private PostRepository postRepository;

        @Autowired
        private CommentRepository commentRepository;


//.....
        /**
         * Creates a new comment
         *
         * @param newCommentDto data of new comment
         * @return id of the created comment
         *
         * @throws IllegalArgumentException if there is no blog post for passed newCommentDto.postId
         */
        public Long addComment(NewCommentDto newCommentDto) {
            try {
                Post post = postRepository.findById(newCommentDto.getPostId()).get();

                Comment comment = new Comment();
                comment.setComment(newCommentDto.getContent());

                post.addComment(comment);
                comment = commentRepository.save(comment);
                return comment.getId();
            } catch (Exception e) {
                throw new IllegalArgumentException("There's no posts for given ID.");
            }

        }

实体映射如下:

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.OneToMany;

@Entity
public class Post {

    @Id
    @GeneratedValue
    private Long id;

    private String title;

    @Column(length = 4096)
    private String content;

    private LocalDateTime creationDate;

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public LocalDateTime getCreationDate() {
        return creationDate;
    }

    public void setCreationDate(LocalDateTime creationDate) {
        this.creationDate = creationDate;
    }

    public Long getId() {
        return id;
    }

    @OneToMany(
            mappedBy = "post",
            cascade = CascadeType.ALL,
            orphanRemoval = true
        )
    private List<Comment> comments = new ArrayList<>();


    public void addComment(Comment comment) {
        comments.add(comment);
        comment.setPost(this);
    }

    public List<Comment>getComments() {
        return this.comments;
    }



}

    import java.time.LocalDateTime;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;

import org.hibernate.annotations.OnDelete;
import org.hibernate.annotations.OnDeleteAction;

    @Entity
    public class Comment {

        @Id
        @GeneratedValue
        private Long id;

        private String comment;

        private String author;

        private LocalDateTime creationDate;

        @ManyToOne(fetch = FetchType.EAGER, optional = false)
        @JoinColumn(name = "post_id", nullable = false)
        @OnDelete(action = OnDeleteAction.CASCADE)
        private Post post;

        public Post getPost() {
            return post;
        }

        public void setPost(Post post) {
            this.post = post;
        }

        public Long getId() {
            return id;
        }

        public void setId(Long id) {
            this.id = id;
        }

        public String getComment() {
            return comment;
        }

        public void setComment(String comment) {
            this.comment = comment;
        }

        public String getAuthor() {
            return author;
        }

        public void setAuthor(String author) {
            this.author = author;
        }

        public LocalDateTime getCreationDate() {
            return creationDate;
        }

        public void setCreationDate(LocalDateTime creationDate) {
            this.creationDate = creationDate;
        }

    }

Comment 的 post 引用中 EAGERLAZYManyToOne 提取类型似乎没有什么区别。

我在这里做错了什么?如何解决这一错误?

根据@mckszcz 建议的更改,现在同一服务上的以下方法在尝试从 post:

获取评论时抛出一些反射异常
/**
 * Returns a list of all comments for a blog post with passed id.
 *
 * @param postId id of the post
 * @return list of comments sorted by creation date descending - most recent first
 */
public List<CommentDto> getCommentsForPost(Long postId) {
    List<Comment> comments = postRepository.getOne(postId).getComments();
    List<CommentDto> result = new ArrayList<>();
    comments.forEach(comment -> {
        result.add(new CommentDto(comment.getId(), comment.getComment(), comment.getAuthor(), comment.getCreationDate()));
    });
    return result;
}

根据方法的 javadoc 中的描述,需要将其更改为 return 属于 post 的所有注释的列表?

由于拥有方是 Comment here,您应该将 addComment 方法更改为:

 public Long addComment(NewCommentDto newCommentDto) {
            try {
                Post post = postRepository.findById(newCommentDto.getPostId()).get();

                Comment comment = new Comment();
                comment.setComment(newCommentDto.getContent());

                comment.setPost(post);
                comment = commentRepository.save(comment);
                return comment.getId();
            } catch (Exception e) {
                throw new IllegalArgumentException("There's no posts for given ID.");
            }

        }

原来除了重排addComment方法中的代码,根除LazyInitializationException(JPA因错误owning side属性而抛出的,如@mckszcz 指出):

/**
     * Creates a new comment
     *
     * @param newCommentDto data of new comment
     * @return id of the created comment
     *
     * @throws IllegalArgumentException if there is no blog post for passed newCommentDto.postId
     */
    public Long addComment(NewCommentDto newCommentDto) {
        try {
            Post post = postRepository.findById(newCommentDto.getPostId()).get();

            Comment comment = new Comment();
            comment.setComment(newCommentDto.getContent());
            comment.setAuthor(newCommentDto.getAuthor());


            comment.setPost(post);
            //post.addComment(comment);
            comment = commentRepository.save(comment);
            return comment.getId();
        } catch (Exception e) {
            throw new IllegalArgumentException("There's no posts for given ID.");
        }

    }

为了解决服务的 getCommentsForPost(Long postId) 方法中的反射 InvocationTargetException,还需要引入一个额外的查找器方法(允许通过包含父项的 ID 搜索多个子项) 到 CommentRepository:

@Repository
public interface CommentRepository extends JpaRepository<Comment, Long> {

    List<Comment> findByPostId(Long postId);

}

然后将该存储库周围的相应更改引入错误的方法中:

/**
 * Returns a list of all comments for a blog post with passed id.
 *
 * @param postId id of the post
 * @return list of comments sorted by creation date descending - most recent first
 */
public List<CommentDto> getCommentsForPost(Long postId) {
    List<Comment> commentsForPost = commentRepository.findByPostId(postId);
    //List<Comment> comments = postRepository.getOne(postId).getComments();
    List<CommentDto> result = new ArrayList<>();
    commentsForPost.forEach(comment -> {
        result.add(new CommentDto(comment.getId(), comment.getComment(), comment.getAuthor(), comment.getCreationDate()));
    });
    return result;
}

这两项措施似乎已经解决了问题。