[자바 ORM 표준 JPA 프로그래밍] 3.4 영속성 컨텍스트의 특징
개발서적/자바 ORM 표준 JPA

[자바 ORM 표준 JPA 프로그래밍] 3.4 영속성 컨텍스트의 특징

Reference. 자바 ORM 표준 JPA 프로그래밍

책 목차 및 이전 글

  • 영속성 컨텍스트와 식별자 값
    • 엔티티를 식별자 값(@Id로 테이블의 기본키와 매핑한 값)으로 구분
    • 영속 상태는 식별자 값이 반드시 필요 (없으면 예외 발생)
  • 영속성 컨텍스트와 데이터베이스 저장
    • 플러시(flush): JPA는 보통 트랜잭션을 커밋하는 순간
      영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영
  • 영속성 컨텍스트가 엔티티를 관리하면 좋은 장점
    • 1차 캐시
    • 동일성 보장
    • 트랜잭션을 지원하는 쓰기 지연
    • 변경 감지
    • 지연 로딩

3.4.1 엔티티 조회

  • 1차 캐시: 영속성 컨텍스트의 내부 캐쉬를 의미
    • 영속 상태의 엔티티는 모두 이곳에 저장
    • ex) 영속성 컨텍스트 내부에 Map이 있고 키는 @Id의 식별자, 값은 인스턴스
// 엔티티를 생성한 상태(비영속)
Member member = new Member();
member.setId(100L);
member.setUsername("회원1");

// 엔티티 영속
em.persist(member);
  • 영속성 컨텍스트에 데이터를 저장, 조회하는 모든 기준은 데이터베이스 기본 키 값
Member member = em.find(Membr.class, "member1"); //"member1" 엔티티 식별자 값(key값)
조회 실행순서
  1. em.find() 호출
  1. 1차 캐시에 엔티티를 검색
  1. 1차 캐시에 존재 하지 않으면 데이터베이스 조회

1차 캐시에서 조회

  • 코드실행로 보는 실행 순서
Member member = new Member();
member.setId(100L);
member.setUsername("회원1");

// 1차 캐시에 저장됨
em.persist(member);

// 1차 캐시에서 조회
Member findMember = em.find(Member.class, 100L);

데이터베이스에서 조회

  1. 1차 캐시에 없으면 엔티티 매니저는 데이터베이스 조회 후 엔티티를 생성
  1. 1차 캐시에 저장한 후 영속 상태의 엔티티를 반환
그림의 실행 순서
  1. em.find(Member.class, "member2") 를 실행
  1. member2가 1차 캐시에 없으므로 데이터베이스 조회
  1. 조회한 데이터를 member2 엔티티 생성 후 1차 캐시에 저장(영속 상태)
  1. 조회한 엔티티 반환

영속 엔티티의 동일성 보장

  • 영속성 컨텍스트1차 캐시에 있는 같은 엔티티 인스턴스를 반환
  • 영속성 컨텍스트는 성능상 이점과 엔티티 동일성을 보장 * 동일성, 동등성 [ 1.2.4 비교 ] 참조
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");

System.out.println(a == b); // 동일성 인스턴스 비교, 동등성 값 비교

3.4.2 엔티티 등록

  • 엔티티 매니저트랜잭션 커밋하기 직전까지 내부 쿼리 저장소에 insert SQL을 저장
  • 트랜잭션 커밋할 때 모아둔 쿼리를 데이터베이스에 실행, 이것을 쓰기 지연
그림 실행 순서
  1. 엔티티 매니저는 1차 캐시에 회원 엔티티를 저장 후, insert SQL을 SQL 저장소에 보관
  1. 트랜잭션을 커밋하면 엔티티 매니저는 영속성 컨텍스트를 플러시
    • 플러시: 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업
    • 등록, 수정, 삭제를 반영 → 쓰기 지연 SQL 저장소에 모인 쿼리를 반영

트랜잭션을 지원하는 쓰기 지연이 가능한 이유

begin(); //트랜잭션 시작

save(A);
save(B);
save(C);

