JPA

[ JPA ] 프로젝트 도중 만난 에러 ( feat. 트랜잭션 )

인생은단짠단짠 2023. 1. 12. 19:16

요즘 그룹스터디에서 사이드 프로젝트를 진행중이다. 간단한 지역 모임 구하기 커뮤니티인데 나는 현재 댓글 기능을 구현하고 있다. JPA를 공부한 이후로 처음 활용해보는 프로젝트라서 그런지 시행착오로부터 배우고 느끼는게 많다... ㅎㅎ

그 중에서 기억에 남는 cascade와 트랜잭션에 대해 기록을 남겨보려고 한다.

 

 


상황

 

대댓글 기능을 구현하고 있었다. Comment Entity에 레벨, 그룹등의 필드를 추가하여 대댓글을 구현하는 방법이 있지만, 나는 JPA의 특성을 적극 활용하여 대댓글을 만들고 싶은 마음에 연관관계를 통해 대댓글을 구현하고자 했다. 

 

 

그래서 Comment Entity에 아래와 같이 parentCommentId, childComments 컬럼을 추가했다.

 

Comment

@Column(updatable = false)
@Setter
private Long parentCommentId;

@ToString.Exclude
@OrderBy("createDate ASC ")
@OneToMany(mappedBy = "parentCommentId", cascade = CascadeType.PERSIST)
private Set<Comment> childComments = new LinkedHashSet<>(); //순서대로 저장하기 위해서

본인을 매핑하는 방식인데 이는 다른사람의 깃허브 코드를 참고했다. 

 

 

그리고 부모 댓글의 id를 통해 부모 엔티티를 찾고, 그 엔티티의 childComment에 댓글을 추가하는 방식으로 DB에 저장하고 싶었기 때문에 CascadeType을 PERSIST로 설정하고,  addChildComment 메서드를 추가했다. 

public void addChildComment(Comment child) {
    child.setParentCommentId(this.getId());
    this.getChildComments().add(child);
}

 


 

다음은 Service의 comment를 저장하는 메서드이다.

 

Comment Save 메서드 

public String saveComment(CommentDTO dto) {

    Post post = postRepository.findById(dto.getPostId()).orElse(null);
    User user = userRepository.findById(dto.getUserDTO().getId()).orElse(null);

    Comment comment = dto.toEntity(user, post, dto.getContent());
    if (dto.getParentCommentId() == null) {
        commentRepository.save(comment);
    } else {
        Comment parentComment = commentRepository.findById(dto.getParentCommentId()).orElse(null);
        if (parentComment != null) {
            parentComment.addChildComment(comment);
            log.info("{}", parentComment.getChildComments());
        }

    }
}

현재 들어온 댓글 dto에 부모comment id가 없으면 그냥 저장하고, 있으면 그 부모 comment를 찾아서 addChildComment를 통해 현재 댓글도 영속화 할 수 있게 했다. 

 


 

문제 상황

그런데 대댓글을 등록하려고 보니 작성한 대댓글이 DB에 들어가지 않았다. 

분명히 parentComment.addChildComment(comment); 가 실행되고, 로그에도 들어온 대댓글이 childComments라고 찍히는데 DB에는 반영되지 않았다. 

 

childComment 컬럼은 DB에서 확인되지도 않고, 나는 이를 처음 경험해봤기 때문에 어디서 문제가 생긴건지 가늠하기 어려웠다. 

 

 

 

해결 과정

코드를 살펴보다 대댓글이 영속화 되기만 하고, DB에 날라가는 로직이 없음을 깨달았다. 

parentComment 라는 이미 영속화된 객체에 변화가 일어났는데 이 변화를 감지하기만 하고 커밋을 시켜주지 않았다.

갑자기 문제가 update(더티체킹) 문제로 바뀌었고 @Transactional을 붙여줘야 함을 알 수 있었다. 트랜잭션의 커밋 시점에 영속성 컨텍스트에 있는 정보들이 DB에 쿼리로 날라가서 대댓글이 DB에 반영될 것이다. 

 

따라서 saveComment 메서드에 @Transactional을 걸어줘야 cascade가 제대로 동작하는 것이다. 

 

 


 

실험

( @Transactional 을 붙이지 않고 save를 통한 반영도 되겠지? )

 

트랜잭션을 붙이지 않고 parentComment를 save 해보았다.

public String saveComment(CommentDTO dto) {
        Post post = postRepository.findById(dto.getPostId()).orElse(null);
        User user = userRepository.findById(dto.getUserDTO().getId()).orElse(null);

        Comment comment = dto.toEntity(user, post, dto.getContent());
        if (dto.getParentCommentId() == null) {
            commentRepository.save(comment);
        } else {
        Comment parentComment = commentRepository.findById(dto.getParentCommentId()).orElse(null);
        if (parentComment != null) {
            parentComment.addChildComment(comment);
            commentRepository.save(parentComment); //추가한 라인
        }
	}
}

save 메서드 안에는 @Transactional이 들어있으니 parentComment를 save하는 것만으로 대댓글이 DB에 반영되지 않을까 하는 생각에서였다.

 

그러나....그렇지 않았다. 

 

그 이유는 parentComment는 이미 DB에 등록된 엔티티였기 때문이다.

save메서드는 새로운 엔티티이면 persist를, 아니면 merge를 호출한다.

 

그런데 나는 cascade = CascadeType.PERSIST만 설정해놓았다. 따라서 아래와 같이 CascadeType.MERGE를 추가했더니  commentRepository.save(parentComment); 를 호출했을 때 대댓글이 DB에 제대로 반영 되었다. 

 

Comment

@OneToMany(mappedBy = "parentCommentId", cascade = {CascadeType.PERSIST,CascadeType.MERGE})
private Set<Comment> childComments = new LinkedHashSet<>();

 


 

정리하면, cascade는 Entity 변화를 연관관계를 가진 Entity에 전파시키는 것을 목적으로 한다. 이 때 Entity 변화를 DB에 날려주려면 트랜잭션은 필수적이다.

따라서 cascade를 동작시키고 싶은 메서드 위에는 반드시 @Transactional을 붙여주도록 하자!

사실 Service 메서드 도중 에러가 발생했을 때 전에 실행되었던 SQL 쿼리들이 다 rollback되어야 하기 때문에 되도록 대부분의 메서드에 @Transactional 어노테이션을 붙여주는 것이 좋다.

 

 

 


 

느낀점

내가 경험이 적다 보니 비즈니스 로직 구현에 집중하여 트랜잭션을 신경쓰지 못했다... ㅎㅎ

공부한건 공부한거고... 실전에서 사용할때는 또 다른것임을 느낀다. 여러가지가 결합되서 사용되다보니 놓치게 되는 것들이 많은 듯하다.. ㅎㅎ  앞으로는 이에 주의해서 코드를 작성해 나가야 겠다! 

 

'JPA' 카테고리의 다른 글

[ JPA ] 사이드 프로젝트 중 만난 문제 (연관관계)  (0) 2023.02.03
[ Jpa ] 연관관계  (0) 2023.01.06
[ Jpa ] 영속성 컨텍스트  (0) 2022.12.08