JPA-영속성 관리
JPA에서 가장 중요한 2가지
• 객체와 관계형 데이터베이스 매핑하기 (Object Relational Mapping)
• 영속성 컨텍스트
엔티티 매니저 팩토리와 엔티티 매니저
영속성 컨텍스트
• JPA를 이해하는데 가장 중요한 용어
• “엔티티를 영구 저장하는 환경”이라는 뜻 • EntityManager.persist(entity);
엔티티 매니저? 영속성 컨텍스트?
• 영속성 컨텍스트는 논리적인 개념
• 눈에 보이지 않는다.
• 엔티티 매니저를 통해서 영속성 컨텍스트에 접근
엔티티의 생명주기
• 비영속 (new/transient) 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
• 영속 (managed) 영속성 컨텍스트에 관리되는 상태
• 준영속 (detached) 영속성 컨텍스트에 저장되었다가 분리된 상태
• 삭제 (removed) 삭제된 상태
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername(“회원1”);
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//객체를 저장한 상태(영속)
em.persist(member);
준영속, 삭제
//회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
//객체를 삭제한 상태(삭제)
em.remove(member);
영속성 컨텍스트의 이점
• 1차 캐시 • 동일성(identity) 보장
• 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
• 변경 감지(Dirty Checking)
• 지연 로딩(Lazy Loading)
//엔티티를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//엔티티를 영속 em.persist(member);
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//1차 캐시에 저장됨 em.persist(member);
//1차 캐시에서 조회 Member findMember = em.find(Member.class, "member1");
데이터베이스에서 조회
Member findMember2 = em.find(Member.class, "member2");
여기서 em.find(Member.class, "member2");을 실행하면 1차 캐시에 member2
가 없으므로 데이터베이스 에서 조회한다. 조회한 데이터로 member2엔티티를 생성해서 1차캐시에 저장한다.
1차캐시에 저장하고 조회한 엔티티를 반환하게 된다.
1차 캐시에 있는 member1, member2 이후 조회할때 select쿼리 없이 바로 불러 올 수 있다.
-
>성능상 이점을 가져올 수 있다.
영속 엔티티의 동일성 보장
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true
1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션
격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공
영속성 컨텍스트는 성능상 이점과 엔티티의 동일성을 보장한다.
엔티티 등록 트랜잭션을 지원하는 쓰기 지연
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA); em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋
커밋하는 순간 쿼리가 날라간다.
1차 캐시에 저장되면서 쓰기 지연 SQL 저장소에 SQL문이 저장된다.
이후 commit이나 em.flush 나 JpqL이 실행될때 SQL 쿼리문이 DB로 전달된다.
엔티티 수정 변경 감지
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // [트랜잭션] 시작
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
//em.update(member) 이런 코드가 있어야 하지 않을까?
transaction.commit(); // [트랜잭션] 커밋
1.트랜잭션을 커밋하면 앤티티 매니저 내부에서 먼저 플러시가 호출된다.
2.엔티티와 스냅샷을 비교해서 변경된 앤티티를 찾는다
3.변경된 앤티티가 있으면 update 쿼리를 생성해서 쓰기 지연 SQL 저장소에 저장한다.
4.쓰기 지연 저장소의 SQL을 데이터베이스에 보낸다.
5.데이터베이스 트랜잭션을 커밋한다.
변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용된다.
//em.update(member) -> 쿼리를 예상하면
UPDATE MEMBER
SET
NAME=?,
AGE=?
WHERE
id=?
으로 예상되지만
UPDATE MEMBER
SET
NAME=?,
AGE=?,
GRADE=?
WHERE
id=?
처럼 모든 필드를 업데이트한다.
JPA의 기본전략은 모든 필드를 업데이트한다.
엔티티 삭제
//삭제 대상 엔티티 조회
Member memberA = em.find(Member.class, “memberA");
em.remove(memberA); //엔티티 삭제
플러시
영속성 컨텍스트의 변경내용을 데이터베이스에 반영
플러시 발생
• 변경 감지
• 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
• 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (등록, 수정, 삭제 쿼리)
영속성 컨텍스트를 플러시하는 방법
• em.flush() - 직접 호출
• 트랜잭션 커밋 - 플러시 자동 호출
• JPQL 쿼리 실행 - 플러시 자동 호출
플러시 모드 옵션
.• FlushModeType.AUTO 커밋이나 쿼리를 실행할 때 플러시 (기본값)
• FlushModeType.COMMIT 커밋할 때만 플러시
플러시는!
• 영속성 컨텍스트를 비우지 않음
• 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화
• 트랜잭션이라는 작업 단위가 중요 -> 커밋 직전에만 동기화 하면 됨
준영속 상태
• 영속 -> 준영속
• 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)
• 영속성 컨텍스트가 제공하는 기능을 사용 못함
준영속 상태로 만드는 방법
• em.detach(entity) 특정 엔티티만 준영속 상태로 전환
• em.clear() 영속성 컨텍스트를 완전히 초기화
• em.close() 영속성 컨텍스트를 종