Persistence Context는 데이터베이스와의 통신을 담당하는 영속성 유닛(Persistence Unit)의 실행 컨텍스트(Context)로서, JPA에서 엔티티 객체의 생명주기를 관리하고 영속성을 보장한다.
Persistence Context는 엔티티 매니저(Entity Manager)를 통해 생성된다. 엔티티 매니저는 데이터베이스와의 연결을 담당하고, Persistence Context를 생성하여 엔티티 객체를 관리한다. 엔티티 매니저는 Persistence Context에 접근하여 엔티티 객체를 검색, 생성, 수정, 삭제할 수 있다.
JPA에서 관리되는 엔티티 객체가 가질 수 있는 상태를 의미한다. JPA에서 엔티티 객체의 상태는 총 4가지로 정의되어 있으며, 이를 Entity LifeCycle이라고 한다.
Member member = new Member();
엔티티 객체가 생성된 직후의 상태이다. 이때 엔티티 객체는 JPA에서 관리되지 않으며, 데이터베이스와도 관계가 없다. 즉, 아직 영속성 컨텍스트에 등록되지 않은 상태이다. 이러한 엔티티 매니저 에 의해 관리되지 않는 POJO객체를 말한다.
//persist()를 통해 영속 컨텍스트에 등록
Member member = new Member();
member.setMemberId(1);
member.setName("홍길동");
entityManager.persist(member);
---
//find()를 통해 DB에서 영속성 컨텍스트로 등록
member = em.find(member.class, 1);
엔티티 객체가 영속성 컨텍스트에 등록된 상태이다. 영속성 컨텍스트에서 관리되는 엔티티 객체는 데이터베이스와 동기화되어 있으며, 엔티티 객체가 수정되면 자동으로 데이터베이스도 변경된다.
엔티티 인스턴스가 persist() 메서드를 통해 엔티티 매니저의 영속성에 등록되거나, find() 메서드 혹은 QueryDSL 메서드 호출로 DB에서 반환된 경우 영속상태로 분류한다. 영속 상태의 엔티티는 1차 캐시에 식별자를 포함하여 저장된다.
엔티티 객체가 영속성 컨텍스트에 존재했다가 분리된 상태이다. 이때 엔티티 객체는 더 이상 영속성 컨텍스트에서 관리되지 않으며, 데이터베이스와의 동기화도 이루어지지 않는다. 따라서, Detached 상태의 엔티티 객체를 수정하더라도 데이터베이스에는 반영되지 않는다.
비영속 상태와 비슷하지만 다음과 같은 차이점이 있다.
// member 객체의 영속성 해제
entityManager.detach(member);
// 해당 매니저의 영속성 컨텍스트 내 모든 영속성 해제
entityManager.clear();
// member 객체의 속성으로 새로운 영속성 컨텍스트 내 엔티티를 만들어 반환
// 반환 후 원래의 레퍼런스에 저장
member = entityManager.merge(member);
//영속성 컨텍스트 종료(제거)
entityManager.close();
엔티티 객체가 영속성 컨텍스트에서 삭제된 상태이다. 이때 엔티티 객체는 데이터베이스와 동기화되어 삭제된다.
// member 제거
entityManager.remove(member);
JPA에서는 엔티티 객체의 상태 변화를 추적하기 위해 Entity LifeCycle을 관리한다. 이를 통해 영속성 컨텍스트에서 관리되는 엔티티 객체의 데이터 일관성을 유지하고, 데이터베이스와의 동기화를 보장할 수 있다.
영속성 컨텍스트(Persistence Context) 내부에 존재하는 캐시를 의미한다. 즉, JPA가 데이터베이스에서 조회한 엔티티 객체를 영속성 컨텍스트 내부에 저장해두었다가, 동일한 엔티티 객체가 필요한 경우에는 데이터베이스에 접근하지 않고 캐시에서 먼저 찾아서 반환하는 기능이다. 1차 캐시를 사용하면 조회한 엔티티 객체를 재사용하면서 성능을 최적화할 수 있다.
JPA는 1차 캐시를 통해 동일한 엔티티 객체를 반복해서 조회하는 경우 SELECT 쿼리를 중복해서 수행하는 것을 방지한다. JPA는 동일한 @Id 값을 가진 엔티티 객체를 1차 캐시에서 먼저 찾아서 반환하므로, 데이터베이스에 접근하지 않아도 되는 것이다. 따라서, 같은 엔티티 객체를 반복해서 조회하는 경우 SELECT 쿼리를 중복해서 수행하는 것을 방지할 수 있다.
JPA는 1차 캐시를 사용하여 조회한 엔티티 객체에 대해 동일성을 보장한다. 즉, 같은 @Id 값을 가진 엔티티 객체는 1차 캐시에서 하나의 객체만 존재하므로, 같은 엔티티 객체를 참조하는 경우에도 항상 동일한 객체를 참조하게 된다. 이는 JPA에서 엔티티 객체를 수정할 때도 중요한 역할을 한다. JPA는 1차 캐시에 저장된 엔티티 객체를 수정하면 자동으로 데이터베이스에 변경 사항을 반영하므로, 동일한 엔티티 객체에 대해서는 데이터 일관성을 보장할 수 있다.
JPA에서는 트랜잭션을 이용하여 쓰기 지연 기능을 제공한다. 쓰기 지연은 JPA에서 엔티티를 수정, 추가, 삭제할 때, 엔티티를 즉시 데이터베이스에 반영하지 않고, 트랜잭션을 flush() 또는 commit()할 때까지 대기하는 기능이다.
예를 들어, 엔티티 매니저를 사용하여 엔티티를 변경하고, 그 변경 내용을 데이터베이스에 반영하는 메소드를 호출하더라도, JPA는 변경 내용을 즉시 데이터베이스에 반영하지 않는다. 대신, JPA는 트랜잭션 내에서 변경된 엔티티를 모아두고, 트랜잭션이 커밋될 때 모아둔 변경 내용을 한 번에 데이터베이스에 반영한다. 이렇게 하면 데이터베이스 쓰기 작업이 한 번만 일어나므로, 성능 향상에 도움이 된다.
쓰기 지연은 또한 엔티티의 상태 변화 추적을 가능하게 하므로, 데이터 일관성 유지에 도움을 준다. 예를 들어, 엔티티를 삭제할 때 즉시 데이터베이스에서 삭제하지 않고, 쓰기 지연 기능을 이용하여 삭제한 후에도 엔티티를 참조하는 다른 엔티티가 있는 경우, 해당 엔티티가 참조하던 엔티티가 삭제된 것을 확인할 수 있다. 이렇게 하면 데이터 일관성을 유지할 수 있다.
flush()는 JPA의 영속성 컨텍스트를 DB와 동기화하는 작업을 말한다. EntityManager.flush()
가 수행되어야 쿼리 저장소에 있는 DB를 보내는 것이다. 이 때 Dirty Checking를 통해 영속상태의 엔티티 중 속성이 변경된 엔티티를 Update한다.
트랜잭션을 begin()
으로 수행한 뒤 commit()
하여 트랜잭션을 종료해야한다. commit()
을 수행하게 되면 내부적으로 EntityManager의 flush()
메서드를 호출한 후 트랜잭션을 닫는다.
flush()
는 쿼리를 전송하는 역할이고 commit()
은 내부적으로 flush()
를 수행한 뒤 트랜잭션을 끝내는 역할이다.
즉 flush()
로 전송된 쿼리는 rollback()
이 가능하지만 commit()
은 트랜잭션을 끝내므로 rollback()
이 불가능하다.
JPA는 EntityManager가 엔티티를 저장/조회/수정/삭제를 진행한다. 그런데 엔티티 매니저의 메서드를 찾아보면, persist(저장), find(조회), delete(삭제)로 update에 해당되는 메서드가 존재하지 않는다. 대신 JPA는 update에 해당하는 더티 체킹(Dirty Checking)을 지원한다.
더티 체킹은 Transaction 안에서 엔티티의 변경이 일어나면, 변경된 내용을 자동으로 데이터베이스에 반영하는 JPA 특징이다.
또한, 영속성 컨택스트(Persistence Context)안에 있는 엔티티를 대상으로 더티 체킹이 일어난다. 여기서 Dirty란 "엔티티 데이터의 변경된 부분"으로 해석하면 된다. 즉, 변경된 부분을 체크해서 DB에 반영한다는 뜻으로 해석한다.
더티 체킹이 일어나는 환경은 아래 두 가지 조건이 충족되어야 한다.
Transaction은 두 가지 방식으로 사용할 수 있다.
@Entity
@Getter @Setter
@Table(name = "user")
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
}
User user = new User();
user.setName("John");
user.setEmail("john@example.com");
new 키워드를 사용하여 user
객체를 생성한다. 현재는 비영속 상태이며 아직 데이터베이스에 반영되지 않는다.
EntityManager em = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
entityManager.persist(user);
EntityManager가 트랜잭션을 시작하고, user
객체를 persist() 메서드를 사용하여 영속화 시킨다.
영속성 컨텍스트 내에는 1차 캐시가 존재하여 managed(영속)가 된 entity를 캐시에 저장한다. 저장된 엔티티는 각각의 고유한 식별자를 갖고 있다.
user.setName("David");
user 객체의 name 속성을 변경한다면, JPA는 1차 캐시에 저장된 user
객체를 통해 객체의 상태가 변경되었음을 알게 되고, 이후에 이 객체가 데이터베이스에 저장될 때(flush() 또는 commit()) 이 변경사항도 함께 반영(update query)된다. 이런 과정을 Dirty Checking이라 한다.
따라서, Dirty Checking은 JPA가 엔티티의 상태를 감지하여 데이터베이스에 업데이트할 필요가 있는지 여부를 결정하는 기능이다. 이를 통해 JPA는 개발자가 직접 데이터베이스에 업데이트하는 코드를 작성하지 않아도 되므로 개발 생산성을 높일 수 있다.