The following Spring Boot service method throws a Hibernate's LazyInitializationException
when trying to add a Comment
to a Post
in post.addComment(comment)
:
@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.");
}
}
The entities are mapped as follows:
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;
}
}
The ManyToOne
fetch type of EAGER
or LAZY
on the Comment
's post reference doesn't seem to make a difference.
What am I doing wrong here? How does one resolve this error?
With the change suggested by @mckszcz, now the following method on the same service throws some reflection exception when trying to get the comments from a 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;
}
What does that need to be changed into to return a list of all comments belonging to a post as described in the method's javadoc?
Turns out that in addition to rearranging the code in the addComment
method to eradicate the LazyInitializationException
(thrown by JPA due to the wrong owning side
property, as pointed out by @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.");
}
}
in order to resolve the reflective InvocationTargetException
in the getCommentsForPost(Long postId)
method of the service, one needs to also introduce an extra finder method (allowing to search multiple children by an ID of their containing parent) to the CommentRepository
:
@Repository
public interface CommentRepository extends JpaRepository<Comment, Long> {
List<Comment> findByPostId(Long postId);
}
and then introduce corresponding changes around that repository into the faulty method:
/**
* 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;
}
Those two measures seem to have resolved the issues.