[JPA, SpringBoot] KnockKnock 개발일지 -1229

Hyebin Lee·2021년 12월 29일
0

knockknock 개발일지

목록 보기
6/29
post-thumbnail
post-custom-banner

오늘의 목표

  1. ✔ 포스트와 해시태그 작성과 삭제 및 핵심기능 구현하기
  2. ✔ 포스트와 해시태그 작성과 삭제 및 핵심기능 테스트 코드 만들기
  3. 댓글 작성과 삭제 및 핵심기능 구현하기
  4. 댓글 작성과 삭제 및 핵심기능 테스트 코드 만들기

오늘의 이슈

  1. 관련된 게시글과 posthashTag가 모두 삭제된 hashTag가 삭제가 안됨
  2. 실행 시 DB 상에 이상 없는데 Test에서만 안되는 경우 (oneToMany 관계에서 Many쪽 하나를 지웠는데 One에서 조회해보면 여전히 그 지워진 데이터가 List에 남아있음)

1. 객체와 관계형DB 간의 혼동을 멈춰주세요

아직 JPA 개념이 익숙하지 않아서 그런지 자꾸 개발할 때 객체와 관계형 DB 간의 관계를 혼동한다. 오늘의 이슈였던 관련된 게시글과 posthashTag가 모두 삭제된 hashTag가 혼자 남아 삭제가 안되어있었던 것도 이와 관련되어있다.

말로 설명하기 힘들어서 일단 문제의 코드를 보자!

 public void delete(Long id){
        Post post = postRepository.findOneById(id);
        List<PostHashTag> postHashTags = post.getPostTags();
        List<HashTag> tags = new ArrayList<>();
        // 해당 posthashTag에 해당하는 hashTag list 가져오기, postHashTag는 삭제
        for(PostHashTag eachPostTag: postHashTags ){
           📌// 이 부분 주목!! 
            HashTag hashTag = eachPostTag.getHashtag();
            hashTagRepository.removePostHashTag(eachPostTag);
            if(hashTag.getPosthashtags().isEmpty()) hashTagRepository.removeHashTag(hashTag);
            📌
            hashTagRepository.removePostHashTag(eachPostTag);
        }
        // 포스트 삭제
        postRepository.remove(post);
    }

어느 부분이 문제인지 보이나요...?😇

hashTag 객체에 postHashTag에 해당하는 hashTag 객체를 가져오면 그 당시의 객체의 postHashTags List에는 해당 postHashTag가 그대로 담겨져 있다.
이후 removePostHashTag로 해당 postHashTag를 DB 상에서 지웠다 한들 hashTag 객체의 멤버변수인 postHashTags List 안의 postHashTag 객체는 여전히 살아있게 된다.
그러니 당연히 해당 객체의 getPosthashtags로 가져온 list는 empty 하지 않다.

DB에서 가져온 객체나 기존 메소드에서만 존재하는 객체들은 모두 객체이다.
각각의 멤버변수와 안에 담긴 내용은 DB상에서 중간에 변한다 한들 변하지 않는다. 이를 변화시키고 싶다면 다시 조회쿼리를 날려서 DB에서 새로 객체 정보를 가져오는 수밖에 없다.

DB는 반대로 트랜젝션과 커밋에 의해 때에 따라 수시로 객체의 내용이 DB에 반영되지만 DB의 내용이 객체에 실시간으로 반영되지 않는 다는 점을 늘 머릿속에 기억해두어야 한다.

  public void delete(Long id){
        Post post = postRepository.findOneById(id);
        List<PostHashTag> postHashTags = post.getPostTags();
        List<HashTag> tags = new ArrayList<>();
        // 해당 posthashTag에 해당하는 hashTag list 가져오기, postHashTag는 삭제
        for(PostHashTag eachPostTag: postHashTags ){
           if(eachPostTag.getHashtag().getPosthashtags().size()==1) hashTagRepository.removeHashTag(eachPostTag.getHashtag());
           hashTagRepository.removePostHashTag(eachPostTag);
        }
        // 포스트 삭제
        postRepository.remove(post);
    }

따라서 코드는 다음과 같이 수정할 수 있다.