commit(); //트랜잭션 커밋
로직을 실행하는 2가지 방법
1. 데이터를 저장하는 즉시 등록 쿼리를 데이터 베이스로 이동 마지막에 트랜잭션을 커밋
2. 데이터를 메모리에 저장, 모아둔 등록 쿼리를 데이터베이스에 보낸 후 커밋
  • 둘의 결과는 동일, 커밋 직전에만 데이터베이스에 SQL을 전달하면 결과는 동일
  • 이것이 트랜잭션을 지원하는 쓰기 지연이 가능한 이유
  • 모아둔 등록 쿼리한번전달해서 성능을 최적화 가능

3.4.3 엔티티 수정

SQL 수정 쿼리의 문제점

  • 요구사항이 늘어나며 수정 쿼리도 점점 추가
  • 기존에는 각각의 UPDATE 쿼리를 작성하거나, 하나의 합쳐진 UPDATE 쿼리를 사용
    → 합쳐진 UPDATE 쿼리는 예상치 못한 상황을 발생, 상황에 따라 UPDATE를 계속 추가 했음
  • 수정 쿼리가 많아지고, 로직 분석을 위해 SQL확인 필요하게 되고 SQL의 의존적이게 됌

변경 감지

  • 엔티티를 수정할 때는 엔티티를 조회해서 데이터만 변경하면 됌 ( em.update() 없음 )
  • 변경감지: 엔티티의 변경사항을 데이터베이스에 자동으로 반영하는 기능
transaction.begin();

//영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");

memberA.setUsername("hi");
memberA.setAge(10);

//em.update(member); update 코드가 존재하지 않음

transaction.commit();
  • 스냅샷: 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사해서 저장해두는 것을 의미
  • 변경감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용
    비영속, 준영속은 반영 안됌
그림 실행 순서
  1. 트랜잭션을 커밋하면 엔티티 매니저 내부에서 플러시(flush()) 호출
  1. 엔티티와 스냅샷을 비교변경 엔티티 찾기
  1. 변경된 엔티티가 있으면 수정 쿼리 생성쓰기 지연 SQL 저장소에 전달
  1. 쓰기 지연 SQL 저장소의 SQL을 데이터베이스 전달
  1. 데이터베이스 트랜잭션 커밋

 

  • 엔티티의 변경된 부분만 수정하는 것이 아니라 엔티티의 모든 필드를 수정
    <단점>
    • 데이터베이스로 보내는 데이터 전송량이 증가하는 단점
    <장점>
    • 수정 쿼리가 항상 동일, 애플리케이션 로딩 시점에 따라 미리 생성 및 재사용 가능
    • 데이터베이스에 동일한 쿼리를 보내면 이전에 파싱된 쿼리를 재사용 가능
--예상되는 쿼리
UPDATE MEMBER
SET 
	NAME=?,
	AGE=?
WHERE 
	id=?
--실제실행 쿼리
UPDATE MEMBER
SET 
	NAME=?,
	AGE=?,
	GRADE=?,
	...
WHERE 
	id=?
  • 수정한 데이터만 반영하고 싶은 경우, 동적 UPDATE SQL 생성하는 전략을 선택
    • 하이버네이트 확장 기능 사용 ( @org.hibernate.annotations.DynamicUpdate )
      → <참고> 확인이 필요 하지만 컬럼이 대략 30개 이상일때 정적보다 동적이 빠름
      테이블의 컬럼이 30개 이상인 경우 설계상 책임도 생각해보아야할 요소
    • @DynamicInsert 데이터가 존재하는 필드만으로 동적 INSERT SQL 생성
@Entity
@org.hibernate.annotations.DynamicUpdate
@Table(name = "member")
public class Member {...}

3.4.4 엔티티 삭제

  • 삭제하려면 삭제 대상 엔티티 조회가 필요
Member memberA = em.find(Member.class, "memberA"); //삭제 대상 엔티티 조회
em.remove(memberA); //엔티티 삭제
  • 엔티티를 즉시 삭제하는 것이 아니고 쓰기 지연 SQL 저장소에 등록
  • 트랜잭션 커밋해서 플러시 호출하면 데이터베이스에 삭제 쿼리를 전달
  • em.remove(memberA)를 호출하는 순간 memberA는 영속성 컨텍스트에서 제거

 

이미지 출처: https://ultrakain.gitbooks.io/jpa/content/chapter3/chapter3.4.html