반응형
JPA 에서 가장 중요하다고 생각하는 영속성 컨텍스트에 대해 정리한 글입니다.
영속성 컨텍스트란?
엔티티 매니저로 엔티티를 저장하거나 조회하면 영속성 컨텍스트에 엔티티를 보관하고 관리한다.
한 마디로 요약하면 "엔티티를 영구 저장하는 환경" 이다.
엔티티의 4가지 상태
먼저 엔티티에는 4가지 상태가 존재한다.
- 비영속
- 영속
- 준영속
- 삭제
1. 비영속
Member member = new Member(); // 객체 생성
member.setId("member1");
member.setUsername("회원1");
순수한 객체 상태이며 영속성 컨텍스트나 데이터베이스와는 전혀 관련이 없는 상태
2. 영속
em.persist(member); // 객체를 저장
영속성 컨텍스트에 저장한 상태, 영속 상태로써 영속성 컨텍스트에 의해 관리된다
병합 merge () - 준영속 비영속을 신경 쓰지 않고 식별자 값으로 엔티티를 조회할 수 있으면 부르고 없다면 새로생성해서 병합한다.
Member mergeMember = em.merge(member);
새로운 영속 상태의 엔티티를 반환한다.
매개변수로 넣은 엔티티와 다른 인스턴스를 반환하는 것에 주의!
3. 준영속
// 아래의 메서드들은 준영속 상태로 만든다.
em.detach(member); // 영속성 컨텍스트에서 분리
em.close(); // 영속성 컨텍스트를 닫기
em.clear(); // 영속성 컨텍스트를 초기화
영속성 컨텍스트가 관리하던 영속 상태의 엔티티가 영속성 컨텍스트가 관리하지 않는 상태가 된것
detach(member); 는 특정 엔티티 하나를 준영속 상태로 만들지만
clear(); 는 해당 영속성 컨텍스트의 모든 엔티티를 준영속 상태로 만듬 close(); 도 마찬가지
4. 삭제
em.remove(member); // 삭제
영속성 컨텍스트와 데이터베이스에서 삭제한다.
영속성 컨텍스트의 장점 ( CRUD )
1. 조회 (READ)
1차캐시
- 영속 상태의 엔티티는 모두 이곳에 저장됨
- 키는 @Id로 매핑한 식별자값(데이터베이스 기본 키), 값은 엔티티 인스턴스
- em.find() 를 호출할때 1차 캐시에 찾는 엔티티가 있으면 데이터베이스를 조회하지 않고 메모리에 있는 1차 캐시에서 조회 -> 성능상 이점
동일성 보장
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
// a == b 는 true
- 동일성 비교 시 1차 캐시에서 같은 엔티티 인스턴스가 반환되기 때문에 결과는 true가 반환됨 즉 엔티티의 동일성을 보장한다.
2. 등록 (CREATE)
쓰기 지연
- 트랜잭션 커밋 직전까지 내부 쿼리 저장소에 INSERT SQL을 모아두고 트랜잭션을 커밋하면 영속성 컨텍스트를 플러시 한다.
*플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화 하는 작업 - 모아둔 등록 쿼리를 데이터베이스에 한 번에 전달해서 성능 최적화가 가능함
3. 수정 (UPDATE)
변경 감지 (dirty checking)
- 따로 업데이트 UPDATE SQL 을 보내지 않아도 엔티티의 변경사항을 데이터베이스에 자동으로 반영하는 기능
- 변경 감지는 영속 상태의 엔티티에만 적용됨
- 변경 감지로 인해 실행된 UPDATE SQL은 모든 필드를 업데이트 한다.
- 즉 엔티티의 name 만 변경하더라도 SET NAME = ? , AGE = ? ... 로 쿼리가 생성됨
- @DynamicInsert, @DynamicUpdate 등 어노테이션으로 전략 선택 가능
변경 감지 순서
- 1. JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장해둠(스냅샷)
- 2. 트랜잭션 커밋 시점 플러시가 먼저 호출됨 스냅샷과 엔티티를 비교(변경된 엔티티 탐색)
- 3. 변경된 엔티티가 있으면 수정 쿼리 생성 -> 쓰기 지연
- 4. 모아둔 SQL 을 데이터베이스에 보낸다.
- 5. 트랜잭션 커밋
4. 삭제 (DELETE)
- 엔티티를 삭제하려면 먼저 삭제 대상 엔티티를 조회해야 한다.
Member memberA = em.find(Member.class, "memberA"); // 삭제 대상 엔티티 조회
em.remove(memberA); // 엔티티 삭제
- em.remove() 에 삭제 대상 엔티티를 넘겨주면 등록과 비슷하게 쓰기 지연 SQL에 저장
- em.remove(memberA) 호출 시 영속성 컨텍스트에서 제거되기 때문에 재사용하지 말것
그렇다면 트래잭션 커밋 시점 먼저 호출되는 플러시 란 뭘까?
트랜잭션이 커밋하는 순간 데이터베이스에 반영함(플러시)
- 플러시(flush()) 는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.
- 플러시가 되는 경우 3가지
- em.flush() 직접 호출 (거의 사용하지 않음)
- 트랜잭션 커밋 시 (자동으로 호출)
- SQL을 전달하지 않고 커밋하면 반영되지 않는다.
- 데이터베이스에 변경 내용을 SQL로 전달해서 반영해야 하기 때문
- JPQL 쿼리 실행 시 (자동으로 호출)
- 영속성 컨텍스트에는 있지만 데이터베이스에는 반영되지 않은 엔티티를 조회하는 문제를 방지
- 식별자를 기준으로 조회하는 find() 메소드를 호출할 때는 플러시가 실행되지 않음
- 플러시 모드를 직접 지정할 수도 있는데 보통 기본설정인 AUTO 를 그대로 사용한다.
플러시 정리
플러시는 영속성 컨텍스트에 보관된 엔티티를 지우는 것이 아니라 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화 하는것!
참고자료 - 자바 ORM 표준 JPA 프로그래밍 (김영한)