본문 바로가기

JPA

영속성 컨텍스트의 장점

package hellojpa;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

@Entity
@Table(name = "MEMBER")
public class Member {
    @Id
    private Long id;
    private String name;

    public Member(Long id, String name) {
        this.id = id;
        this.name = name;
    }

    protected Member() {

    }

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

Member.java

package hellojpa;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
import java.util.List;

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

        EntityTransaction tx = em.getTransaction();
        tx.begin(); //트랜잭션 시작

        try {
            //핵심 로직 부분
            tx.commit();
        } catch (Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
        emf.close();
    }
}

JpaMain.java

아래 쪽에 나오는 JAVA 코드들은 위 코드에서 //핵심 로직 부분에 작성하시면 됩니다.

구체적인 설정이 궁금하신 분들은 아래 링크를 참고해주세요.

https://code-mania.tistory.com/7


1차 캐시 구조 알아보기

//핵심 로직 부분에 작성
Member member1 = new Member(1L, "code-mania");
Member member2 = new Member(2L, "code-lover");
em.persist(member1);
em.persist(member2);

persist 메서드는 영속성 컨텍스트에 Entity 객체를 저장해준다고 했다. 내부적으로 어떻게 저장되는 것일까??

 

영속성 컨텍스트와 1차 캐시

위 그림처럼 영속성 컨텍스트 안의 1차 캐시에 저장된다.

마치 map처럼 @Id 어노테이션이 붙은 필드(여기서는 Member의 Long id)가 key로 저장되고 Value로는 해당하는 Entity가 들어가게 된다.

과연 1차 캐시를 통해 어떤 이점을 얻을 수 있을까?


동일성 보장과 성능 향상

//핵심 로직 부분에 작성(이전 코드는 지우기)
Member findMember1 = em.find(Member.class, 1L);
Member findMember2 = em.find(Member.class, 1L);
System.out.println("result = " + (findMember1==findMember2)); //true

성능 향상

find 메서드는 먼저 1차 캐시에서 해당 id를 가진 Entity를 찾는다.

그런데 1차 캐시에는 1이라는 id를 가진 Entity가 없다. 따라서 DB에 select 쿼리를 날려서 가져온 값을 Entity에 매핑시킨다.
그리고 이 Entity는 반환되기 전에 '1'이라는 id를 key로 가지고 1차 캐시에 저장된다.
그렇다면 findMember2는 어떻게 될까?

"find 메서드는 먼저 1차 캐시에서 해당 id를 가진 Entity를 찾는다."라고 첫 줄에서 말했듯

1차 캐시에서 1이라는 id를 가진 Entity를 찾을 것이고 이번에는 있을 것이다. 그러면 DB와의 통신없이 Entity를 가져올 수 있게 된다.

즉, 통신 비용을 아낄 수 있는 것이다. console을 확인해보면 select 쿼리가 2번이 아닌 1번만 날아가는 것도 확인할 수 있다.

(참고로 persist로 Entity를 1차 캐시에 저장한 경우에도 select 쿼리를 날리지 않고 캐시에서 가져온다.)

동일성 보장

그러면 이렇게 가져온 findMember1과 findMember2를 == 비교해보자~~~ 결과는 true가 나온다. 

다들 알다시피 JAVA에서 == 비교는 객체의 주소를 비교하는 것이고, 이 주소가 똑같다는 것은 완전히 두 객체가 일치함을 의미한다.

 

사실 1차 캐시에 의한 동일성 보장과 성능 향상은 한 트랜잭션 내에서 유효하다.

또한 웹 상에서 트랜잭션은 고객의 요청마다 생기고 사라지기에 큰 성능 향상을 기대기 힘들고,
동일성 보장같은 경우 로직이 정말 복잡해지지 않는 이상 쓸 일이 드물다. 그래도 알아두면 쓸모가 있을 것이다.


트랜잭션을 지원하는 쓰기 지연과 버퍼링

persist 메서드는 쿼리를 바로 DB에 보내지 않고, 커밋하는 순간 보낸다. 내부 동작을 더 구체적으로 살펴보자~~~

내부적으로 크게 두 가지 일을 한다.

1. Entity를 영속상태로 만든다.

2. 쓰기 지연 SQL 저장소에 SQL을 만들어서 저장한다.

 

remove 메서드도 persist 메서드와 비슷한 메커니즘으로 돌아가고, Entity를 영속상태가 아닌 삭제(removed) 상태로 만든다는 데에서 차이가 있다.
삭제 상태는 트랜잭션이 플러시 될 경우 DB에서 삭제되도록 예약되어 있는 것을 말한다.

 

이번에는 commit 메서드를 알아보자~~~

1. flush 메서드를 통하여 쓰기 쓰기 지연 SQL 저장소에 저장한 SQL들을 DB에 보낸다.

2. DB에 커밋한다.

 

동작을 잘 살펴보면 remove나 persist 메서드를 통해 SQL들을 차곡차곡 쌓아놓고 한 번에 DB에 보내서 커밋하는 것을 알 수 있다. 이를 버퍼링이라고 한다.
이런 JPA의 내부동작 덕분에 우리는 DB에서 트랜잭션을 쓰는 것처럼 JPA에서도 사용할 수 있고, DB와의 통신 비용을 아낄 수 있다.

 

여기서 언급하지 않은 부분이 있는데, 그러면 update 쿼리는 어떻게 동작할까?


변경감지

사실 1차 캐시에는 스냅샷이라는 것이 존재한다. 스냅샷은 객체가 맨 처음에 영속화 될 때 생성되는 것이다.

즉, 초기 상태라고 생각하면 된다.

그리고 commit 메서드에 의해 호출되는 flush 메서드는 DB에 SQL들을 전송하기 전에 update를 위한 작업을 한다.

  1.  스냅샷과 Entity를 비교한다.
  2. 비교하여 변한 필드가 하나라도 있다면 쓰기 지연 SQL 저장소에 update 쿼리를 저장한다.
    (이로써 insert, delete 쿼리와 함께 update 쿼리도 버퍼링되는 것)
  3. 쓰기 지연 SQL 저장소에 저장한 SQL들을 DB에 보낸다.

그리고 이러한 내부동작때문에 update같은 경우 별도의 메서드(persist나 remove 같은 메서드)를 호출할 필요가 없고,

Setter를 통해 값을 수정해주기만 하면 commit 메서드 시점에 알아서 update 쿼리가 날아간다.

 

이렇게 update 쿼리까지 어떻게 버퍼링되는 건지 살펴보았다.

 

참고강의: 배달의 민족 개발팀장 김영한 강사님의 JPA 강의

'JPA' 카테고리의 다른 글

객체와 Table 매핑  (0) 2021.02.14
flush  (0) 2021.02.13
영속성 컨텍스트  (0) 2021.02.13
JPA로 CRUD해보기(4) JPQL  (0) 2021.02.09
JPA로 CRUD해보기(3) Entity와 CRUD 코드 작성해보기  (0) 2021.02.08