영속성 컨텍스트
- “Entity”를 영구 저장하는 환경이라는 뜻이다.
EntityManager.persist(entity)
를 통해서 엔티티를 영속성 컨텍스트에 저장한다.(중요한 포인트는 연동된 DB에 저장하지 않는다는 것이다)- EntityManager를 통해서 영속성 컨텍스트에 접근 할 수 있다.
- 아래 그림과 같이 J2SE환경에서는 서로 1대1관계를 맺고 있다.
엔티티의 생명주기
- 비영속 : 엔티티를 생성만 한 상태
- 영속 : em.persist()를 통해 영속성 컨텍스트에 엔티티를 넣은 상태(앞서 언급한 것 처럼 DB에 저장이 되지 않으며, 즉 쿼리가 나가지 않음)
- 준영속 : em.detach()를 통해 영속성 컨텍스트에서 엔티티를 분리시킨 상태
- 삭제 : em.remove()를 통해 DB에 저장된 엔티티까지 삭제시킨 상태
실습해보기
- 아래와 같은 코드를 통해 Member 객체를 생성하고 em.persist()를 통해서 영속성 컨텍스트에 엔티티를 넣었다.
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
Member member = new Member();
member.setId(100L);
member.setName("brido");
//영속
System.out.println("Before");
em.persist(member);
System.out.println("After");
tx.commit();
} catch (Exception e) {
tx.rollback();
}finally {
em.close();
}
emf.close();
}
- 하지만 출력된 로그를 통해 보면 Before와 After사이에는 아무런 insert문이 데이터베이스에 날라가지 않았다.
- 이제부터 이와 관련된 궁금증을 알아보자.
그러면 DB에 entity를 저장하는 쿼리는 언제 발생하는걸까?
영속성 컨텍스트의 기능
1차 캐시
- 영속성 컨테스트는 아래와 같이 1차 캐시 구조를 가진다.
em.find()
를 통해서 엔티티를 조회할 경우, 우선적으로 1차 캐시부터 먼저 탐색한다.즉 DB까지 조회하지 않고 엔티티를 조회할 수 있다.- 1차 캐시에서 탐색 후, 없는경우에만 직접 DB로 쿼리를 날려 해당되는 엔티티를 가져와서 1차캐시에 저장하고 나서,반환해준다.
영속 엔티티의 동일성 비교(== 비교)
- 쉽게 말해서 아래와 같은 코드를 실행하면 동일하다고 true가 출력된다.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b);
- 메모리 주소 연산을 하듯이, 동일한 1차캐시에 저장되어 있기에 위와같은 결과가 도출된다.
쓰기 지연
- 위에서 언급된 질문에 대한 대답을 해줄 수 있는 기능이다.
em.persist(memberA)
, em.persist(memberB)
를 한 경우 아래의 그림과 같이 영속성 컨텍스트가 동작한다.- 먼저 영속성 컨텍스트에 해당 엔티티를 생성하고 위의 언급된 1차캐시에 저장한다.
- 이후 Insert 문을 ‘쓰기 지연 SQL 저장소’에 넣어둔다.
- 해당 저장소에 저장된 쿼리는 어떠한 명령어가 들어오기 전까지 DB로 보내지지 않는다.
- 모여있는 쿼리들은
transaction.commit()
을 통해서 DB로 보내진다. - 커밋을 실행하면 아래와 같은 동작방식으로 DB에 쿼리가 날라간다.
- 아래와 같은 동작방식이란, flush를 통해서 쓰기 지연 저장소에 있는 SQL을 모두 DB로 날려준다.
Flush
- 영속성 컨텍스트의 변경내용을 데이터베이스에 반영해주는 명령어이다.
- 즉, 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스로 날려준다.
- tx.commit()을 할 경우 flush가 자동으로 호출된다. 이후 DCL commit을 실행한다.
- JPQL 쿼리 실행시 flush가 자동으로 호출된다.(쿼리문이 select인 경우 선택할 데이터들이 DB에 없으면 안되기 때문에 미리 flush를 호출해준다)
- 마지막으로 em.flush()로 플러시만 따로 호출 할 수 있다.(DB로 날라가는 쿼리를 확인해보고 싶을 경우 사용)
변경 감지
- 흔히, Ditry-Checking이라고도 불리는 해당 기능은 엔티티에 대한 속성이 변경하고자 할때, 해당 엔티티에 대해서 다시
em.persist()
를 하지 않아도 자동으로 DB에 update문을 날려주는 기능이라고 보면 된다. - 아래의 코드를 보면 PK값 1L을 가지는 멤버 엔티티를 findMember라는 객체에 저장해 둔다.(em.find의 대상이 되는 memberA는 이미 em.pesist되었다고 가정한다)
- 이후 해당 엔티티에 대한 데이터를 수정하는 작업을 거치고 tx.commit()으로 인한 flush을 통해 쓰기 지연 SQL 저장소에 있던 쿼리들을 DB로 반영된다.
- 그러면 아래에서 주석 처리된 em.pesist(memberA)라는 코드를 쓰지 않아도 DB에 업데이트문이 날라가면서 엔티티가 수정된다.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // 트랜잭션 시작
// 영속 엔티티 조회
Member findMember = em.find(Member.class, "1L");
// 영속 엔티티 데이터 수정
memberA.setUsername("brido");
memberA.setAge(10);
//em.persist(memberA)
transaction.commit();
- 아래와 같은 방식으로 엔티티에 대한 변경 감지가 발생한다.
Ref