[JPA-Basic] 영속성 컨텍스트

나영·2023년 8월 22일
0

Spring-JPA

목록 보기
2/2
post-thumbnail

개요

JPA 에서 가장 중요한 2가지는 뭘까 ?

  1. 객체관계형 데이터베이스 매핑하기
  2. 영속성 컨텍스트

영속성 컨텍스트란, JPA 를 이해하는 데 가장 중요한 용어 중 하나로, 엔티티를 영구 저장하는 환경 이라는 뜻이다.

엔티티 매니저 팩토리와 엔티티 매니저

엔티티 매니저 팩토리엔티티 매니저를 생성한다. 그리고 엔티티 매니저 팩토리를 생성할 때, 커넥션 풀도 생성한다. 엔티티 매니저는 트랜잭션을 시작할 때, DB 연결에 꼭 필요한 커넥션을 획득한다.

이러한 팩토리는 비용이 많이 들기 때문에 하나의 어플리케이션 전체에서 공유하도록 설계되어 있다. 매니저는 비용이 적어서 많이 만들어도 무방하다.

즉, 팩토리는 하나인데 매니저가 여러 개이면 여러 스레드가 있다는 뜻이다. 팩토리는 여러 스레드가 동시에 공유할 수 있고, 매니저동시성 문제로 스레드 간 공유를 절대로 할 수 없다 !

스프링 프레임워크 같은 컨테이너 환경에서는 엔티티 매니저와 영속성 컨텍스트가 N:1 이다.

엔티티의 생명주기

비영속 (new/transient)

  • 영속성 컨텍스트와 전혀 관계 없는 새로운 상태
// 객체를 생성한 상태 (비영속) 
Member member = new Member(); 
member.setId("member1"); 
member.setUsername("회원1");

영속 (managed)

  • 영속성 컨텍스트에 관리되는 상태
// 객체를 생성한 상태 (비영속) 
Member member = new Member(); 
member.setId("member1"); 
member.setUsername(“회원1);

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

// 객체를 저장한 상태 (영속)
em.persist(member);

준영속 (detached)

  • 영속성 컨텍스트에 저장되었다가 분리된 상태
// 회원 엔티티를 영속성 컨텍스트에서 분리한 상태 (준영속) 
em.detach(member); 

삭제 (remove)

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

영속성 컨텍스트의 이점

1차 캐시

영속성 컨텍스트 안의 엔티티들은 각 키가 있고, 그 식별자 값은 DB 기본 키매핑되어있다. 즉, JPA 에서 @Id 에 쓰이는 칼럼이 키라는 뜻이다.

만약, 엔티티 매니저를 통해 어떠한 특정 엔티티를 찾는다고 할 때, 1차 캐시 에서 식별자 값으로 엔티티를 찾기 때문에 존재한다면, DB 까지 갈 필요가 없다. 즉, 조회 성능이 좋아진다. 하지만 존재하지 않는다면, DB 에서 엔티티를 추출해 1차 캐시에 저장한 후, 영속 상태의 엔티티를 반환한다.

Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

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

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

동일성 보장

1차 캐시에서 같은 엔티티 인스턴스를 반환하기 때문에 동일성을 보장해준다.

Member a = em.find(Member.class, "member1"); 
Member b = em.find(Member.class, "member1");

System.out.println(a == b); // 동일성 비교 true

트랜잭션을 지원하는 쓰기 지연

엔티티 매니저는 트랜잭션을 커밋 하기 전까지는 DB 에 엔티티를 저장하지 않고, 내부 쓰기 지연 저장소INSERT SQL 을 모아둔다. 그리고, 트랜잭션을 커밋하는 순간, 모아둔 쿼리를 DB 에 보내게 된다.

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

// 엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작

em.persist(memberA);
em.persist(memberB);
// 여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.

// 커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋

변경 감지 (Dirty checking)

말 그대로 변경하는 것을 감지해준다는 뜻이다. JPA 가 영속 컨텍스트에 엔티티를 보관할 때, 최초 상태를 복사해서 저장하게 되는데, 그것이 스냅샷 이라 한다.

플러시 시점에 엔티티스냅샷비교해, 변경된 엔티티를 찾고 UPDATE SQL 을 생성하게 되는 것이다. 굳이 Update(member) 메서드를 생성할 필요가 없다는 것이다 !!

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) 이런 코드가 있어야 하지 않을까? -> NO !
transaction.commit(); // [트랜잭션] 커밋

지연 로딩

실제 객체 대신 프록시 객체를 로딩해두고, 해당 객체를 실제 사용할 때 영속성 컨텍스트를 통해 데이터를 불러온다.

플러시

  • 영속성 컨텍스트의 변경 내용DB 에 반영하는 것 (동기화)

  • 플러시가 발생하면 ?

    • 변경 감지
    • 수정된 엔티티 쓰기 지연 SQL 저장소 에 등록
    • 쓰기 지연 SQL 저장소의 쿼리를 DB 에 전송
  • 플러시가 실행돼도 영속성 컨텍스트를 비우지 않음 !

  • 커밋 직전에만 동기화하면 됨.

영속성 컨텍스트를 플러시하는 방법

  • em.flush() - 직접 호출

  • 트랜잭션 커밋 - 플러시 자동 호출

  • JPQL 쿼리 실행 - 플러시 자동 호출

💡 커밋과 플러시

  • 커밋 : 트랜잭션 작업이 성공적으로 끝났고 DB 가 일관된 상태가 있을때 트랜잭션 관리자에게 알리는것
  • 플러시 : 영속성 컨텍스트에 있는 엔티티 정보를 DB 에 동기화하는 것

즉, 엔티티 매니저가 DB 의 일관된 상태를 말하기 전까진 쓰기 지연 SQL 저장소 에 쿼리를 모아두고 있다가, Flush 를 해서 저장소에 있던 SQL 을 전부 DB 에 적용을 시킨 후 동기화하면, 그제서야 Commit 을 한다.

준영속 상태

  • 영속 -> 준영속 상태
  • 영속성 컨텍스트가 제공하는 기능 사용 X

준영속 상태로 만드는 방법

  • em.detach(entity) : 특정 엔티티만 준영속 상태로 전환

  • em.clear() : 영속성 컨텍스트를 완전히 초기화

  • em.close() : 영속성 컨텍스트 종료


🔗 참고
자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한)

0개의 댓글

관련 채용 정보