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 引用中 EAGER
或 LAZY
的 ManyToOne
提取类型似乎没有什么区别。
我在这里做错了什么?如何解决这一错误?
根据@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;
}
这两项措施似乎已经解决了问题。
以下 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 引用中 EAGER
或 LAZY
的 ManyToOne
提取类型似乎没有什么区别。
我在这里做错了什么?如何解决这一错误?
根据@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;
}
这两项措施似乎已经解决了问题。