- Entity를 영구 저장하는 환경
- 눈에 보이지 않는 논리적인 개념
- Entity Manager를 통해 접근
Primary Key로 엔티티 구분
영속성 컨텍스트는 엔티티를 테이블의 식별자 값으로 구분한다.
따라서 영속 상태는 식별자 값이 반드시 있어야 한다.
데이터베이스 저장 시점
Entity Manager는 트랜잭션을 commit하기 전까지는 영속성 컨텍스트에 SQL을 저장하고, 트랜잭션을 commit하는 순간 데이터베이스에 저장한다.
장점
영속성 컨텍스트가 엔티티를 관리하면 다음과 같은 장점을 갖는다.
- 1차 캐시
- 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연 가능
- 변경 감지
- 지연 로딩
Member memeber = new Member();
member.setId(1L);
member.setName("kitty");
jpa.persist(member); // 이때 Entity Manager는 데이터베이스에 저장하는 것이 아니라, 영속성 컨텍스트에 insert문을 저장한다.
transaction.commit(); // Entity Manager가 엔티티를 저장하는 시점
jpa.persist(member)
를 할 때, 1차 캐시에 식별자와 엔티티를 캐싱한다.
이때, 엔티티는 아직 데이터베이스에 저장되지 않았다.
Member member = jpa.find(Memeber.class, 1L);
이렇게 엔티티를 조회했을 때도
Entity Manager는 데이터베이스를 먼저 조회하지 않고, 영속성 컨텍스트의 1차 캐시를 먼저 조회한 후
1차 캐시에 엔티티가 있으면 그 값을 가져오고, 1차 캐시에 없을 경우 데이터베이스에 접근한다.
영속성 컨텍스트는 한 transaction 내에서만 존재한다.
Member member1 = jpa.find(Member.class, 1L);
Member member2 = jpa.find(Member.class, 1L);
System.out.println(member1 == member2) // true
member1
을 데이터베이스에서 조회 했을 때, Entity Manager는 영속성 컨텍스트의 1차 캐시에 저장한다. 그리고 member2
를 조회할 때, 이미 같은 식별자 값을 가진 엔티티가 1차 캐시에 있으므로 member2
는 데이터베이스가 아닌 1차 캐시에서 조회된 값이다. 따라서 member1
과 member2
는 같은 엔티티 인스턴스 이므로 System.out.println(member1 == member2)
은 true
를 반환한다.
Entity Manager는 트랜잭션을 commit하기 전까지는 내부 쿼리 저장소에 SQL을 모아둔다.
그리고 commit을 할 때 모아둔 쿼리를 한번에 데이터베이스에 보낸다.
이것을 트랜잭션을 지원하는 쓰기 지연 transactional wirte-behind
이라 한다.
Member member1 = new Member();
Member member2 = new Member();
member1.setId(1L);
member1.setName("rabbit");
member2.setId(2L);
member2.setName("piggy");
// 이 시점에서는 아직 데이터베이스에 저장하지 않고 내부 쿼리 저장소에 쿼리만 저장 (영속화) -> 쓰기지연
jpa.persist(member1);
jpa.persist(member2);
// 저장된 쿼리를 한번에 실행하여 데이터베이스에 저장
transaction.commit();
transaction.commit();
을 하면 Entity Manager는 영속성 컨텍스트를 flush한다.
이것은 버퍼링을 가능하게 하기 때문에, 최적화할 수 있는 여지가 주어진다.
flush
: 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업
Member member = jpa.find(Member.class, 1L);
member.setName("kitty"); // SQL저장소에 update 쿼리 저장
trasaction.commit(); // update 쿼리 실행
JPA로 엔티티를 수정할 때는 단순히 엔티티 조회 -> 필드값 변경 만 하면 된다.
변경한 엔티티를 다시 저장하는 코드를 작성할 필요가 없는 것이다.
변경감지 기능이 실행 될 때, 영속성 컨텍스트에서는 아래와 같은 일이 일어난다.
transaction.commit();
을 하면 Entity Manager 내부에서 flush가 호출된다.- 엔티티와 스냅샷을 비교해서 변경된 엔티티를 찾는다.
- 변경된 엔티티가 있으면 update 쿼리를 생성해 쓰기 지연 SQL 저장소에 쿼리를 저장한다.
- SQL 저장소의 쿼리를 데이터베이스에 보낸다.
- 데이터베이스 트랜잭션을 commit 한다.
💡
스냅샷
: 영속성 컨텍스트에 보관된 엔티티의 최초 상태
-엔티티의 값이 변경되면 위 그림에서 Entity는 같이 변경되지만, 스냅샷은 변경되지 않는다.
변경감지로 실행된 update
문은 변경된 필드만 수정하는 쿼리가 아니라, 모든 필드를 업데이트 하는 쿼리문으로 작성된다.
이것은 데이터 전송량이 증가하는 단점이 있지만, 재사용이 가능하다는 장점을 지닌다.
특별히 필드가 많거나, 저장되는 내용이 너무 클 경우에는 객체에 하이버네이트 확장 기능인 @DynamicUpdate
을 사용하면 된다.
이와 비슷하게, 데이터를 저장할 때 데이터가 존재하는 필드만 저장하는 insert
문을 생성하는 @DynamicInsert
도 있다.
자바 ORM 표준 JPA 프로그래밍 - 기본편
자바 ORM 표준 JPA 프로그래밍(김영한)다정한 피드백 환영해요 🤗