영속성 관리

Jeongyeon Kim·2023년 1월 16일
0

JPA

목록 보기
2/11
post-thumbnail

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

엔티티 매니저 = 엔티티를 저장하는 가상의 데이터베이스

// 공장 만들기, 비용이 아주 많이 듦.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("jpabook");
// 공장에서 엔티티 매니저 생성, 비용이 거의 안 듦.
EntityManager em = emf.createEntityManager();
  • 엔티티 매니저 팩토리는 여러 스레드가 동시에 접근해도 안전하므로 서로 다른 스레드 간 공유 가능
  • 엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간 공유 불가
  • 트랜잭션을 시작할 때 커넥션을 획득하는데 엔티티 매니저는 데이터베이스 연결이 꼭 필요한 시점까지 커넥션을 얻지 않음.
  • J2SE 환경에서 엔티티 매니저 팩토리를 생성할 때 커넥션풀도 만듦.
  • J2SE 환경에서 엔티티 매니저를 만들면 영속성 컨텍스트도 함께 만들어짐.

2. 영속성 컨텍스트란?

영속성 컨텍스트(persistence context) = 엔티티를 영구 저장하는 환경

  • 영속성 컨텍스트는 엔티티 매니저를 생성할 때 하나 만들어짐.
  • 엔티티 매니저를 통해서 영속성 컨텍스트에 접근할 수 있고, 영속성 컨텍스트를 관리할 수 있음.

3. 엔티티의 생명주기

1. 비영속(new/transient)

  • 영속성 컨텍스트나 데이터베이스와 전혀 관계가 없는 상태
  • 순수한 객체 상태
// 객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

2. 영속(managed)

  • 영속성 컨텍스트가 관리하는 엔티티
  • em.find()나 JPQL을 사용해서 조회한 엔티티도 영속성 컨텍스트가 관리하는 영속 상태
// 객체를 저장한 상태(영속)
em.persist(member);

3. 준영속(detached)

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

4. 삭제(removed)

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

4. 영속성 컨텍스트의 특징

  1. 영속성 컨텍스트와 식별자 값
    • 영속 상태는 식별자 값이 반드시 있어야 함
  2. 영속성 컨텍스트와 데이터베이스 저장
    • JPA는 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영(flush)
  • 영속성 컨텍스트가 엔티티를 관리하는 것의 장점
    • 1차 캐시
    • 동일성 보장
    • 트랜잭션을 지원하는 쓰기 지연
    • 변경 감지
    • 지연 로딩

1. 엔티티 조회

  • 1차 캐시: 영속성 컨텍스트 내부에 있는 캐시

  • 영속성 컨텍스트에 데이터를 저장하고 조회하는 모든 기준은 데이터베이스 기본 키 값

  • em.find()를 호출하면 1차 캐시에서 엔티티를 찾고 만약 찾는 엔티티가 1차 캐시에 없으면 데이터베이스 조회 후 엔티티를 생성해서 1차 캐시에 저장(영속 상태)

  • 영속성 엔티티의 동일성 보장

    • em.find(Member.class, "member1)를 반복해서 호출해도 영속성 컨텍스트는 1차 캐시에 있는 같은 엔티티 인스턴스 반환

    영속성 컨텍스트는 성능상 이점과 엔티티의 동일성 보장

2. 엔티티 등록

  • 엔티티 매니저는 트랜잭션을 커밋하기 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 모아둠
  • 트랜잭션을 지원하는 쓰기 지연: 트랜잭션을 커밋할 때 모아둔 쿼리를 데이터베이스에 보냄

    트랜잭션 커밋 ➡️ 영속성 컨텍스트 플러시 ➡️ 실제 데이터베이스 트랜잭션 커밋

3. 엔티티 수정

  • SQL을 사용해서 수정 쿼리를 직접 작성하면 프로젝트가 커지면서 수정 쿼리가 많아지는 것은 물론이고 비즈니스 로직을 분석하기 위해 SQL을 계속 확인해야 함(비즈니스 로직이 SQL에 의존)

  • 변경 감지(dirty checking)

    • JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초의 상태를 복사해서 저장해둠.(스냅샷)
    • 플러시 시점에 스냅샷과 엔티티 비교해서 변경된 엔티티 찾음.
  • 변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용됨.
  • JPA의 기본 전략은 엔티티의 모든 필드 업데이트
    • 모든 필드 사용하면 수정 쿼리가 항상 같아 애플리케이션 로딩 시점에 수정 쿼리를 미리 생성해두고 재사용할 수 있음.
    • 데이터베이스에 동일한 쿼리를 보내면 데이터베이스는 이전에 한 번 파싱된 쿼리를 재사용할 수 있음.
  • 필드가 많거나 저장되는 내용이 너무 크면 org.hibernate.annotations.DynamicUpdate 어노테이션을 사용해 수정된 데이터만 사용해서 동적으로 UPDATE SQL 생성
  • c.f) @DynamicInsert: 데이터를 저장할 때 데이터가 존재하는(null이 아닌) 필드만으로 INSERT SQL을 동적으로 생성

