이번에는 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 기반을 탄탄하게 쌓도록 도와주고 계신 김영한님께 감사인사를 드립니다 🙇♀️ 감사합니다 !
댓글