영속성 관리
영속성 컨텍스트 (Persistence Context)
영속성 컨텍스트란 "엔티티를 영구 저장하는 환경”이다.
영속성 컨텍스트는 논리적인 개념이기에 눈에 보이지 않는다.
EntityManager를 통해서 영속성 컨텍스트에 접근한다. EntityManager.persist(entity);
영속성 컨텍스트 엔티티의 생명주기
1. 비영속
- 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
- 객체를 생성한 상태
Member member = new Member();
member.setId(1L);
member.setName("KHM");
2. 영속
- 영속성 컨텍스트에 관리되는 상태
- em.persist() 또는 em.find()
- 객체가 영속된다고 데이터베이스에 바로 쿼리를 날리지 않는다. 트랜잭션이 commit() 되는 시점에 쿼리가 날라간다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(member); //영속성 상태를 얻는다.
3. 준영속
- 영속성 컨텍스트에 저장되었다가 분리된 상태 즉, 영속성 컨텍스트가 관리하던 엔티티를 관리하지 않는 상태이다.
- em.detach() 또는 em.clear() 또는 em.close()
em.detach(member); //member 엔티티의 영속성을 없앤다.
em.clear(); //영속성 컨텍스트를 초기화한다.
em.close(); //영속성 컨텍스트를 닫는다.
4. 삭제
- 엔티티를 영속성 컨텍스트와 데이터 베이스에서 삭제한 상태이다.
- em.remove()
em.remove(member);
em.persist(member)
- 1차 캐시에 저장한다.
- 영속성 컨텍스트에 의해 관리되는 상태
- tx.commit()를 호출해야 데이터 베이스에 저장된다.
영속성 컨텍스트의 이점
1차 캐시
em.find()를 호출했는데 엔티티가 1차 캐시에 없으면 엔티티 매니저는 데이터베이스를 조회해서 엔티티를 생성한다. 그리고 1차 캐시에 저장한 후, 영속 상태의 엔티티를 반환한다.
1차 캐시에 엔티티가 있다면 DB를 조회하지 않고 반환하여 성능이 향상된다.
1차 캐시는 현재 트랙잭션이 끝나면 초기화된다.
영속 엔티티의 동일성 보장
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true
같은 트랙잭션안에서 같은 id로 조회한 객체는 동일성이 보장된다.
Member a는 1차캐시에 찾는 엔티티 객체가 있다면 그 객체를 받고 만약 없다면 DB에서 엔티티 객체를 가져와서 1차캐시에 저장한 후 받는다.
Member b는 1차캐시에 있는 엔티티 객체를 받는다.
쓰기 지연
엔티티 매니저는 트랜잭션을 커밋하기 직전(tx.commit())까지 데이터베이스에 엔티티를 저장하지 않고 쓰기 지연 SQL 저장소에 INSERT SQL(em.persist())을 모아둔다. 그리고 트랜잭션을 커밋할 때 모아둔 쿼리를 데이터베이스에 보낸다. 이를 쓰기 지연이라고 한다.
변경 감지
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
JPA에서 엔티티 객체를 수정할 때는 단순히 객체를 조회해서 데이터를 변경하면 된다.
준영속 엔티티를 수정하는 2가지 방법 중 하나이다. 엔티티를 변경할 때는 무조건 변경감지를 이용하여 변경하자!
병합( merge ) 사용
em.merge(member)
- 데이터 베이스에 있는 member(영속)와 병합한다. 기본키와 매핑하여 데이터 베이스에 있는 객체(영속)를 찾아서 member(준영속)의 값으로 병합한다.
병합시 동작방식
- 준영속 엔티티의 식별자 값으로 영속 엔티티를 조회한다.
- 영속 엔티티의 값을 준영속 엔티티의 값으로 모두 교체한다.(병합한다.)
- 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 데이터베이스에 UPDATE SQL이 실행
준영속 엔티티를 수정하는 2가지 방법 중 하나이다.
스냅샷 : JPA는 엔티티를 영속성 컨텍스트에 보관할때 최초 상태를 복사해서 저장해둔 것을 의미한다.
플러시 (flush)
쓰기 지연 SQL 저장소의 쿼리(등록, 수정, 삭제)를 데이터베이스에 즉시 전송하고 비운다.
- 영속성 컨텍스트(1차 캐시)를 비우지 않고 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화
- 트랜잭션이라는 작업 단위가 중요 => 커밋 직전에만 동기화 하면 된다.
- 플러시를 하면 엔티티와 스냅샷이 같아진다.
영속성 컨텍스트를 플러시하는 방법
1. em.flush() - 플러시 직접 호출
2. 트랜잭션 커밋 - 플러시 자동 호출
3. JPQL 쿼리 실행 - 플러시 자동 호출
준영속 상태
영속성 컨텍스트가 더는 관리하지 않는 상태
영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)된 상태이다.
임의로 만들어낸 엔티티도 기존 식별자를 가지고 있으면 준영속 엔티티로 볼 수 있다. (DB에 id(pk)가 1인 Item 객체가 있는데 임의로 id가 1인 Item 객체를 생성한다면 이는 준영속 엔티티이다.)
Member memberA = em.find(Member.class, "memberA");
em.detach(memberA); // 준영속 상태로 변경
transation.commit();
준영속 상태가 되면 영속성 컨텍스트가 지원하는 어떤 기능도 동작하지 않음과 동시에 쓰기 지연 SQL 저장소의 INSERT SQL도 제거되어서 데이터베이스에 저장되지 않는다.
준영속 상태로 만드는 방법
- em.detach(entity) : 특정 엔티티만 준영속 상태로 전환
- em.clear() : 영속성 컨텍스트를 완전히 초기화
- em.close() : 영속성 컨텍스트를 종료