pooney
Published 2024. 7. 20. 21:45
JPA - 1차캐시 동작 조건 JPA

 

 

 

안녕하세요 프로젝트르 진행하면서 JPA의 강력한 기능인 중 하나인 1차 캐시가 언제 동작하는 지 잘 모르는 고 사용하시는 분들이 존재해서 한번 작성하게 되었습니다. 1차 캐시이 무엇인지에 대한 설명은 많은 분들이 잘 작성하셨기 때문에 간단하게 설명하고 "언제 동작하는가?" 에대해서 설명을 하고자합니다. 

 

 

 

 

1차 캐시란?

 

 

JPA하면 1차 캐시를 빼놓을 수가 없는데요 성능적으로 많은 이점을 가지고 갈 수 있는 강력한 기능이라고 생각합니다. 

1차캐시는 영속성 컨텍스트에 Entity를 보관을 하는데 보관시 @Id를 기반으로 보관을 합니다.  때문에 JPA는 우선 1차캐시를 확인하여 해당 Entity가 있는지 여부를 확인 하고 있다면 캐시에서 가져다 사용을 하고 없다면 DB에 쿼리를 날려 해당 데이터를 가지고 오고 이것을 다시 1차캐시에 적재를 하게 됩니다. 때문에 쿼리를 여러번 요청 할 필요 없이 캐시에서 가져다 사용하니깐 성능적으로 많은 이점을 제공해 줍니다.  

 

 

 

 

 

 

 

하지만 많은 분들은 JPA를 사용하시면서 실수를 하시거나 잘 못 아시는 부분이 있는데요 그것은  "entity로 단순하게 조회하면 1차 캐시를 활용 할 수 있는 거 아니야? "  입니다. 

 

 

 

위에 보시면 @Id 를 기반으로 보관한다고 설명을 했는데요. 즉 PK를 기반으로 보관을 해놓았기때문에 적재할때는 상관 없지만 1차캐시에서 조회를 하고 싶은 경우 PK로 조회를 해야 원하는 결과를 볼 수가 있습니다

 

.  

 

즉 1차캐시에서 가져올때는 findById 로 조회를 해야합니다

 

 

 

 

 

그러면 간단한 예제를 진행해서 확인 해보 겠습니다.  간단하게 Member라른 엔티티가 있다고 가정하겠습니다. 

 

 

Member.class

@Getter
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "member")
public class Member {

    @Id
    @Column(name = "member_idx")
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long memberIdx;

    @Column(name = "member_id")
    private String memberId;

    @Column(name = "name")
    private String name;

    @Column(name = "rgdt")
    private LocalDateTime rgdt;
    
    @Column(name = "comment")
    private String comment;
}

 

 

 

 

 

 

그리고 추가적으로 아래 하나의 Row를 넣어줍니다. 

 

INSERT INTO member (member_idx, age, name, member_id, rgdt, status, comment, id) VALUES (1, 20, 'pooney', 'pooney_life', '2023-07-20 21:23:23', 1, null, null);

 

 

 

 

 

 

 

Junit 

@DisplayName("JPA 1차캐시에 데이터가 있는 경우(1차 캐쉬 활용 0)")
    @Transactional
    @Test
    public void jpa_1차캐시_1(){
        log.info("##jpa first query start");
        Optional<Member> byId = memberRepository.findById(1010L);
        log.info("##jpa first query end");
        log.info("##jpa second query start");
        Optional<Member> byId1 = memberRepository.findById(1010L);
        log.info("##jpa second query end");
    }


    @DisplayName("JPA 1차캐시에 데이터가 없는 경우(1차 캐쉬 활용 X)")
    @Transactional(readOnly = true)
    @Test
    public void jpa_1차캐시_2(){
        log.info("##jpa first query start");
        Optional<Member> byId = memberRepository.findById(999L);
        log.info("##jpa first query end");
        log.info("##jpa second query start");
        Optional<Member> byId1 = memberRepository.findById(999L);
        log.info("##jpa second query end");
    }

    @DisplayName("JPA 1차캐시에 findById를 사용하지 않는경우(1차 캐쉬 활용 X)")
    @Transactional(readOnly = true)
    @Test
    public void jpa_1차캐시_2_1(){
        log.info("##jpa first query start");
        Optional<Member> byId = memberRepository.findById(1L);
        log.info("##jpa first query end");
        log.info("##jpa second query start");
        Optional<Member> byId1 = memberRepository.findByMemberIdx(1L);
        log.info("##jpa second query end");
    }



    @DisplayName("기본키가 아닌 다른 필드로 조회하는 경우(1차 캐쉬 활용 X)")
    @Transactional(readOnly = true)
    @Test
    public void jpa_1차캐시_3(){
        log.info("##jpa first query start");
        Optional<Member> byId = memberRepository.findByMemberId("pooney_life");
        log.info("##jpa first query end");
        log.info("##jpa second query start");
        Optional<Member> byId1 = memberRepository.findByMemberId("pooney_life");
        log.info("##jpa second query end");
    }

    @DisplayName("기본키가 아닌 다른 필드로 조회하고 후에 기본키로 조회하는 경우(1차 캐쉬 활용 0)")
    @Transactional(readOnly = true)
    @Test
    public void jpa_1차캐시_4(){
        log.info("##jpa first query start");
        Optional<Member> byId = memberRepository.findByMemberId("pooney_life");
        log.info("##jpa first query end");
        log.info("##jpa second query start");
        Optional<Member> byId1 = memberRepository.findById(byId.get().getMemberIdx());
        log.info("##jpa second query end");
    }

    @DisplayName("리스트 조회후 기본키로 조회하는 경우(1차 캐쉬 활용 0)")
    @Transactional(readOnly = true)
    @Test
    public void jpa_1차캐시_5(){
        log.info("##jpa first query start");
        List<Member> list = memberRepository.findAll();
        log.info("##jpa first query end");
        log.info("##jpa second query start");
        Optional<Member> byId1 = memberRepository.findById(list.get(0).getMemberIdx());
        log.info("##jpa second query end");
    }

 

 

 

최대한 케이스를 작성해보려고 노력 했는데 요정도로 작성해 볼 수 있을 것 같습니다. 직접 실행 보시고 결과를 확인 해 보셨으면 좋겠습니다. 참고로 QueryDsl을 사용하는 경우도 마찬가지 입니다.  

 

 

이처럼 JPA의 1차캐시 기능을 대략적으로 아시는 분들이 많은데 정확히 어떻게 사용하고 어느 조건에 동작하는지 모르시는 분들을 위해 한번 작성을 해보았는데 도움이 되셨으면 좋겠습니다. 

 

'JPA' 카테고리의 다른 글

JPA - OneToOne관계 N+1문제  (0) 2022.04.24
JPA - find by 외래키(FK) 조회 방법  (9) 2021.08.16
profile

pooney

@pooney

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!