4. 엔티티 삭제

em.remove() ➡️ 트랜잭션 커밋 ➡️ 플러시 ➡️ 실제 데이터베이스에 삭제 쿼리 전달

5. 플러시

플러시(flush()) = 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영

플러시하는 방법

직접 호출

- 엔티티 매니저의 flush() 메소드를 직접 호출해서 영속성 컨텍스트를 강제로 플러시

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

- 트랜잭션을 커밋하기 전에 꼭 플러시를 호출해서 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영해야 하기 때문에 JPA는 트랜잭션을 커밋할 때 플러시를 자동을 호출

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

- JPQL은 SQL로 변환되어 데이터베이스에서 엔티티를 조회하는데 쿼리 실행 직전에 영속성 컨텍스트를 플러시해서 변경 내용을 데이터베이스에 반영해야 하기 때문에 JPQL을 실행할 때 플러시 자동 호출

플러시 모드 옵션

  • FlushModeType.AUTO: 커밋이나 쿼리를 실행할 때 플러시(기본 값)
  • FlushModeType.COMMIT: 커밋할 때만 플러시

6. 준영속

준영속 상태 = 영속성 컨텍스트가 관리하는 영속 상태의 엔티티가 영속성 컨텍스트에서 분리된 것

준영속 상태로 만드는 방법

em.detach(entity)

- 특정 엔티티만 준영속 상태로 전환
- 이 메소드를 호출하는 순간 1차 캐시부터 쓰기 지연 SQL 저장소까지 해당 엔티티를 관리하기 위한 모든 정보 제거됨.

em.clear()

- 영속성 컨텍스트를 완전히 초기화
- 해당 영속성 컨텍스트의 모든 엔티티를 준영속 상태로 만듦.

em.close()

- 영속성 컨텍스트 종료

준영속 상태의 특징

  • 거의 비영속 상태에 가까움
  • 식별자 값을 가지고 있음
  • 지연 로딩을 할 수 없음

병합: merge()

merge(): 준영속 상태의 엔티티를 받아서 그 정보로 새로운 영속 상태의 엔티티 반환

  • 병합은 파라미터로 넘어온 엔티티의 식별자 값으로 영속성 컨텍스트(1차 캐시)를 조회하고 찾는 엔티티가 없으면 데이터베이스에서 조회하고 1차 캐시에 저장
  • 조회한 영속 엔티티에 준영속 상태의 값을 채워 넣음
    • 만약 준영속 상태의 엔티티의 값이 변했으면 변한 값이 반영되어 조회한 영속 엔티티에 채워짐
    • 준영속 상태의 엔티티는 변경 감지 기능이 동작하지 않아 바뀐 값이 데이터베이스에 반영되지 못했는데 영속 엔티티는 변경 감지 기능이 동작하므로 여기서 변경된 내용이 데이터베이스에 반영됨
  • 비영속 병합: 데이터베이스에서도 엔티티를 발견하지 못하면 새로운 엔티티를 생성해서 병합

병합 = save or update

profile
Backend Developer👩🏻‍💻

0개의 댓글