영속성 컨텍스트에 대해 먼저 간단히 설명하자면 '엔티티를 영구 저장하는 환경' 이라는 뜻인데,
엔티티 메니저가 아래 코드와 같이 엔티티를 저장하거나 조회하면 영속성 컨텍스트는 엔티티를 보관/저장하여 관리하게 된다.
EntityManager.persist(entity);
영속성 컨텍스트에 대해 본격적으로 얘기하기 앞서 먼저 알아야 할것은 엔티티의 생명 주기이다.
엔티티의 생명주기를 코드와 함께 보면 다음과 같다.
비영속 (new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
영속 (managed) : 영속성 컨텍스트에 관리되는 상태
Member member = new Member();
member.setId("member1");
member.setUsername(“회원1”);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//객체를 저장한 상태(영속)
em.persist(member);
이제 엔티티의 생명주기가 어떤식으로 운영되는지, 또한 영속성 컨텍스트는 어떤식으로 동작하는지 얘기해보자.
맨 앞에서 영속성 컨텍스트란 엔티티객체를 관리해주는거라 간단히 말했는데 이렇게 영속성 컨텍스트를 이용하므로써 얻을 수 있는 이점은 다음과 같다.
1차 캐시
동일성(identity) 보장
트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
변경 감지(Dirty Checking)
지연 로딩(Lazy Loading)
먼저 1차 캐시를 가짐으로써 db와의 통신을 줄일 수 있다.
처음 엔티티 객체를 생성하게 되면 비영속 상태이고, 엔티티 객체와 영속 컨텍스트는 아래 그림처럼 아무런 관계가 없다.
비영속 상태의 엔티티 객체를 엔티티매니저를 통해 저장(em.persist)하면 아래 그림처럼 영속 켄텍스트가 1차 캐시를 통해 엔티티객체를 저장/관리 한다.
이렇게 영속 컨텍스트에 의해 관리되는 영속상태의 엔티티객체를 조회(em.find)할 경우 아래 그림처럼 db를 통하지 않고 영속 켄텍스트의 1차캐시를 통해 객체 정보를 받아온다.
영속 컨텍스트로 관리되고 있지 않는 비영속 상태 엔티티객체를 조회할 경우에는 아래 그림과 같이 작동한다.
다음 이점인 동일성 보장이란 1차 캐시에 있는 엔티티를 참조함으로써 == 연산을 가능하게 해준다는 뜻이다.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); // 동일성 비교 true
만약 1차캐시가 있는 영속 컨텍스트를 이용하지 않는다면 db에 직접 정보를 받아와 각각의 객체에 할당해야하기 때문에 동일한 정보라도 ==연산을 하면 false가 나올것이다.
다음 이점으로 트랜잭션을 지원하는 쓰기 지연이 있다.
영속 컨텍스트에는 1차 캐시 말고도 '쓰기 지연 SQL 저장소'라는게 있다. 트랜잭셕이 시작하고 나서 에티티 매니저가 요청하는 INSERT 쿼리들은 바로바로 db로 넘어가는 것이 아니라 쓰기 지연 SQL 저장소에 저장 된다.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋
저장된 INSERT SQL 들은 트랜잭셕이 커밋되는 시점에 flush되서 db로 한꺼번에 넘어가게 된다.
다음 이점은 엔티티 수정 변경 감지(Dirty Checking)가 있다.
영속 컨텍스트는 1차캐시에 스냅샷이란것을 유지해서 flush되는 시점에 현재 엔티티객체의 상태와 1차캐시의 스냅샷에 저장된 엔티티객체의 상태를 비교하고 다르다면 UPDATE SQL을 생성하여 쓰기 지연 SQL 저장소에 등록 했다가 기존에 쓰기 지연 SQL 저장소에 등록돼 있었던 쿼리들과 함께 한꺼번에 db에 전송한다.
영속성 컨텍스트로 얻을 수 있는 이점으로 지연로딩이 남았는데, 이는 프록시와 관련되 있어서 프록시를 주제로 다룰때 따로 설명할 예정이다.
참고로 영속성 컨텍스트를 플러시 하는 방법으로는 아래와 같은 경우들이 있다.
em.flush() - 직접 호출
트랜잭션 커밋 - 플러시 자동 호출
JPQL 쿼리 실행 - 플러시 자동 호출
여기서 주의할 점은 플러시는 쓰기 지연 SQL 저장소에 있는 쿼리들을 db에 전송하는 것이지 영속성 컨텍스트를 비우는 것이 아니다.
영속성 컨텍스트를 완전히 초기화하기 위해서는 em.clear()라는 명령어가 쓰이고, 영속성 컨텍스트를 종료하기 위해서는 em.close()가 쓰인다.