반응형

영속성 컨텍스트

  • 엔티티를 영구 저장하는 환경을 의미한다.
  • 논리적인 개념이며 눈에 보이지 않는다.
  • 엔티티 매니저를 통해서 영속성 컨텍스트에 접근할 수 있다.
  • 예) EntityManager.persist(entity); 엔티티를 영속성 컨텍스트에 저장한다는 의미

 

엔티티의 생명주기

  • 비영속(new/transient)
    • 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
  • 영속(managed)
    • 영속성 컨텍스트에 관리되는 상태
    • 1차 캐시에 올라간 상태
  • 준영속(detached)
    • 영속성 컨텍스트에 저장되었다가 분리된 상태
    • 영속성 컨텍스트가 제공하는 기능을 사용하지 못한다.
  • 삭제(removed)
    • 삭제된 상태
  • 예) 엔티티 생명주기
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();

//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

//객체를 저장한 상태(영속)
em.persist(member); //특정 엔티티를 영속 상태로 전환
em.find(Member.class, "member1"); //엔티티 조회 시, 1차 캐시에 없으면 DB에서 조회 후 영속 상태로 관리

//엔티티를 영속성 컨텍스트에서 분리(준영속)
em.detach(member); // 특정 엔티티만 준영속 상태로 전환
em.clear(); //영속성 컨텍스트를 완전히 초기화
em.close(); //영속성 컨텍스트를 종료

//객체를 삭제한 상태(삭제)
em.remove(member);

tx.commit();

 

영속성 컨텍스트의 이점

  • 1차 캐시
  • 동일성(identity) 보장
  • 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
  • 변경 감지(Dirty Checking)
  • 지연 로딩(Lazy Loading)

 

1차 캐시

EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();

//객체를 생성한 상태(비영속)
Member member = new Member(); //Entity
member.setId("member1"); //@Id
member.setUsername("회원1");

//객체를 저장한 상태(영속)
em.persist(member); //1차 캐시에 저장됨

Member findMember = em.find(Member.class, "member1"); //1차 캐시에서 조회
Member findMember2 = em.find(Member.class, "member2"); //1차 캐시에 없는 상태, DB 조회 후 1차 캐시에 저장.
  • 1차 캐시에서 바로 조회할 수 있다.
  • 엔티티 조회 시, 먼저 영속 컨텍스트의 1차 캐시에서 일치하는 것이 있는지 조회한다.
  • 1차 캐시에서 찾으면 그것을 조회 결과로 사용하고 별도의 DB 쿼리를 요청하지 않는다.
    • Member findMember = em.find(Member.class, "member1");
  • 1차 캐시에서 못찾으면 DB를 통해 조회하고 그 결과를 1차 캐시에 저장한 후, 조회의 결과로 사용한다.
    • Member findMember2 = em.find(Member.class, "member2");
  • 보통 Entitymanager는 데이터베이스 트랜잭션 단위로 만들고 1회성으로 사용된다.(쓰레드에 안전하지 않다.) 생명주기가 짧기 때문에 1차 캐시의 이점을 크게 누리지 못한다.
  • 애플리케이션 전체에서 공유하는 캐시는 2차 캐시라고 한다.

 

영속 엔티티의 동일성을 보장한다.

  • 1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공한다.
Member member1 = em.find(Member.class, 103L);
Member member2 = em.find(Member.class, 103L);
System.out.println("result = " + (member1==member2)); // 동일성 비교 true

 

엔티티 등록 시, 트랜잭션을 지원하는 쓰기 지연(transactional write-behind) 가능

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();

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

Member memberA = new Member("memberA", "A");
Member memberB = new Member("memberB", "B");
em.persist(memberA);
em.persist(memberB);

transaction.commit(); // 트랜잭션 커밋
//커밋하는 순간 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 보낸다.(flush) 
//그 후 실제 데이터베이스의 트랜잭션이 commit 된다.
  • 쓰기 지연 SQL 저장소와 같은 중간 계층이 생김으로써 추가적인 성능 개선 포인트가 생겼다.(버퍼링)
    • 만약, DB에 쿼리가 바로바로 전달되는 상황이라면 버퍼링과 같은 처리를 하기에는 매우 힘들 것이다.
  • 배치 처리가 가능하다.
    • 컴퓨터에서는 큰 배열을 한꺼번에 계산하는 것이 분할된 작은 배열을 여러 번 계산하는 것보다 빠르다.
    • 한 번의 네트워크 통신으로 여러 개의 쿼리를 동시에 보내서 처리할 수 있다.

 

 

엔티티 수정 시, 변경 감지(Dirty Checking)할 수 있다.

  • 트랜잭션이 커밋되는 시점에 flush가 발생한다.
  • flush가 발생하면 우선 엔티티와 스냅샷을 비교한다.
    • 최초로 읽어온 시점(최초로 영속성 컨텍스트 1차 캐시에 들어온 상태)의 엔티티를 스냅샷에 저장한다.
  • 엔티티와 스냅샷을 비교하여 변경된 부분이 있다면 쓰기 지연 SQL 저장소에 update 쿼리를 만들어 저장한다.(변경 감지)
  • 그리고 쓰기 지연 SQL 저장소에 있는 update 쿼리가 실제 db에 적용된다.
  • 엔티티 삭제도 마찬가지로 위의 메커니즘 대로 동작한다.

 

플러시

  • 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화한다.
  • 플러시는 영속성 컨텍스트를 비우지 않는다. 즉, 플러시가 발생해도 1차 캐시의 내용은 그대로 유지된다.
  • 플러시 메커니즘이 가능한 이유는 트랜잭션이라는 논리적인 작업 단위가 존재하기 때문이다.
    • 트랜잭션이 커밋되기 직전에만 변경사항을 보내면 된다.

플러시 발생 시, 수행하는 동작

  • 변경 감지
  • 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록
  • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송(등록, 수정, 삭제 쿼리)

영속성 컨텍스트에서 플러시가 수행되는 경우

  • em.flush()
    • 직접 호출
  • 트랜잭션 커밋
    • 플러시 자동 호출
  • JPQL 쿼리를 실행
    • 플러시 자동 호출

 

플러시 모드 옵션

  • 기본 값을 변경하여 사용할 일은 거의 없다.
  • em.setFlushMode(FlushModeType.COMMIT)
  • FlushModeType.AUTO
    • 커밋이나 쿼리를 실행할 때 플러시(기본 값)
  • FlushModeType.COMMIT
    • 커밋할 때만 플러시

[참고자료]

자바 ORM 표준 JPA 프로그래밍 - 기본편, 김영한

반응형

+ Recent posts