본문 바로가기
JPA

find 메서드와 1차 캐시의 관계

by sangyunpark99 2025. 2. 4.
"find() 메서드는 언제 select 쿼리문이 나가는걸까?"

 

이번 글에서는 JPA에서 데이터를 조회할 때 사용되는 find() 메서드와 1차 캐시(영속성 컨텍스트)의 관계에 대해 알아보겠습니다.

 

find() 메서드는 어떤 원리로 실행될까요?

 

find() 메서드 동작 원리

JPA의 find()메서드는 기본적으로 1차 캐시(영속성 컨텍스트)를 먼저 확인한 후, 데이터가 있으면 1차 캐시에서 데이터를 바로 가져오고,  1차 캐시에 데이터가 없는 경우 DB에 select 쿼리를 사용해 데이터를 조회하는 방식으로 동작합니다.

 

그림으로 표현

 

1차 캐시는 무엇인가요?

 

1차 캐시는 영속성 컨텍스트(Persistence Context) 내부에 존재하는 메모리 캐시입니다. 

JPA의 EntityManager는 조회된 엔티티를 1차 캐시에 저장하고, 이후 같은 데이터를 요청하면 DB가 아닌 1차 캐시에서 반환합니다. 

 

1차 캐시는 왜 필요한가요?

 

1차 캐시는 불필요한 DB 조회를 방지하여 성능을 최적화하는 중요한 역할을 합니다.

 

find 메서드 테스트

find 메서드가 앞서 소개했던 동작 원리대로 동작하는지 테스트 코드를 통해 확인해보겠습니다.

2가지 상황을 테스트해보도록 하겠습니다.  테스트 상황은 다음과 같습니다.

1. find() 메서드 호출시, 1차 캐시에 데이터가 존재하는 경우
2. find() 메서드 호출시, 1차 캐시에 데이터가 존재하지 않는 경우

 

사용되는 User 엔티티 코드는 다음과 같습니다.

package com.example.spring_study_test.entity;

import jakarta.persistence.*;

@Entity
@Table(name = "user")
public class UserEntity {

    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;

    protected UserEntity() {

    }

    public UserEntity(String name) {
        this.name = name;
    }

    public Long getId() {
        return this.id;
    }
}

 

이제, User 엔티티를 사용해서 각 상황별 테스트 결과를 통해 동작 원리가 맞는지 확인해보겠습니다.

 

1차 캐시에 데이터가 존재하는 경우

@SpringBootTest
@Transactional
public class findTest {

    @PersistenceContext
    EntityManager em;

    @Test
    void givenOneUser_whenFindWithCache_thenExecutesSelectQuery() throws Exception{

        //given
        UserEntity user = new UserEntity("김민수");
        em.persist(user);

        //when
        UserEntity findUser = em.find(UserEntity.class,user.getId());

        //then
        Assertions.assertEquals(user.getId(), findUser.getId());
    }
}

 

 

 

위에 명시된 테스트 코드에서 persist()를 호출하면, 엔티티는 JPA의 1차 캐시에 저장됩니다.
하지만, IDENTITY 전략을 사용했기 때문에 JPA는 즉시 INSERT 쿼리를 실행하여 DB에서 자동 생성된 ID 값을 가져옵니다.

 

1차 캐시에 데이터가 저장되어 있다면, 정말 SELECT 쿼리가 실행되지 않을까요?

 

실행 결과는 다음과 같습니다. 

insert 쿼리문만 실행되고, select 쿼리문은 실행되지 않음을 확인할 수 있습니다.

 

 

1차 캐시에 데이터가 존재하지 않는 경우

import com.example.spring_study_test.entity.UserEntity;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.transaction.annotation.Transactional;

@SpringBootTest
@Transactional
public class findTest {

    @PersistenceContext
    EntityManager em;

    @Test
    void givenOneUser_whenFindWithNoCache_thenExecutesSelectQuery() throws Exception{

        //given
        UserEntity user = new UserEntity("김민수");
        em.persist(user);
        
        em.clear();

        //when
        UserEntity findUser = em.find(UserEntity.class,user.getId());

        //then
        Assertions.assertEquals(user.getId(), findUser.getId());
    }
}

 

위에 명시된 테스트 코드에서는 em.clear()를 호출하여 1차 캐시를 초기화합니다.
쉽게 말해, 1차 캐시를 초기화한다는 것은 1차 캐시에 존재하던 모든 데이터를 삭제하는 과정입니다.

 

clear()메서드는 정말 1차캐시를 초기화 해줄까요?

이를 직접 확인해보기 위해 JPA의 contains(Object entity) 메서드를 활용해 보겠습니다.
이 메서드는 특정 엔티티가 현재 영속성 컨텍스트(1차 캐시)에 존재하는지 여부를 확인하는 기능을 합니다.

 

테스트 코드는 다음과 같습니다.

@Test
void givenOneUser_whenEntityManagerClear_thenCacheIsCleared() {
    //given
    UserEntity user = new UserEntity("김민수");
    em.persist(user);

    em.clear();

    Assertions.assertFalse(em.contains(user));
}

 

데이터가 존재하지 않는다면, em.cotains(Object obejct) 메서드는 false를 반환합니다.

 

실행 결과는 다음과 같습니다.

clear()라는 메서드를 통해 1차 캐시가 초기화됨을 확인할 수 있습니다.

 

1차 캐시에 데이터가 저장되어 있지 않으니, 정말 SELECT 쿼리가 실행될까요?

 

실행 결과는 다음과 같습니다.

insert 쿼리문이 실행되고 난후, select 쿼리문도 실행됨을 확인할 수 있습니다.

 

 

정리

find메서드가 호출할때, 1차 캐시에 데이터가 저장되어 있는경우엔 DB에서 데이터를 가져오지 않고,

1차 캐시에 데이터가 저장되어 있지 않은 경우엔 DB에서 select쿼리문을 통해 데이터를 가져옵니다.

 

'JPA' 카테고리의 다른 글

JPA 낙관적 락  (0) 2025.02.06
JPA와 읽기 전용 쿼리  (0) 2025.02.05
IDENTITY 식별자 생성 전략과 persist()의 관계  (0) 2025.02.03
동시성 문제 해결방법(Update Query편)  (0) 2025.01.29
save()와 SELECT의 관계  (0) 2025.01.26