본문 바로가기

강의/자바 ORM 표준 JPA

JPA, 영속성 컨텍스트의 이점 (1차 캐시)

영속성 컨텍스트에 대해서 명확하게 알게되면 JPA가 내부적으로 어떻게 돌아가는지 알 수 있다.

앞서 영속성 컨텍스트는 EntityManager을 이용할 수 있는 공간이라고 하였다.

 

영속성 컨텍스트를 사용하면 5가지의 이점을 얻을 수 있다.

1차 캐시

• 동일성(identity) 보장

• 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)

• 변경 감지(Dirty Checking)

• 지연 로딩(Lazy Loading)

 

1차 캐시 ( db 조회 횟수를 줄일 수 있다)


jpa 영속성 컨텍스트는 어플리케이션과 database 사이에 존재한다.

중간에 존재하면서 얻는 이점 중 한가지인 1차 캐시에 대해 알아보자.

캐시라는 말에서 알 수 있듯이 데이터의 정보를 잠시 저장하고 있는 역할을 해주기도 한다.

 

영속성 컨텍스트에 저장

primary key가 member1인 Member 객체를 생성해 봅니다.

 

Member member = new Member("member1");

 

entityManager를 통해 영속성 컨텍스트에 member를 저장합니다.

 

em.persist(member);

 

primary key는 영속성 컨텍스트에서 key값으로 저장이 되고 value값으로 Member 객체가 저장이 됩니다.

 

영속성 컨텍스트에서 조회

앞서 영속성 컨텍스트에 Member라는 객체를 저장하였습니다.

이 후에 Member라는 객체를 DB에서 조회하려는 시도가 나타난다면, 이 Member 객체는 영속성 컨텍스트에 저장이 되어 있는 값이기 때문에 db에 쿼리를 날리지 않고 영속성 컨텍스트에 저장되어 있는 값을 반환해 줍니다.

 

Member findMember = em.find(Member.class, "member1");

 

아래와 같이 말이죠

 

 

 

영속성 컨텍스트에 없는 것을 조회할 경우 


1차 캐시에 존재하지 않는 것을 조회할 경우, db에서 값을 조회한 후 1차 캐시에 저장을 하고 반환하는 과정을 거친다.

 

 

진짜 1차 캐시에서 저장을 할까?


정말 1차 캐시에서 저장을 하고 그 값을 반환하는지 코드로 한번 살펴보겠습니다.

 

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpaMain {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();
        // code

        // 비영속
        Member member = new Member();
        member.setId(101L);
        member.setName("HelloJPA2");

        // 영속 (1차 캐시에 저장하는 상태, database에는 아직 값이 저장된 상태가 아니다.)
        System.out.println("=== BEFORE ===");
        em.persist(member);
        System.out.println("=== AFTER ===");

        // database에 저장되지 않은 상태임에도 불구하고, 현 상태에서 member를 찾아와 검색할 수 있는건
        // 1차 캐시에서 정보를 가져오기 때문이다.
        Member findMember = em.find(Member.class, 101L);
        System.out.println("findMember id = " + findMember.getId());
        System.out.println("findMember name = " + findMember.getName());

        // commit 시점에 query를 넘김 (데이터 베이스에 저장)
        tx.commit();
        em.close();
        emf.close();
    }
}

 

위 코드를 설명해보겠다.

Member라는 객체의 값을 설정하고 (비영속 상태)

entityManager를 이용하여 1차 캐시안에 member를 저장해두자 (영속 상태)

em.persist 명령어 위 아래로 "BEFORE", "AFTER"를 출력하도록 세팅해 놓았다.

방금 말했듯이 em.persist라는 명령어는 1차 캐시에 저장해 놓는 상태이지 database에 값을 저장하는 것은 아니다.

프로그램을 실행하면 아래와 같은 출력을 확인할 수 있다.

 

 

"BEFORE", "AFTER"가 모두 출력된 뒤 query를 날려 database에 저장하는 것을 확인할 수 있다.

database에 값을 저장하지도 않았는데

member를 찾고, memberID, memberName을 출력할 수 있었던 이유는 바로 1차 캐시안에 값을 저장해 놓았기 때문이다.

 

 

두번째 예시이다.

이미 id 101을 가지고 있는 Member라는 객체가 database에 저장되어 있는 상태이고 우리는 동일한 객체를 세번 찾아볼 것이다.

3번의 query를 날려 database에서 객체 정보를 받아오는 것이 일반적이겠지만

1번의 query만 날아가는 것을 확인할 수 있다.

 

첫번째 query를 날려 database에서 객체를 찾아오면, 영속성 컨텍스트에 그 정보를 임시 저장을 해 놓는다.

이후에 동일한 객체를 찾는다면, query를 날려 database에서 찾아오는 대신에 1차 캐시에서 정보를 받아온다.

 

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;

public class JpaMain {

    public static void main(String[] args) {
        EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

        EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        tx.begin();

        // code
        // 이미 id 101을 가지고 있는 Member라는 객체가 database에 저장되어 있는 상태이다.
        // database에서 member를 찾아보자.
        // select query는 1번만 날릴 것이다. 
        // 101L 아이디를 가진 Memeber를 찾은 다음에 영속성 컨텍스트 1차 캐시에 해당 정보를 저장해 놓을 것이고
        // 동일한 객체를 찾을때는 db가 아닌 캐시에서 우선적으로 찾기 때문에 query를 날릴 필요가 없다.
        Member findMember1 = em.find(Member.class, 101L);
        Member findMember2 = em.find(Member.class, 101L);
        Member findMember3 = em.find(Member.class, 101L);

        tx.commit();
        em.close();
        emf.close();
    }
}

 

1차 캐시를 이용함으로 인해 DB 조회 횟수를 줄여주기는 하지만

성능면에서 이점을 얻는 것 보다 객체지향적으로 코드를 작성하는 부분에 있어서 컨셉이 주는 이점이 더 크다고한다.

이 부분에 대해서는 다음에 더 알아보도록 하겠다.