(JPA) 영속성 컨텍스트 (1차 캐시, 동일성 보장, 지연로딩, 변경감지)
1차 캐시
1차 캐시의 과정은 세 가지 과정과 같다.
- 조회 시 처음 1차 캐시에 해당 데이터가 있는지 탐색을 한다. -> 만약 있으면 바로 리턴
- 조회 결과 1차 캐시에 데이터가 없으면 데이터베이스에 접근해 값을 탐색한다.
- 탐색 결과를 바로 리턴하는 것이 아닌 다음 탐색에서 재사용할 수 있도록 1차 캐시에 저장한다.
하나 1차 캐시에 항상 저장하고 있는 것이 아니라 EntityManager는 트랜잭션 단위이기 때문에 트랜잭션이 끝나면 1차 캐시도 지워버린다.
굉장히 짧은 찰나의 순간에만 장점이 있다.
아래와 같이 101L을 PK로 하는 Member 인스턴스를 저장하고 조회하는 코드를 작성해보자!
Member member = new Member();
member.setId(101L);
member.setName("HelloJPA");
System.out.println("*********Before*********");
entityManager.persist(member);
System.out.println("*********After*********");
Member findMember1 = entityManager.find(Member.class, 101L);
System.out.println("findMember id = " + findMember1.getId());
System.out.println("findMember name = " + findMember1.getName());
transaction.commit();
조회 결과 SELECT 쿼리가 생기지 않았다!!!
entityManager.persist(member); 저장 시점에 데이터베이스에 저장되는 것이 아닌 1차 캐시에 저장이 된다.
그리고 나는 똑같은 PK (101L)을 조회를 했기 때문에 1차 캐시에 있는 값을 가져와서 조회 쿼리가 생성되지 않은 것이다.
두 번째 조회하는 시점에서는 쿼리가 나가지 않는다! (1차 캐시에 저장되어있기 때문에)
Member findMember1 = entityManager.find(Member.class, 101L);
Member findMember2 = entityManager.find(Member.class, 101L);
동일성 보장
아래의 코드는 같은 PK 값 (101L)을 가지는 엔티티에 대해서 동일성을 보장해준다는 것을 의미하는 코드이다.
Member findMember1 = entityManager.find(Member.class, 101L);
Member findMember2 = entityManager.find(Member.class, 101L);
System.out.println(findMember1 == findMember2);
JPA가 아닌 MyBatis에서는 조회 결과를 다시 인스턴스로 감싸서 리턴하기 때문에 위의 결과는 False가 나오지만 JPA는 같은 참조값을 갖게 해 주므로 True 가 나온다.
트랜잭션을 지원하는 쓰기 지연
아래 코드를 실행시키면 persist 하는 시점이 아닌 ******* 이후에 commit 시점에 쿼리가 발생하는 것을 볼 수 있다.
Member member1 = new Member(200L, "A");
Member member2 = new Member(201L, "A");
entityManager.persist(member1);
entityManager.persist(member2);
System.out.println("*********");
transaction.commit();
persist를 호출하는 시점에 쿼리를 보내도 되지 않을까라는 생각이 들었지만
이렇게 함으로써 버퍼링이란 기능을 쓸 수 있다. 이렇게 함으로써 최적화할 수 있는 여지를 남길 수 있다.
예를 들어서
<property name="hibernate.jdbc.batch_size" value="10"/>
위와 같은 설정을 사용해서 해당 크기만큼을 모아서 네트워크로 보낼 수 있다.
변경 감지
현재 아래와 같이 ID 201A라는 이름으로 저장이 되어있다.
아래와 같이 setter로 값을 변경만 해도 NAME이 변경된다.
Member member = entityManager.find(Member.class, 201L);
member.setName("Hello junwoo");
transaction.commit();
나는 persist를 호출하지 않았고 값만 변경했는데 아래와 같이 쿼리가 나가면서 동시 데이터베이스에 있는 값도 변경되었다.
변경 감지의 과정에 대해서 정리하자면
- 1차 캐시에 저장될 때 1차 캐시에는 최초 저장 시점의 SnapShot을 저장해둔다.
- 만약 변경이 감지되면 commit 되는 시점에 flush가 호출되면서 JPA가 일일이 각각의 SnapShot을 비교해서 변경을 감지한다.
- 변경을 감지하였다면 UPDATE 쿼리를 쓰기 지연 SQL 저장소에 생성을 하고 데이터베이스에 반영하고 커밋을 진행한다.
References
www.inflearn.com/course/ORM-JPA-Basic/dashboard
Code Link