오봉이와 함께하는 개발 블로그

JPA - 영속성 컨텍스트 본문

BE/JPA

JPA - 영속성 컨텍스트

오봉봉이 2022. 6. 23. 01:15
728x90

영속성 컨텍스트 1

JPA에서 가장 중요한 두 가지를 꼽자면 객체와 관계형 데이터베이스 매핑(ORM)과 영속성 컨텍스트가 있을 것이다.

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

엔티티 매니저 팩토리에서 고객이 요청할 때 마다 엔티티 매니저를 생성한다.
생성된 엔티티 매니저는 커넥션 풀을 통해 DB를 사용한다.

영속성 컨텍스트

영속선 컨텍스트는 JPA를 이해하는데 가장 중요한 용어다.
영속성 컨텍스트란, 엔티티를 영구 저장하는 환경이라 한다.
예를 들면 EntityManaget.persist(entity)를 통해 DB에 저장한다 생각하지만 실제로는 아니다.
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();

// 객체를 저장한 상태(영속)
// 아직 DB에 저장할 쿼리가 날아가지 않음.
em.persist(member);

준영속, 삭제

//회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);

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

영속성 컨텍스트 2

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

1차 캐시

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

1차 캐시에서 조회

Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//1차 캐시에 저장됨
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");

엔티티 매니저는 보통 DB Transaction 단위로 만들고 DB Transaction이 끝날 때 종료하는데
고객의 요청이 들어와서 끝날 때 지우면 1차 캐시가 날아가기 때문에 찰나의 순간에만 이득이 있어 여러 명이 사용하는 캐시가 아니다.
그래서 1차 캐시는 사실상 큰 도움이 되지는 않는다.

예제
Member member = new Member();
member.setId(100L);
member.setName("HelloJPA");

System.out.println("=== BEFORE ===");
em.persist(member);
System.out.println("=== AFTER ===");

Member findMember = em.find(Member.class, 100L);
System.out.println("findMember.getId() = " + findMember.getId());
System.out.println("findMember.getName() = " + findMember.getName());

tx.commit();
=== BEFORE ===
=== AFTER ===
findMember.getId() = 100
findMember.getName() = HelloJPA
Hibernate: 
    /* insert hellojpa.Member
        */ insert 
        into
            Member
            (name, id) 
        values
            (?, ?)

데이터베이스에서 조회

Member findMember2 = em.find(Member.class, "member2");

영속 엔티티의 동일성 보장

Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true

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

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋


persist할 때 마다 쿼리를 날리면 최적화의 여지가 사라지게 된다.
쓰기 지연을 통해 한 번에 쿼리를 날리면 지연되는 시간동안 최적화를 통해 성능을 향상시킬 수 있다.

엔티티 수정 - 변경 감지(Dirty Checking)

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(); // [트랜잭션] 커밋

JPA는 Java 컬렉션 다루듯 사용하는 목적으로 개발되었는데, Java 컬렉션은 수정을 하고 다시 넣는 작업을 하지 않는다.

JPA가 값을 찾아왔을 최초 시점에 1차 캐시에 스냅샷을 저장하고, 커밋하는 시점의 엔티티와 비교를 한다.
JPA는 변경을 자동으로 감지해서 update 쿼리를 쓰기 지연 저장소에 넣어두고 한 번에 쿼리를 날려 커밋을 진행한다.

엔티티 삭제

//삭제 대상 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
em.remove(memberA); //엔티티 삭제
출처 : 인프런 김영한 지식공유자님의 스프링 부트와 JPA 실무 완전 정복 로드맵 강의
728x90
Comments