예외 발생 현황
Post를 삭제했을 때, Comment와 Like가 함께 삭제될 수 있도록
Post Entity에 Comment와 Like를 @OneToMany를 사용하여 cascade, orphanRemoval을 사용하여 엮었다.
Post Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Entity
@SQLDelete(sql = "UPDATE post SET deleted_at = current_timestamp WHERE id = ?")
@Where(clause = "deleted_at is null")
public class Post extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String body;
private String title;
@ManyToOne(fetch = LAZY)
@JoinColumn(name = "user_id")
private User user;
@OneToMany(mappedBy = "post", cascade = CascadeType.REMOVE, orphanRemoval = true)
List<Comment> comment;
@OneToMany(mappedBy = "post", cascade = CascadeType.REMOVE, orphanRemoval = true)
List<Likes> Likes;
...
}
Post Service, Delete
@Transactional
public void deletePost(Integer postId, String userName) {
User user = service.validateGetUserByUserName(userName);
Post post = service.validateGetPostById(postId);
service.validateCheckAdminAndEqualWriter(user, post);
postRepository.delete(post);
}
해당 엔티티를 가지고 Post 삭제 기능을 실행시켰을 때, 연결되어 있는 comment와 likes가 함께 이상 없이 삭제되는 것을 확인할 수 있었다.
프로젝트 요구사항을 거의 다 완료하고, 모든 것을 테스트하던 도중 이미 완성되어 있던 Post 수정 부분에서 갑자기 다음과 같은 예외가 발생..
HibernateException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance
본론
Hibernate에서 예외를 던지고 있었고, 해당 cascade가 있는 컬렉션에서 엔티티 인스턴스가 참조되지 않아서 생기는 이슈 같았다.
기존의 Modify Service 로직은 다음과 같다.
Post Service, Modify
@Transactional
public void modifyPost(Integer postId, PostModifyRequest request, String userName) throws SNSAppException {
User user = service.validateGetUserByUserName(userName);
Post post = service.validateGetPostById(postId);
service.validateCheckAdminAndEqualWriter(user, post);
postRepository.save(request.toEntity(postId, post.getUser()));
}
구글링을 했을 때 연결되어있는 엔티티들을 clear 해보라는 글을 보았고, 그대로 시도.
@Transactional
public void modifyPost(Integer postId, PostModifyRequest request, String userName) throws SNSAppException {
...
post.getComment().clear();
post.getLikes().clear();
postRepository.save(request.toEntity(postId, post.getUser()));
}
결과는 똑같았다.
그러던 와중, 새로운 엔티티를 만들어 save(update)를 할 경우 "참조가 끊길 수 있다"라는 글을 보게 되었다.
Request DTO의 toEntity() 메서드를 사용하여 save 하는 로직 자체가 새로운 객체를 통해 저장하는 로직이다 보니, "참조가 끊길 수 있다"라는 추측이 맞다는 판단이 들었다.
해결과정
1. Comment와 Likes 엔티티를 만들어 post를 매개체로 각각 정보를 받아와야 했으므로 해당 쿼리를 날려줄 메서드를 작성했다.
public interface LikesRepository extends JpaRepository<Likes, Integer> {
...
List<Likes> findAllByPost(Post post);
}
public interface CommentRepository extends JpaRepository<Comment, Integer> {
...
List<Comment> findAllByPost(Post post);
}
2. 받아온 Like와 Comment를 toEntity()의 매개변수에 추가해야 한다.
...
public class PostModifyRequest {
private String title;
private String body;
public Post toEntity(Integer postId, User user, List<Comment> comment, List<Likes> likes) {
return Post.builder()
.id(postId)
.title(this.title)
.body(this.body)
.user(user)
.comment(comment) // 추가
.likes(likes) // 추가
.build();
}
}
3. Repository에 만든 메서드를 사용하여 받아온 Comment와 Like를 toEntity()에 함께 넘겨 save(update)를 진행한다
@Transactional
public void modifyPost(Integer postId, PostModifyRequest request, String userName) throws SNSAppException {
User user = service.validateGetUserByUserName(userName);
Post post = service.validateGetPostById(postId);
service.validateCheckAdminAndEqualWriter(user, post);
List<Comment> comment = commentRepository.findAllByPost(post);
List<Likes> likes = likesRepository.findAllByPost(post);
postRepository.save(request.toEntity(postId, post.getUser(), comment, likes));
}
참조