JPA 영속성 컨텍스트 - 1차캐시와 변경감지
영속성 컨텍스트는 내부에 캐시를 가지고 있는데, 이것을 1차 캐시라고 한다. 엔티티가 영속상태가 되면 모두 1차 캐시에 저장이 된다. 1차 캐시의 형태는 @Id로 매핑된 엔티티의 식별자와 이에 해당하는 엔티티 객체, 스냅샷이 들어있다. 우선적으로 스냅샷을 제외하고 1차 캐시가 하는 역할을 알아보자.
엔티티 등록
밑의 소스코드는 엔티티 객체를 생성하고 초기화한 다음, 엔티티 매니저를 통해서 엔티티 인스턴스를 영속화하는 코드이다.
//엔티티 인스턴스를 생성(비영속)
Member memberA = new Member();
member.setId("memberA");
member.setUsername("회원A");
//엔티티 인스턴스를 영속화(영속)
em.persist(memberA);
그렇다면 위 코드를 실행하면 영속성 컨텍스트 내부는 어떻게 동작할까? 위 코드를 실행하면 아직 트랜잭션 커밋을 하지 않았다고 가정을 하면 데이터베이스에 바로 반영되는 것이 아니고, 영속성 컨텍스트의 1차 캐시에 우선적으로 저장된다. 1차 캐시의 키(Key)는 식별자 값, 즉 데이터베이스의 기본 키(PK)와 매핑된다.
그림에서 보면 쓰기 지연 SQL 저장소가 눈에 띌 것이다. 이 쓰기 지연 저장소는 영속화된 엔티티 객체들이 데이터베이스에 커밋되기 전에 미리 SQL문을 만들어서 저장해두는 곳이다. 즉, 모든 작업이 완료되고 커밋을 하면 쓰기 지연 SQL 저장소에 저장되어있는 SQL문이 날라가서 데이터베이스에 최종적으로 반영이 된다.
엔티티를 데이터베이스에 반영하는 과정을 요약하면 다음과 같다.
- 엔티티 객체를 생성하고 초기화하여 영속성 컨텍스트에 영속화한다.
- 엔티티 객체는 1차 캐시에 저장되고, 이와 동시에 쓰기 지연 SQL 저장소에 INSERT 쿼리가 저장된다.
- 데이터베이스 커밋을 하여 최종적으로 엔티티 객체를 데이터베이스에 반영한다. 즉, 쓰기 지연 SQL 저장소에 있는 모든 쿼리를 날려서 데이터베이스에 반영한다.
엔티티 조회
그렇다면 조회를 할 때의 영속성 컨텍스트의 내부 동작은 어떻게 이루어지게 될까?
기본적으로 엔티티를 조회하면 영속성 컨텍스트의 1차 캐시에서 우선적으로 엔티티를 조회하게 된다. 만약에 1차 캐시에 찾고자하는 엔티티가 없으면 그때서야 데이터베이스를 조회해서 엔티티를 찾아오게 된다.
다음 코드를 통해서 좀 더 구체적으로 알아보자. member1 인스턴스는 한 트랜잭션 안에서 영속화하고 조회한다고 가정하고, member2 인스턴스는 조회만 수행한다고 가정하자.
Member member1 = new Member();
member.setId("member1");
member.setUsername("회원1");
//member1 영속화 -> 1차 캐시에 저장됨
em.persist(member1);
//member1 조회 -> 1차 캐시에서 조회
Member findMember1 = em.find(Member.class, "member1");
//member2 조회 -> 1차 캐시에 없음 -> DB에서 조회
Member findMember2 = em.find(Member.class, "member2");
member1은 한 트랜잭션 안에서 엔티티 객체를 영속화하고 조회를 하였으므로 1차 캐시에서 데이터를 가져온다. 그러나 member2는 1차 캐시에 존재하지 않으므로 데이터베이스에서 조회하여 엔티티를 가져오게 된다.
다음 상황을 그림으로 표현하면 다음과 같다.
1차 캐시에 존재하지 않는 엔티티를 조회하는 과정을 요약하면 다음과 같다.
- 엔티티 매니저를 통해서 엔티티를 조회한다. -> 1차 캐시에서 우선적으로 조회한다.
- 1차 캐시에 엔티티 객체가 없으므로 데이터베이스에서 엔티티 객체를 조회한다.
- 조회한 데이터는 이제 영속화시켜야 하므로 1차 캐시에 저장한다.
- 조회한 데이터를 반환하게 된다.
엔티티 수정
엔티티를 수정할 때는 엔티티 매니저에서 별도의 메서드를 제공하지 않는다. 이것의 비밀은 바로 영속성 컨텍스트의 변경감지 기능 때문인데, 먼저 말하자면 자바의 세터(Setter)를 통해서 영속화되어있는 엔티티 객체를 변경을 해주기만 하면 바로 반영이 된다.
그림과 소스코드를 통해서 확인해보자.
//엔티티 객체 조회 -> 영속 상태
Member memberA = em.find(Member.class, "memberA");
//영속상태의 엔티티 데이터 수정
member1.setUsername("James");
member1.setAge(20);
//트랜잭션 커밋
transaction.commit();
위의 소스코드에서는 엔티티 매니저에 의해서 데이터가 수정되지 않는다. JPA에서는 단순히 영속화된 엔티티 객체를 수정하기만 하면 변경감지로 인해서 데이터베이스에 반영이 된다.
그렇다면 변경감지는 어떻게 동작하는 것일까?
JPA는 엔티티를 영속화해서 영속성 컨텍스트에 보관할 때, 최초의 상태를 복사해서 저장해두는데 이것을 스냅샷이라고 한다. 영속상태에 있는 엔티티 객체를 수정한 후 플러시하여 데이터베이스에 반영할 때, 스냅샷과 엔티티를 비교해서 변경된 엔티티를 찾아서 반영해주는 원리이다.
변경감지를 통해서 엔티티가 수정되는 과정과 그 과정을 그림을 통해서 알아보자.
- 엔티티를 수정하기 전에 엔티티를 조회해서 영속화되고, 1차 캐시에 식별자, 엔티티, 스냅샷을 저장한다.
- 값을 변경하고 동시에 1차 캐시의 엔티티 값이 변경된다.
- 마지막으로 트랜잭션을 커밋을 하면, 커밋하기 전에 엔티티와 스냅샷을 비교해서 변경된 엔티티를 찾는다.
- 변경된 엔티티에 대한 UPDATE SQL문이 쓰기 지연 SQL 저장소에 모두 저장된다.
- 쓰기 지연 SQL 저장소의 SQL을 모두 데이터베이스에 보내고 트랜잭션을 커밋하게 된다.
엔티티 삭제
엔티티를 삭제하는 법은 엔티티를 등록하는 것과 비슷하게 동작한다. 단지 엔티티를 조회하고 찾는 과정만 추가되었을 뿐이다.
엔티티를 삭제하는 소스코드는 다음과 같이 간단하다.
//삭제 대상 엔티티 조회 -> 영속화
Member member = em.find(Member.class, "member1");
//엔티티 삭제
em.remove(member);
엔티티를 삭제하는 과정은 다음과 같다.
- 엔티티를 조회하고 엔티티를 영속상태로 만든다.
- em.remove(member)를 호출하면 커밋하기 전에 쓰기 지연 SQL 저장소에 DELETE 쿼리를 저장한다.
- 트랜잭션 커밋이 이루어지면 쓰기 지연 SQL 저장소의 쿼리가 데이터베이스에 반영된다.
'JPA' 카테고리의 다른 글
JPA - 영속성 전이와 고아객체 (0) | 2022.06.08 |
---|---|
JPA - 프록시와 즉시, 지연로딩 (0) | 2022.06.06 |
연관관계 매핑 종류 (0) | 2022.05.21 |
값 타입 (0) | 2022.02.02 |
프록시와 연관관계 관리 (0) | 2022.02.02 |