영속성 컨텍스트
영속성 컨텍스트의 의미
영속성 컨텍스트란 엔티티를 영구 저장하는 환경이라는 의미를 가진다. 이는 DB에 저장한다는 의미보다는 영속성 컨텍스트를 통해 엔티티를 영속화 한다는 의미를 가진다. 따라서 엔티티를 영속성 컨텍스트에 저장한다는 뜻이다. 이후 엔티티 매니저를 통해 영속성 컨텍스트에 접근할 수 있다. 엔티티매니저와 영속성 컨텍스트는 N:1의 관계를 가진다.
엔티티의 생명주기
1. 비영속 (new, transient)
: 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
2. 영속 (managed)
: 영속성 컨텍스트에 관리되는 상태
엔티티 매니저를 불러와 persist 함수로 멤버 객체를 집어 넣게 되면, 엔티티 매니저 안의 영속성 컨텍스트 안에 멤버 객체가 들어가며 영속 상태가 되는 것이다. 이때 DB에 저장되는 것은 아니다. DB에 쿼리가 날라가는 시점은 트랜잭션을 커밋하는 시점이다.
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(member);
3. 준영속 (detached)
: 영속성 컨텍스트에서 엔티티를 분리한 상태
em.detach(member);
4. 삭제 (removed)
: 객체를 삭제한 상태
em.remove(member);
영속성 컨텍스트의 이점
1. 1차 캐시 가능
DB와 애플리케이션 사이에 중간 계층이 있는 것이나 마찬가지이다. 따라서 1차 캐시가 가능하다. 영속성 컨텍스트는 내부에 1차 캐시를 가지고 있다. 어플리케이션에서 member1을 em.find로 조회를 하면 JPA는 영속성 컨텍스트의 1차 캐시에서 먼저 member1을 찾는다. 결국 캐시 값을 SELECT문 없이 바로 조회할 수 있다.
만약 1차 캐시에 없는 member2를 find 하려고 한다면 아래와 같은 과정으로 객체를 반환한다. 이후에 member2를 다시 조회하면 1차 캐시에서 바로 조회할 수 있다.
사실 위 과정은 성능의 큰 이점이 되지는 않는다. 엔티티 매니저는 주로 DB 트랜잭션 단위로 만들고 트랜잭션이 끝날 때 종료된다. 즉 고객의 요청이 하나 들어와 비즈니스가 한 번 끝나며면 영속성 컨텍스트를 지운다는 의미인데 그렇다면 1차 캐시도 모두 날라가기 때문이다.
2. 동일성(Identity)를 보장해준다.
JPA는 영속 엔티티의 동일성을 보장해준다. 이 동일성 보장도 1차 캐시가 있기 때문에 가능한 것이다. 즉 1차 캐시로 반복가능한 읽기 등급의 트랜잭션 격리 수준을 DB가 아닌 애플리케이션 차원에서 제공한다.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");;
System.out.println(a==b);
//결과는 true
3. 트랜잭션을 지원하는 쓰기 지연이 가능하다.
영속 컨텍스트에는 쓰기 지연 SQL 저장소가 있다. em.persist(member1)을 실행하게 되면, member1이 1차 캐시에 들어감과 동시에 JPA가 엔티티를 분석하여 INSERT 쿼리를 생성한다. 그리고 이 쿼리를 쓰기 지연 SQL 저장소에 저장한다. 이후 em.persist(member2)를 하게 되면 누적으로 INSERT 쿼리가 쓰기 지연 SQL 저장소에 저장된다.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
em.persist(memberA);
em.persist(memberB);
//커밋시, DB에 INSERT SQL을 보낸다.
transaction.commit();
이후 트랜잭션이 commit 되는 시점에 쓰기 지연 SQL 저장소에 쌓여 있던 쿼리들을 DB에 날린다.
이때 버퍼링이라는 기능을 사용할 수 있다. JDBC batch 기능을 사용하여 batch size을 지정하고 그 사이즈만큼 모아 DB에 한 번의 네트워크로 쿼리를 두 번 보낸 후 DB를 commit 시킬 수 있다.
4. 변경 감지(Dirty Checking)가 가능하다.
데이터가 변경이 되면 commit 직전에 JPA에 변경을 감지하여 자동으로 UPDATE 쿼리를 날린다.
Member member = em.find(Member.class, 150L);
member.setName("ZZZZ");
//em.persist(member); 이 필요 없다.
tx.commit();
DB transaction을 commit을 하게 되면 내부적으로 flush가 호출이 된다. 스냅샷은 값을 읽어 영속성 컨텍스트에 들어온 최초 시점의 상태를 떠둔 것인데, 트랜잭션이 commit 되는 시점에 JPA가 엔티티와 스냅샷을 비교하여 변경 사항이 있으면 UPDATE 쿼리를 쓰기 지연 SQL 저장소에 만들어 저장해둔다. 이후 쿼리를 DB에 반영한 후 commit 된다. 삭제도 같은 매커니즘이다.
플러시
영속성 컨텍스트의 변경 내용을 DB에 반영하는 것이다. 즉 영속성 컨텍스트의 현재 변경 사양과 DB를 맞추는 작업이다. 플러시가 발생하게 되면 변경을 감지하고, 수정된 엔티티를 쓰기 지연 SQL 저장소에 등록한다. 이후 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송한다. 이때 쿼리는 등록, 수정, 삭제 쿼리일 수 있다. 플러시는 em.flush() 로 직접 호출 할 수도 있고, 트랜잭션 커밋과 JPQL 쿼리 실행 시에는 자동 호출이 된다. 플러시를 하게 되어도 1차 캐시가 지워지지는 않는다.
준영속 상태
영속 상태의 엔티티가 영속성 컨텍스트에서 분리되는 것이다. 이렇게 되면 영속성 컨텍스트가 제공하는 기능을 사용하지 못한다. 직접 사용할 일은 주로 없지만, 준영속 상태로 만드는 방법은 아래와 같다.
//특정 엔티티만 준영속 상태로 전환
em.detach(entity)
//영속성 컨텍스트를 완전히 초기화
em.clear()
//영속성 컨텍스트를 종료
em.close()
'Spring > SpringBoot&JPA' 카테고리의 다른 글
[JPA] 연관관계 매핑 (단방향, 양방향, 연관관계의 주인) (0) | 2024.03.26 |
---|---|
[JPA] 엔티티 매핑 (매핑 어노테이션, DDL, 기본키 전략) (0) | 2024.03.22 |
[자바 ORM 표준 JPA 프로그래밍] 섹션2. JPA 시작하기 (0) | 2024.03.15 |
[자바 ORM 표준 JPA 프로그래밍] 섹션1. JPA 소개 (0) | 2024.03.15 |
[스프링부트와 JPA 활용 2] Section2. API 개발 고급 - 준비 (0) | 2023.11.27 |