2. 실행 시 DB에서는 이상 없이 잘 되는데 TEST에서 오류나는 코드

 @Test
   public void 댓글삭제(){
       //given
       List<Post> posts = postRepository.findByTag("영어");
       Post post = posts.get(0);
       CommentRequest commentRequest = new CommentRequest();
       commentRequest.setComment("this comment should be deleted");
       commentRequest.setPostId(post.getId());
       Comment thisComment = commentService.save(commentRequest);
       
       //when
       commentService.delete(thisComment.getId());
       
       //then
       List<Post> posts2 = postRepository.findByTag(<"영어");
       Post post2 = posts2.get(0);
       List<Comment> comments = post2.getPostcomments();
       Assertions.assertThat(comments.size()).isEqualTo(1);
   }

오류가 났던 문제의 코드이다.
RollBack 풀고 DB가서 확인해보면 분명히 해당 comment는 지워지고 없는데
이 test는 통과하지 못했다..

쿼리 로그 확인해보면 delete 쿼리도 잘 나갔고 comment는 삭제되었는데
왜 해당 포스트 객체에 comment가 그대로 남아있는 것일까? ㅎㅎ....😭


가설 1. 이미 영속성 컨텍스트에 들어온 해당 post는 다시 post2로 findByTag를 해도 DB에서 다시 가져와지지 않는다.
따라서 DB상에서 다시 조회한게 아니기 때문에 comment 객체는 DB 상에 없지만 객체로서 계속 살아있는것..

--> 근데 이거는 말이 좀 안되는게.... 애초에 delete 에서 @Transactional 옵션을 줘서 이미 트랜젝션과 커밋을 마쳤는데 영속성 컨텍스트 안에 남아있는 post data는 dirty checking 이 안되나?


원인은 모르겠지만 일단 해결방안은 찾았다.
나는 코드 짤 때 이제 원인은 못찾고 해결방안 먼저 찾는듯....ㅎㅎㅎㅎ
이게 옳은 방향은 아닌거 같지만 뭐 어쨌든 기록해둬야겠당

//@Transactional
   public void remove(Comment comment){
       em.remove(comment);
       em.flush();
   }

원인은 CommentRepository 에 있는 remove 함수에 적용된 Transactional 어노테이션이 동작하지 않아서였다.

Test를 실행했을 때, rollback 을 하면 delete 쿼리가 안나가고 rollback을 안하면 delete 쿼리가 나갔는데
rollback을 한 test 메소드는 마지막에 transaction이 적용되지 않은 채 끝나기 때문에 쿼리가 안나가게 되며 rollback을 안한 test 메소드는 마지막에 transaction을 해준다는 차이가 있어서 나타나는 현상이였다.

이 내용은 몰랐던 거라 나중에 이와 비슷한 문제가 생겼을 때 이 방법으로 transaction 부분에 문제가 있는지 확인해볼 수 있겠다 ❣

여튼 그래서 transactional 어노테이션을 빼고 em.flush()로 강제 플러시를 해주니까 영속성 컨텍스트 안에서만 적용되었던 remove가 DB에 반영되었고 TEST 도 통과하게 되었다.

한 가지 의문이였던 점은 flush를 여태까지 나는 단순히 쓰기지연 query를 DB에 날려주기만 하고 실행은 되지 않는 메서드로 알고 있었는데 그게 아니였다는 점이다. 대체 transaction과 commit 그리고 flush의 차이가 뭔지 점점 더 미궁속으로 빠져든다..........

여기서 제일 의문점은 왜 transactional 어노테이션이 동작하지 않는지이다. EntityManager 사용 방식에서 뭔가 오류가 있는건지, EntityManager가 정확히 어떤 것이며 어떻게 사용해야 하는지 내일 더 공부해서 정리하고 해당 문제를 해결해봐야겠다.

내일 추가로 봐야할 부분

  1. transaction, commit, flush의 정의와 차이 관계 명확히 공부
  2. EntityManager의 역할과 사용방법 명확히 정리하기
  3. @Transactional 이 동작하지 않았던 원인 찾기
post-custom-banner

0개의 댓글