영속성 관리
3. 영속성 관리
3.1 엔티티 매니저 팩토리와 엔티티 매니저
- 엔티티 매니저 팩토리
- 엔티티 매니저를 만드는 오브젝트이다.
- META-INF/persistence.xml의 정보를 바탕으로 EntityManagerFactory 오브젝트를 생성한다.
- 스레드 간에 공유해도 된다.
- 엔티티 매니저
- 여러 스레드가 동시에 접근하면 동시성 문제가 발생한다.
- 스레드 간에 공유하면 안된다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
<persistence-unit name="jpabook">
<properties>
...
</properties>
</persistence-unit>
3.2 영속성 컨텍스트란?
- 영속성 컨텍스트는 ‘엔티티를 영구 저장하는 환경’ 으로 해석된다.
- 엔티티 매니저를 통해서 영속성 컨텍스트에 접근, 관리 할 수 있다.
- 엔티티 매니저의 persist() 메소드는 엔티티를 영속성 컨텍스트에 저장한다.
3.3 엔티티의 생명주기
- 비영속
- 엔티티 객체가 생성된 상태이다.
- 영속성 컨텍스트와 관련이 없는 상태이다.
- 일반적으로 식별자 값이 할당되지 않은 상태이다.
Member member = new Member();
member.setUsername("회원1");
- 영속
- 엔티티가 영속성 컨텍스트에 의해 관리되는 상태이다.
em.persist(member);
Member member = em.find(Member.class, memberId);
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
- 준영속
- 영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않는 상태이다.
- 엔티티 객체에 식별자 값이 있는 상태이다.
em.detach(member);
em.close();
em.clear();
- 삭제
- 엔티티가 영속성 컨텍스트에서 삭제된 상태이다.
- 엔티티가 데이터베이스에서 제거된다.
em.remove(member);
3.4 영속성 컨텍스트의 특징
- 영속성 컨텍스트와 식별자 값
- 영속성 컨텍스트는 엔티티를 식별자 값으로 구분한다. (식별자 값이란 @Id로 기본 키와 매핑한 값을 말한다.)
- 따라서 영속 상태는 식별자 값이 반드시 있어야 한다.
- 영속성 컨텍스트와 데이터베이스 저장
- 영속성 컨텍스트에 엔티티를 저장하는 시점과 데이터베이스에 저장하는 시점은 다르다.
- 플러시를 하면 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영한다.
- 영속성 컨텍스트가 엔티티를 관리했을 때 얻을 수 있는 장점은 다음과 같다.
- 1차 캐시
- 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연
- 변경 감지
- 지연 로딩
3.4.1 엔티티 조회
- 1차 캐시
- 영속성 컨텍스트 내부에 캐시가 있는데, 영속상태의 엔티티가 저장된다.
- Map에 키는 식별자이고 값은 엔티티 인스턴스인 형태로 저장한다.
- em.find() 호출시
- 1차 캐시에서 식별자 값으로 엔티티를 찾는다. 만약 엔티티가 있으면 데이터베이스를 조회하지 않고 1차 캐시에서 조회한다.
- 1차 캐시에 엔티티가 없으면 데이터베이스를 조회해서 엔티티를 생성하여 1차 캐시에 저장한다. 그리고 엔티티를 반환한다.
- 식별자가 같은 엔티티를 반복해서 호출했을 때 1차 캐시에 있는 같은 엔티티가 반환되므로 동일성이 보장된다.
3.4.2 엔티티 등록
- em.persist() 호출시
- 1차 캐시에 식별자와 엔티티를 키와 값으로 저장한다.
- 등록 쿼리를 생성하여 쓰기 지연 SQL 저장소에 저장한다.
- 트랜잭션 커밋시 영속성 컨텍스트를 플러시 하면서 데이터베이스에 엔티티가 저장된다.(이 때 쿼리가 전송된다.)
- 단, 식별자 자동 생성 사용시 IDENTITY 전략을 사용하면 persist 메소드 호출 뒤에 쿼리가 전송된다.
- 트랜잭션 커밋 직전에만 데이터베이스에 SQL을 전송하면 되므로, 트랜잭션을 지원하는 쓰기 지연이 가능하다.
3.4.3 엔티티 수정
- 엔티티를 조회해서 데이터를 변경하면 된다.
- 변경 감지(dirty checking) 기능에 의해 엔티티의 변경사항이 자동으로 데이터베이스에 반영된다.
- 변경 감지
- JPA가 엔티티를 영속성 컨텍스트에 저장할 때 최초 상태를 복사해서 저장해둔다. (이를 스냅샷이라고 부른다.)
- 플러시 시점에 스냅샷과 엔티티를 비교해서 변경된 엔티티를 찾는다.
- 변경된 엔티티가 있으면 수정 쿼리를 생성해서 쓰기 지연 SQL 저장소에 저장한다.
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송한다.
- 변경 감지로 인해 실행된 Update SQL은 모든 필드를 업데이트한다.
3.4.4 엔티티 삭제
- em.find()로 삭제 대상 엔티티를 먼저 조회한다.
- em.remove()로 삭제 대상 엔티티를 넘겨준다.
- 영속성 컨텍스트에서 제거된다.
- 삭제 쿼리가 쓰기 지연 SQL 저장소에 저장된다.
3.5 플러시
- 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.
- 변경 감지가 동작해서 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교해서 수정된 엔티티를 찾는다.
- 수정된 엔티티는 수정 쿼리를 만들어 쓰기 지연 SQL 저장소에 등록한다.
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송한다.(등록, 수정, 삭제 쿼리)
- 플러시는 3가지 방법으로 할 수 있다.
- em.flush()를 호출한다.
- 트랜잭션 커밋 시 플러시가 자동 호출된다.
- JPQL 쿼리 실행 시 플러시가 자동 호출된다.
- JPQL 쿼리 실행 전 영속상태의 엔티티들을 데이터베이스에 반영해야 JPQL 쿼리가 제대로 동작하기 때문이다.
try {
// 비영속
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
em.persist(member);
// 플러시 자동 호출됨
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
for (Member m : members) {
System.out.println("m = " + m);
}
tx.commit();
}
catch (Exception e) {
tx.rollback();
e.printStackTrace();
}
finally {
em.close();
}
emf.close();
3.5.1 플러시 모드 옵션
- FlushModeType.AUTO: 커밋이나 쿼리를 실행할 때 플러시(기본값)
- FlushModeType.COMMIT: 커밋할 때만 플러시
em.setFlushMode(FlushModeType.AUTO);
em.setFlushMode(FlushModeType.COMMIT);
3.6 준영속
- 영속성 컨텍스트가 관리하는 영속 상태의 엔티티가 영속성 컨텍스트에서 분리된 것을 준영속 상태라고 한다.
- 준영속 상태의 엔티티는 영속성 컨텍스트가 제공하는 기능을 사용할 수 없다.
3.6.1 detach()
- detach() 메소드를 호출하는 순간 1차 캐시에서 엔티티가 제거된다. 쓰기 지연 SQL 저장소의 해당 엔티티의 쿼리도 제거된다.
- 따라서 아래 코드에서 member는 데이터베이스에 저장되지 않는다.
tx.begin();
try {
// 비영속
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
// 영속
em.persist(member);
// 준영속
em.detach(member);
tx.commit();
}
catch (Exception e) {
tx.rollback();
e.printStackTrace();
}
finally {
em.close();
}
emf.close();
3.6.2 clear()
- clear() 메소드는 영속성 컨텍스트를 초기화하여 해당 영속성 컨텍스트의 모든 엔티티를 준영속 상태로 만든다.
3.6.3 close()
- close() 메소드는 영속성 컨텍스트를 종료한다.
3.6.4 준영속 상태의 특징
- 거의 비영속 상태에 가깝다.
- 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않는다.
- 식별자 값을 가지고 있다.
- 이미 한 번 영속 상태였으므로 식별자 값을 가지고 있다.
- 지연 로딩을 할 수 없다.
- 엔티티가 다른 객체를 참조할 경우 프록시 객체를 로딩해두고, 해당 객체를 실제 사용할 때 영속성 컨텍스트를 통해 데이터를 불러오는 방법이다. 준영속 상태에서 지연 로딩시 문제가 발생한다.
3.6.5 merge()
- 준영속 상태의 엔티티, 비영속 상태의 엔티티를 영속 상태로 변경한다.
- 준영속 병합
- 파라미터로 넘어온 준영속 엔티티의 식별자 값으로 1차 캐시에서 엔티티를 조회한다.
- 1차 캐시에 엔티티가 없으면 데이터베이스에서 엔티티를 조회하고 1차 캐시에 저장한다.
- 조회한 엔티티에 member 엔티티의 값을 채워넣는다.
- 영속 상태의 member를 반환한다. (파라미터로 넘어온 준영속 엔티티는 준영속으로 남아있다.)
- 비영속 병합
- 파라미터로 넘어온 엔티티의 식별자 값으로 영속성 컨텍스트를 조회하고 없으면 데이터베이스에서 조회한다.
- 데이터베이스에서도 찾지 못하면 새로운 엔티티를 생성해서 병합한다.
try {
// 비영속
Member member = new Member();
member.setUsername("member1");
member.setAge(10);
// 영속
em.persist(member);
// 준영속
em.flush();
em.clear();
System.out.println(em.contains(member)); // false
member = em.merge(member);
System.out.println(em.contains(member)); // true
tx.commit();
}
catch (Exception e) {
tx.rollback();
e.printStackTrace();
}
finally {
em.close();
}
emf.close();
Leave a comment