Persistence Context
에 접근하여 Entity 객체
들을 조작하기 위해서는 Entity Manager
가 필요하다.
이름 그대로, @Entity
어노테이션을 달고 있는 Entity
객체들을 관리하며 실제 DB 테이블과 매핑하여 데이터를 조회/수정/저장 하는 중요한 기능을 수행한다.
Entity Manager
는 Persistence Context
라는 논리적 영역을 두어, 내부적으로 Entity의 생애주기를 관리
한다.
Entity Manager는 EntityManagerFactory를 통해 생성하여 사용할 수 있다.
EntityManagerFactory는 일반적으로 DB 하나에 하나만 생성되어 애플리케이션이 동작하는 동안 사용된다.
EntityManagerFactory를 만들기 위해서는 DB에 대한 정보를 전달해야한다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("DB이름");
EntityManager em = emf.createEntityManager();
DB에 대한 정보를 토대로 JPA는 EntityManagerFactory를 생성하여 EntityManager를 생성할 수 있다.
Entity
를 영구 저장하는 환경이라고 생각하면 된다.
애플리케이션과 DB 사이에서 객체를 보관하는 가상의 DB 역할을 한다.
Entity Manager
를 통해 Entity
를 저장하거나 조회하면 Entity Manager
는 Persistence Context
에 Entity
를 보관하고 관리한다.
Persistence Context
와 전혀 관계가 없는 새로운 상태를 의미한다.
즉, 처음 생성되어 Entity Manager
의 관리를 받지 않은 상태이다.
객체를 생성한 상태
Member member = new Member();
member.setId(1);
member.setUsername("엄준식");
Persistence Context
에 관리되는 상태를 의미한다.
말 그대로, Entity Manager
에게 관리되고 있는 상태이다.
EntityManager에서 persist() 메서드를 호출하게 되면 이 상태로 놓이게 된다.
persist() 메서드를 호출하게 되면 Persistence Context
내부에 데이터가 저장된 일종의 캐싱 상태에 가까우며, DB에 실제로 데이터가 저장된 것은 아니다.
EntityManager em = EntityManagerFactory.createEntityManager(); // Entity 매니저 생성
EntityTransaction et = em.getTransaction(); // EntityTransaction을 가져온다.
et.begin(); // 트랜잭션 시작
try {
Member member = new Member();
member.setId(1);
member.setUsername("엄준식");
em.persist(member); // Persistence Context에 저장한다.
et.commit(); // 오류가 발생하지 않고 정상적으로 하면 commit을 호출
} catch(Exception e) {
et.rollback(); // 오류 발생 -> 롤백
}
...
Entity Manager
가 더 이상 관리하지 않은 상태로,Managed 상태
에서Entity Manager
로 부터 분리된 상태이다.
em.detach(member); // member 엔티티를 Persistence Context에서 분리
Entity
만 Detached 상태
로 전환한다.em.clear();
Persistence Context
안을 완전히 초기화 한다.
Persistence Context
의 모든 Entity
를 Detached
상태로 전환한다.
그렇다고 Persistence Context
를 이용할 수 없는 것은 아닌, 내용이 비워진 거 뿐이다.
em.close();
Persistence Context
를 종료한다.
해당 Persistence Context
가 관리하던 Entity
들은 모두 Detached 상태
로 변경한다.
clear()와는 다르게 Persistence Context
가 종료되어버려서 더이상 사용이 불가능하다.
Detached 상태에서 다시 Managed 상태로 변경하려면?
em.merge(member);
전달받은 Entity
를 사용하여 새로운 영속 상태의 Entity
를 반환한다.
Persistence Context
에 데이터가 없다면?
Entity
를 Persistence Context
에 저장한 뒤, Entity값을 사용하여 병합한다, 그 뒤에 Update SQL을 수행만약 DB에도 없다면?
Entity
를 Persistence Context
에 저장하고 DB에 Insert SQL을 수행
Entity Manager
의Persistence Context
에서 삭제된 상태이다.
Removed 상태가 되면 DB에 맵핑된 데이터도 함께 제거된다.
em.remove(member);
Persistence Context
는Entity 객체
를 효율적으로 쉽게 관리하기 위해 만들어진 공간이다.
어떻게 효율적으로 관리할 수 있을까?
1차 캐시
동일성(identity) 보장
쓰기 지연
변경 감지
Persistence Context
는 내부적으로 캐시 저장소를 가지고 있다.
Persistence Context
에 등록된 Entity 객체
의 @Id
어노테이션이 붙은 필드값을 사용하여 Map<Id,Entity>
형태로 저장하게 된다.
따라서 Persistence Context
는 캐시 저장소 Key
에 저장된 식별자 값을 사용하여 Entity 객체
를 구분한다.
어플리케이션에서 데이터 조회를 요청하면 EntityManager
는 먼저 내부에 캐싱된 Entity
가 있는지 확인을 한다.
만약 데이터가 존재한다면 DB에 쿼리를 전송하지 않으며, 캐싱된 Entity를 반환한다.
반대로 데이터가 존재하지 않으면 DB에 쿼리를 전송하여, 내부의 데이터를 캐싱한다.
이를 통해 쿼리 성능을 최적화한다.
EntityManager
는 내부적으로 Entity
를 저장한다.
따라서 동일한 Entity
를 요청할 때는 동일한 Entity
를 반환함을 보장한다.
Entity Manager
는 데이터 변경 시 반드시 트랜잭션
을 시작해야 한다.
Entity Manager
는 실제 트랜잭션이 커밋되기 전까지 발생한 쿼리를 모아서, commit()
이 호출될 때 한번에 전달한다.
commit()
후 추가적인 동작이 있는데, 바로 flush()
메서드의 호출이다.flush()
메서드는 Persistence Context
의 변경 내용들을 DB에 반영하는 역할을 수행한다.만약 데이터를 처리하는 과정에서 의도치 않게 에러가 발생할 경우 roll-back
에 용이하다.
추가
쓰기 지연 저장소
를 만들어 SQL을 모아두고 있다가 트랜잭션
commit후 한번에 DB에 반영을 한다.EntityManager em = EntityManagerFactory.createEntityManager();
EntityTransaction et = em.getTransaction();
et.begin();
Member member = em.find(Memo.class, "memo");
member.setUsername("memo1");
member.setContents("내용변경");
et.commit();
Entity Manager
에 등록된 Entity
에 수정이 발생하면, 트랜잭션
이 커밋되는 시점에 1차 캐시 와의 비교를 통해 변경사항을 감지하여 Update 쿼리를 자동으로 실행한다.
변경 감지에 대해 순서를 말하면
Persistence Context의 1차 캐시에는 memo의 초기 데이터가 저장되어 있을 것이다.
이후 set 메서드를 통해 데이터를 변경한다.
트랜잭션 커밋 시 flush() 메서드가 호출되면서 1차 캐시에 엔티티와 비교하여 변경에 대한 감지를 한다.
이후 SQL Update 쿼리를 생성하여 쓰기 지연 SQL 저장소에 쿼리를 보낸다.
DB에 저장된 데이터를 수정한다.