Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

영속성 컨텍스트로 인해 발생하는 issue 본문

JPA/Trouble shooting

영속성 컨텍스트로 인해 발생하는 issue

Seung__ 2021. 7. 11. 23:40

영속성 컨텍스트가 가지고 있는 캐시를 이용하여  DB와 불일치하는 문제 자주있음.

 

1.  local date time 의 scale


 

auto DDL에서 local date time은 기본적으로 localDateTime(6)으로 생성한다.

 

하지만 실제로 auto ddl을 통해 db를 생성하는 일은 거의 없다.

 

이미 만들어진 DB에 JPA를 맵핑하여 사용한다. 만약 DB의 date time의 sacle이 6가 아니라면..?

 

 

<Commnet class>

 

Column class에 commentedAt을 만들고

 

DB에서 1초단위 까지 입력되도록 해당 column에 columnDefinition = "datetime"을 입력해준다.

@Column(columnDefinition = "datetime") //초단위가지만
private LocalDateTime commentedAt;

<Test>

    @Test
    @Transactional
    void commentTest(){

        Comment comment = commentRepository.findById(3L).get();

        comment.setCommentedAt(LocalDateTime.now());

        commentRepository.save(comment);

        //entityManager.clear();

        System.out.println(commentRepository.findById(3L).get());
    }

마지막 findbyid는 cache에서 값을 가져오게되고, cache에 저장돼있는 localDateTime(6) scale을 가져온다.

Comment(super=BaseEntity(createdAt=2021-07-11T22:52:04.174608800, 
updatedAt=2021-07-11T22:52:04.174608800), id=4, comment=오우야, 
ommentedAt=2021-07-11T22:52:04.101114200)

하지만 save 이후에 entity manger를 통해 clear를 한다면..? DB에서 값을 불러온다.

 

DB에는 초단위까지 저장되었고, 해당 값 그대로 불러온것을 볼 수 있다.

Comment(super=BaseEntity(createdAt=2021-07-11T23:06:58.560014, updatedAt=2021-07-11T23:07:01.553367), id=3, 
comment=오히려안좋아, commentedAt=2021-07-11T23:07:01)

 

즉! cache에 저장된 값과 DB에 저장된 값이 다른 경우이다!

 

그리고 Comment calss는 @EqualsAndHashCode가 적용되어있다.

@EqualsAndHashCode(callSuper = true)
public class Comment extends BaseEntity{

따라서 commentedAt도 동일성 비교 대상이다.

 

하지만 cache를 조회하는 것과 DB를 조회하는 것이 다를 수 있다. 이로 인해 경우에 따라 동일성이 true, false일 수도..

 

 

2. 두 번째 사례


 

<comment class> columnDefinition에 "datetime(6) default now(6)"입력해준다.

만약 default를 따로 정해주지 않으면, 현재 time stamp값을 자동으로 입력한다.

@Column(columnDefinition = "datetime(6) default now(6)") //초단위가지만
    private LocalDateTime commentedAt;

<Test> comment객체를 새로 만들고 DB에 반영시킨 뒤 findAll을 통해 불러와보자.

    @Test
    @Transactional
    void commentTest(){

        Comment comment = new Comment();
        comment.setComment("ㅗㅜㅑ");
       // comment.setCommentedAt(LocalDateTime.now());

        commentRepository.saveAndFlush(comment);

     //   entityManager.clear();

        //System.out.println(comment);
        //System.out.println(commentRepository.findById(3L).get());
        commentRepository.findAll().forEach(System.out::println);
    }

columnDefinition에 의해 now(6)가 입력되어야 하는데, DB에 null로 들어가있다..?

Comment(super=BaseEntity(createdAt=2021-07-11T23:18:07.127096500, updatedAt=2021-07-11T23:18:07.127096500),
id=4, comment=ㅗㅜㅑ, commentedAt=null)

 

insert query가 실행될 때 commented_at에 null이 들어간 것 같다.

즉  column definition에서 지정한 default값이 아닌 null이 입력되었다.

Hibernate: 
    insert 
    into
        comment
        (created_at, updated_at, comment, commented_at, review_id) 
    values
        (?, ?, ?, ?, ?)

 

jpa에서 모든 column을 insert column으로 사용을 하도록 되어있어서 발생한 문제이다.

 

Commnet class에 @DynamicInsert annotation을 추가하자.

@DynamicInsert
public class Comment extends BaseEntity{

 

@DynamicInsert : insert 시 null인 필드를 제외한다.

 

이 경우 comentedAt은 comment객체 생성 후 set을 통해 입력하지 않았으니 null상태이다.

따라서 @DynamicInsert에 의해 commentedAt은 제외될 것이다.   

 

예상한 대로 insert query에서 commented_at이 제외되었다!

Hibernate: 
    insert 
    into
        comment
        (created_at, updated_at, comment) 
    values
        (?, ?, ?)

 

<Test> 다시 한 번 돌려보자. comment객체를 만들고 setCommentedAt을 실행하지 않닸다.

saveAndFlush를 통해 DB에 반영은 되나 cache가 생성된다.

cache에는 commendtedAt에 null이 입력되고, DB에는 columnDefinition에 의해 now(6)가 입력될 것이다.

    @Test
    @Transactional
    void commentTest(){

        Comment comment = new Comment();
        comment.setComment("ㅗㅜㅑ");
       // comment.setCommentedAt(LocalDateTime.now());

        commentRepository.saveAndFlush(comment);

        //entityManager.clear();

        //System.out.println(comment);
        //System.out.println(commentRepository.findById(3L).get());
        commentRepository.findAll().forEach(System.out::println);
    }

실행 결과 commentedAt에 null이 입력되었고, cache에서 값을 가져온 것을 알 수 있다.

Comment(super=BaseEntity(createdAt=2021-07-11T23:32:33.083116200, updatedAt=2021-07-11T23:32:33.083116200), id=4,
comment=ㅗㅜㅑ, commentedAt=null)

이 번에는 entityManager를 중간에 clear 해보자.

cache가 삭제되었기 때문에, DB에서 값을 가져오게된다.

Comment(super=BaseEntity(createdAt=2021-07-11T23:31:31.462721, updatedAt=2021-07-11T23:31:31.462721), id=4, 
comment=ㅗㅜㅑ, commentedAt=2021-07-11T23:31:31.525659)

 

 

cache가 존재함으로서 DB값과 entity간에 불일치가 발생하는 경우이다.

 

 

실제 값과 cache간에 이격이 발생하지 않도록 cache를 관리해야한다.

 

영속성 cache가 save, find 전후로 어떻게 작동하는지 주의깊에 보자!

 

 

3. GitHub : 210711 DB & Cache 불일치 사례

 

bsh6463/BookManager

Contribute to bsh6463/BookManager development by creating an account on GitHub.

github.com

 

'JPA > Trouble shooting' 카테고리의 다른 글

Dirtycheck, 성능이슈  (0) 2021.07.12
JPA Fetch type, N + 1 이슈  (0) 2021.07.11
Comments