3장. 영속성 관리
'자바 ORM 표준 JPA 프로그래밍'(김영한 저)를 참고하였습니다.
엔티티 매니저 팩토리와 엔티티 매니저
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
EntityManager em = emf.createEntityManager();
엔티티 매니저 팩토리가 하나가 만들어지고 만들어진 그 하나의 객체로 여러 개의 엔티티 매니저를 만들어서 사용되는 구조입니다. 팩토리는 만드는데 많은 비용이 소모됩니다.
영속성 컨텍스트
영속성 컨텍스트는 엔티티 매니저를 생성할 때 하나가 만들어집니다. 이 엔티티 매니저를 통해서 영속성 컨텍스트에 접근 및 관리를 할 수 있는 것이죠.
엔티티 생명주기
엔티티에는 4가지의 상태가 존재합니다 - 비영속, 영속, 준영속, 삭제
이 4가지 상태 중 가장 궁금한게 '준영속'입니다. 뭔지 쉽게 감이 오지 않거든요. 우선 영속성은 '영속성 컨텍스트에 의해 관리된다'라는 뜻이라고 합니다. 비영속성 상태에 있던 것이 이젠 관리 대상이 되는 것이죠.
em.persist(member); // member는 비영속 객체
준영속 상태는 영속성 컨텍스트에 관리하지 않는 상태입니다. em.detach()
나 em.close()
, em.clear()
를 통해 가능한데요. em.remove(member)
를 통해 삭제하는 것과의 차이는 영속성 컨텍스트를 잠시 분리시키느냐, 아니면 완전히 삭제하느냐의 차이인 것 같습니다. 삭제의 경우 심지어 데이터베이스에서까지 삭제합니다.
영속성 컨텍스트의 특징
- 반드시 식별자값(
@Id
) 가 존재해야 합니다. - JPA는 트랜잭션을 커밋하는 순간 DB에 반영을 하는데 이러한 과정을 플러시(flush)라고 합니다.
- 1차 캐시, 동일성 보장, 쓰기 지연, 변경 감지, 지연 로딩
위 특징들이 어떤 의미인지 쉽게 와닿지 않죠. CRUD 과정을 통해 하나씩 살펴보겠습니다.
1차 캐시, 동일성 보장
영속성 컨텍스트에 있는 내부 캐시입니다. 이 캐시는 Map형태로 값을 가지고 있는데요. key는 @Id
로 매핑한 식별자, value는 엔티티 인스턴스입니다. 엔티티를 조회할 때 우선 1차 캐시에 있는지 확인하고 만약에 없으면 데이터베이스에서 조회하는 방식입니다. 1차 캐시에 없어 DB에서 가져온 값은 1차 캐시에 넣은다음 영속 상태로 반환합니다. 그리고 1차 캐시에서 가져온 엔티티는 반복 호출하여도 같은 값 입니다(동일성 보장). 동일성이란 말은 값 뿐만 아니라 저장된 메모리 주소까지 같다는 말입니다.
쓰기지연
엔티티 매니저는 트랜잭션을 커밋하기 직전까지 내부 쿼리 저장소에 SQL를 차곡차곡 모아두고 커밋할 때 데이터베이스에 보냅니다. 이것을 쓰기 지연이라고 합니다. 트랜잭션을 커밋을 할 때 먼저 플러시를 하는데요. 플러시는 등록, 수정, 삭제한 엔티티를 DB에 보내는 작업입니다. 그리고 실제 DB에서 커밋하는 것이죠. 쓰기지연이 가능한 이유는 DB로 전달할 뿐 커밋하지 않기 때문입니다.
그럼 플러시는 매번 이루어지는 것이고 DB앞까지 가는 것이지만 커밋이 안되어서 쓰기지연이 가능했던 것인걸까요?
변경 감지
엔티티 변경사항을 데이터베이스에 자동으로 반영하는 기능을 말합니다. 이것이 가능했던 이유는 1차 캐시 내에 '스냅샷'이라는 부분이 있는데요. 이 스냅샷은 최초 상태를 복사해둬 저장된 값입니다. 그래서 변경시점에 스냅샷 상태와 비교하여 변경된 부분에 한해 수정 쿼리를 쓰기 지연 SQL저장소를 통해 DB에 보내는 것이죠.
변경감지는 영속 상태의 엔티티에만 적용이 됩니다. 즉 저희 프로젝트 내에서는 @Transactional
안에서만 적용이 되는 것입니다. 변경 감지는 쿼리는 모든 필드를 사용하는데요. 그에 대한 장점은 여러 쿼리를 만들 필요가 없다는 점, DB에서도 이전에 파싱된 쿼리를 재사용할 수 있다는 점에서 유리합니다.
이전에 파싱된 쿼리는 무엇을 의미할까요?
플러시
플러시는 영속성 컨텍스트의 변경 내용을 DB에 반영(동기화)하는 일을 하는데요. 구체적으로 변경 감지를 통해 수정 쿼리를 SQL저장소에 등록하고 쓰기 지연 SQL 저장소 쿼리를 DB에 전송하는 일을 합니다. JPA는 기본적으로 커밋을 하기 전에 자동으로 플러시를 호출하는데요. 그 이유는 영속성 컨텍스트의 변경 내용이 까먹고 반영되지 않는 것을 방지하기 위함입니다.
기본적으로 플러시는 트랙잭션 커밋시점 이전에만 실행되면 되고, 엔티티 매니저는 기본적으로 작업단위가 트랜잭션 작업 단위이기 때문에 설정을 통해 플러시 시점을 늦출 수 있습니다. 물론 트랜잭션 커밋시점 이전에 한해서 말이죠.
준영속
준영속을 만드는 방법은 위에서 말한바와 같이 3가지 입니다.
em.detach(entity) : 특정 엔티티만 준영속 상태로 전환
- 1차 캐시, 쓰기 지연 SQL 저장소까지 모든 정보가 제거됩니다.
- 물론 변경 감지도 동작하지 않습니다.
em.clear() : 영속성 컨텍스트를 완전히 초기화
- 모든 엔티티를 detach() 하여 준영속 상태로 만든 것입니다.
em.close() : 영속성 컨텍스트의 종료
- 이것 또한 모든 엔티티를 준영속 상태로 만드는 것인데요. 차이가 있다면 종료되었기 때문에 더이상 1차 캐시, 쓰기 지연 SQL 저장소라는 것이 없다는 것이죠.
준영속 상태의 특징
거의 비영속 상태에 가깝습니다
- 1차 캐시, 쓰기 지연, 변경 감지 등 영속성 기능을 모두 사용할 수 없습니다.
식별자 값을 가지고 있습니다.
- 준영속 상태는 한번 영속 상태였으므로 반드시 식별자 값을 가지고 있습니다.
식별자 값은 어떻게, 어디에서 가지고 있는 걸까요?
지연로딩을 할 수 없습니다.
지연 로딩은 실제 객체 대신 프록시 객체를 로딩해두고 해당 객체를 실제 사용할 때 영속성 컨텍스트를 통해 데이터를 불러오는 방법입니다.
무슨 말일까요?
병합 : merge()
준영속 상태의 엔티티를 다시 영속 상태로 변경하기 위해선 병합을 사용하면 됩니다.
Member mergeMember = em.merge(member); // member는 준영속, mergeMember는 영속
em.contains(member); // false
em.contains(mergeMember); // true
member와 mergetMember는 완전히 다른 상태입니다. member는 merge() 후에도 그대로 준영속 상태로 있습니다. 그리고 병합은 비영속 에티티도 영속 상태로 만들 수 있습니다. 즉, 병합은 준영속, 비영속 신경쓰지 않는 것이죠. 식별자 값으로 엔티티 조회할 수 있으면 불러서 병합하고 조회할 수 없으면 새로 생성해서 병합합니다.
Q&A
준영속 예제는 어떤 것이 있을까요?
- 읽기 전용으로 사용될 수 있습니다.
- 그 값이 더이상 DB에 반영되지 않기 때문입니다.
준영속 식별자 값은 어디에 있을까요?
- Entity안에는 식별자 값이 가지고 있기 때문입니다.
commit 이후엔 close가 자동으로 되는 걸까요?
- 수동으로 구현할 경우 close은 commit시점에 자동으로 실행되지 않습니다.
- 만약 close를 하지않고 그대로 둘 경우 버그를 발생시킬 위험이 있습니다.
지연로딩은 무엇일까요?
플러시와 커밋의 차이는 무엇일까요?
- 플러시는 SQL 저장소의 내용들이 DB로 배달하는 것이고, 커밋은 DB에 반영하는 것입니다.
'Book > programming' 카테고리의 다른 글
[자바 ORM 표준 JPA 프로그래밍] 05장. 연관관계 매핑 기초2 (0) | 2018.12.26 |
---|---|
[자바 ORM 표준 JPA 프로그래밍] 05장. 연관관계 매핑 기초 (0) | 2018.12.26 |
프로가 되기 위한 웹기술 입문3 (2) | 2018.11.09 |
프로가 되기 위한 웹기술 입문2 (0) | 2018.11.08 |
프로가 되기 위한 웹기술 입문1 (0) | 2018.11.07 |