저번편에서는 영속성 컨텍스트에 구체적으로 다루진 않았지만 코드와 결과를 보며 간접적으로 영속성 컨텍스트의 존재를 알았다. 이번엔 영속성 컨텍스트와 엔티티 등록의 메커니즘에 대해 자세하게 알아보자.
Member member = new Member();
member.setId(1L);
member.setName("HelloJPA");
1편에서 사용했던 코드를 다시 가져왔다.
Member 객체를 만들고 Setter 메서드를 통해 필드에 값을 채워넣은 상태이다.
JPA의 관점에서 보면 현재 저 member 객체는 무엇일까?
당연히 객체 그 이상, 이하 아무것도 아니다. 단지, 프로그램 실행 중에 생성 된 하나의 객체일뿐이다.
이러한 상태를 JPA에서는 비영속 상태라고 부른다.
객체는 만들었으나, 이 객체가 영속 컨텍스트에 들어가지 있지 않다면, JPA의 관리를 받지 못하는 것이다. 조금 생각해보면 당연한 일이다. 프로그램 실행 중 만들어지는 객체는 수없이 많을텐데.. 이 중 어떤 객체를 DB에 저장할지, 수정할지, 삭제할지 결정할 기준이 있어야한다. 그리고 그 기준이 되는 곳을 영속성 컨텍스트라고 보면 될것 같다.
그렇다면, 비영속 상태인 member 객체를 영속 상태로 만들어주기 위해서는 어떻게 해야할까?
1편을 다시 보고 곰곰이 생각해본다면, 어렵지 않게 정답을 알 수 있다.
em.persist(member);
위 처럼 persist 메서드를 사용하면
위 그림과 같이 member 객체는 영속 상태가 되어 그 때부터는 JPA의 영역 안으로 들어오게 된다. 이 시점에서 중요한 점은 member 객체는 영속상태가 되었지만, 영속 상태를 DB에 저장 된 상태와 동일하게 생각해서는 안된다는 것이다. 영속상태가 되었다는 것은 정확한 표현일지는 모르겠지만 DB에 저장될 수 있는 후보가 된 상태 정도로 볼 수 있다. 물론 모든 후보가 무조건 당선되는게 아니듯이 저 객체 역시 저장이 될지 수정이 될지 다시 영속상태에서 나오게 될지는 알 수 없는 것..!
그렇다면 당연한 의문점이 나온다.
"영속 컨텍스트에서 관리하고 있는 엔티티가 실제로 DB에 저장되는 시점은 언젠데?"
... 생략(memberA, memberB 객체 생성)
em.persist(memberA);
em.persist(memberB);
위 그림은 영속컨텍스트의 내부를 조금 더 자세하게 그려놓은 그림이다. 영속 컨텍스트는 내부적으로 1차캐시와 쓰기지연 SQL 저장소를 갖고있는데, persist를 통해 새로운 객체가 영속 상태가 되면 1차 캐시에 엔티티가 저장되면서, 이 엔티티를 분석하여 INSERT SQL을 만들고 쓰기 지연 SQL 저장소에 저장하고 대기한다. (1차 캐시는 데이터 조회 메커니즘을 설명할 때 조금 더 자세하게 다루겠다.)
tx.commit();
코드가 실행되면 아래 그림처럼
쓰기 지연 SQL 저장소에 저장되있던 INSERT문들이 DB를 향해 날아가고(flush) 데이터를 저장한다. 전편에서 예시로 들었던 코드와 결과를 다시 가져와보겠다.
🎈 전편 예제코드 3
Member member = new Member();
member.setId(2L);
member.setName("TestJPA");
System.out.println("=== BEFORE ===");
em.persist(member);
System.out.println("=== AFTER ===");
tx.commit();
🎉 전편 로그 3
=== BEFORE ===
=== AFTER ===
Hibernate:
/* insert hellojpa.Member
*/ insert
into
Member
(name, id)
values
(?, ?)
이번엔 위와 같은 결과(로그)가 나오는 이유가 이해가 되겠는가?
em.persist(member) 코드를 실행했을 때는 member 객체는 영속 상태가 되어 1차캐시에 저장되고 INSERT SQL은 쓰기 지연 SQL 저장소에 저장되있을뿐. 실제 INSERT문이 실행되는 시점은 바로 tx.commit() 코드가 실행 되는 시점이다. 그러므로 BEFORE / AFTER가 모두 출력 된 뒤에 INSERT문이 나오는 것을 확인할 수 있다.
그렇다면 여기서 추가 의문점이 생길 수 있다.
"그냥 영속상태가 될 때 바로 저장하면 안될까? 왜 이렇게 번거로운 메커니즘을 갖고 있지?"
이 부분은 내가 아직 정확히 설명할 수 없지만, 쓰기 지연 SQL 저장소를 일종의 버퍼라고 생각하면 될것같다. 쿼리문을 그때 그때 보내든, 모아서 보내든 성능상의 이점을 얼마나 얻을 수 있을지는 정확히 모르겠지만, 어찌 됐든 개발자가 원하는 시점에 보낼 수 있냐와 없냐는 최적화의 여지가 남아있는지 없는지를 결정할 수 있기 때문이다.
여기까지, 영속성 컨텍스트가 엔티티를 등록하는 메커니즘에 대해 살펴보았다.
다음편에서는, 영속성 컨텍스트가 엔티티를 조회하는 메커니즘, 그리고 그 과정 중 등장하는 1차 캐시와 장점에 대해 살펴보도록 하겠다!