본문 바로가기
SQL/JPA

[JPA] JPA 특징 (동일성 보장, 쓰기 지연, 변경 감지, 그리고...)

by 지구 2021. 7. 1.

이번에는 JPA 의 특징으로 많이 거론되는 3대장에 대해서 정리해보고자 합니다.

👥 사실 이 3대장은 영속성 컨텍스트의 동작 방식에서 나온 특징이라는 점 참고 부탁드립니다.

영속성 컨텍스트는 엔티티를 영구 저장하는 환경을 뜻합니다.


동일성 보장

동일성 보장은 EntityManager 에서 한 객체에 대해 여러 번 꺼내도 꺼낸 객체들은 모두 같다(`==`) 는 것을 보장해줍니다.

이게 어떻게 가능할까요?
영속성 컨텍스트 안에 있는 1차 캐시에서 Transaction Isolation Level 을 level2 (반복 가능한 읽기, REPEATABLE_READ) 수준을 사용하기 때문에 데이터베이스가 아닌 애플리케이션 차원에서 보장해줄 수 있는 것 입니다.

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

    tx.begin();

    // 동일성 보장 확인
    Member findMember1 = em.find(Member.class, 100L);
    Member findMember2 = em.find(Member.class, 100L);
    System.out.println("result = " + (findMember1 == findMember2)); // "result = true"

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

코드로 보면 findMember1 과 findMember2 는 같다고 보장해주는 것을 확인할 수 있습니다.

 

쓰기 지연

쓰기 지연은 Entity 를 저장(.persist()) 한다고 해서 데이터베이스에 쿼리가 바로 전송 되는 게 아니라
트랜잭션이 종료되는 시점(commit, 내부적으로는 .flush()) 에 쿼리가 전송되는 것을 뜻합니다.

이게 어떻게 가능할까요?
영속성 컨텍스트 안에는 '쓰기 지연 SQL 저장소' 와 1차 캐시(Entity 저장소) 가 나뉘어져 있어서 .persist() 시점에는 쿼리를 쓰기 지연 SQL 저장소에 저장해두었다가, 트랜잭션이 종료되는 시점에 쓰기 지연 SQL 저장소에 있는 쿼리들을 데이터베이스로 보내는 구조이기 때문입니다.

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

    tx.begin();

    // 쓰기 지연 확인
    Member member1 = new Member(100L, "지구");
    Member member2 = new Member(200L, "hajs");
    em.persist(member1);
    em.persist(member2);
    System.out.println("==== where is insert query ====");

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

네, insert query 는 "==== where is insert query ====" 주석 아래에 찍힙니다.
트랜잭션이 종료(tx.commit()) 가 될 때 쿼리를 보내기 때문이죠! 👏

 

변경 감지 (dirty checking)

변경 감지는 한 트랜잭션 안에서 발생하는 Entity 의 변경사항을 감지하고 update 해주는 것을 뜻합니다.

이게 어떻게 가능할까요?
영속성 컨텍스트 안에 있는 1차 캐시에는 Entity 가 적재되는 순간을 기록하는 '스냅샷' 공간이 있습니다.
이 공간에 저장된 스냅샷과 <> 트랜잭션이 종료 되었을 때의 해당 Entity 스냅샷을 비교하고
두 스냅샷에 차이가 있으면 변경된 정보를 update 쿼리로 만들어서 쓰기 지연 SQL 저장소에 적재하는 구조를 가졌기 때문입니다.

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

    tx.begin();

    // 변경 감지 확인 (dirty checking)
    Member findMember = em.find(Member.class, 100L);
    findMember.setName("지수"); // 지구 -> 지수
    System.out.println("==== where is update query ====");

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

네, 이번에도 update query 는 "==== where is update query ====" 아래에 찍힙니다.

처음에 .find() 로 findMember 를 select 하고 Entity 를 1차 캐시에 넣었을겁니다. -> SNAPSHOT_1
이후에 findMember 의 이름을 '지구' -> '지수' 로 변경하고
트랜잭션이 종료되었는데, commit 시점의 Entity 와 SNAPSHOT_1 의 Entity 가 다릅니다.

영속성 컨텍스트는 이를 감지하고, 결국 Member 객체에 대한 update query 를 데이터베이스에 보냈기에 로그는 아래에 찍힌겁니다!

 

이 쯤 되니... 궁금하지 않으세요?

저는 위의 내용을 공부하면서 궁금한 점이 생겼습니다.

1. RDBMS 에서 삭제는 보통 auto commit 이 되는데, JPA 에서는 어떻게 동작하게 될까?

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

    tx.begin();

    // entity 삭제 동작 방식이 궁금합니다!!
    Member findMember = em.find(Member.class, 100L);
    em.remove(findMember);
    System.out.println("==== where is delete query ====");

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

그냥 바로 코드를 짜서 실행시켜 보았습니다.

네, 혹시나 싶었지만 역시나 "==== where is delete query ====" 밑에 로그가 찍혔습니다.
트랜잭션이 종료되는 시점에 delete query 가 보내졌고, 이로써 영속성 컨텍스트의 특징과 이점을 다시 알 수 있었습니다 :)

 

2. 한 트랜잭션 안에서 객체를 생성하고 제거하면, 1차 캐시 내에서만 핸들링이 되는걸까?

아니, 그러면 한 트랜잭션 안에서 객체를 생성하고 제거하면 1차 캐시 내에서만 추가되었다가 제거되는걸까?
쓰기 지연 SQL 저장소는 어떻게 반응할까? query 는 어떻게 나갈까? 궁금해졌습니다...

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

    tx.begin();

    // entity 추가와 삭제를 같이 하면 어떻게 될 지 궁금합니다 !!
    Member memberHa = new Member(111L, "하");
    em.persist(memberHa);
    System.out.println("==== where is insert query ====");
    em.remove(memberHa);
    System.out.println("==== where is delete query ====");

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

출력 : "==== where is insert query ====" \n "where is delete query ====" \n { insert query -> delete query }

결과적으로는 insert query 와 delete query 가 실행 되었습니다.

한 트랜잭션 안에서 객체를 생성하고 제거하는 작업이라도, 각 작업이 이뤄질 때 마다 1차 캐시와 쓰기 지연 SQL 저장소에 객체를 저장한다는 것을 알 수 있는.. 즉, 영속성 컨텍스트가 '엔티티를 영구 저장하는 환경' 이라는 뜻에 가장 부합하는 예제가 아닐까 생각했습니다 😊


JPA 기반을 탄탄하게 쌓도록 도와주고 계신 김영한님께 감사인사를 드립니다 🙇‍♀️ 감사합니다 !

반응형

댓글