JaeWon's Devlog
article thumbnail
반응형

이전 글에서 JPA에 대한 기본 정리를 진행해보았습니다.

이번 글에서는 JPA 에서 가장 중요할 수 있는 영속성 컨텍스트(Persistence Context)에 대해서 정리해보고자 합니다.


1. 영속성 컨텍스트란?

- 엔티티(Entity)를 영구 저장하는 환경.

- 어플리케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 저장소 같은 역할.

- 엔티티 매니저(EntityManager)를 통해 영속성 컨텍스트에 접근.

- EntityManager 를 통해 Entity 를 저장, 조회하면 EntityManager 는 영속성 컨텍스트에 해당 Entity 를 보관하고 관리.

2. 영속성 컨텍스트 생명주기

- 영속성 컨텍스트의 생명주기는 아래 이미지와 같이 4가지 상태가 있다.

  • 비영속(new) : 영속성 컨텍스트와는 무관한 상태
  • 영속(managed) : 영속성 컨텍스트에 저장된 상태
  • 준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제(removed) : 영속성 컨텍스트에서 삭제된 상태

2-1. 비영속(New)

- Entity 객체를 생성했지만, 아직 영속성 컨텍스트에는 저장되지 않은 상태.(순수 객체)

Member member = new Member();

2-2. 영속(Managed)

- Entity Manager를 통해서 Entity를 영속성 컨텍스트에 저장한 상태.

- 해당 객체는 영속성 컨텍스트에 의해 관리된다는 의미.

//	순수 객체
Member member = new Member();

//	영속성 컨텍스트에 저장 -> 영속성 컨텍스트가 관리
EntityManager em;
em.persist(member);

2-3. 준영속(Detached)

- 영속성 컨텍스트가 관리하던 상태에서 엔티티를 더이상 관리하지 않는 상태.

- 준영속 상태의 특징

  • 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않는다.
  • 식별자 값을 가지고 있다.
//	엔티티를 영속성 컨텍스트에서 분리.
em.detach(member);
//	영속성 콘텍스트를 비움(초기화).
em.claer();
//	영속성 콘텍스트를 종료.
em.close();

- 엔티티를 준영속 상태로 전환하는 방법

  • detach(entity)
    - 특정 엔티티만 준영속 상태로 전환(영속성 컨텍스트로부터 분리)
    - 1차 캐시, 쓰기 지연, SQL 저장소 정보 제거
    - 영속성 컨텍스트 안에서의 Insert, Update 쿼리도 제거되어 DB에 저장되지 않음
  • clear()
    - 영속성 컨텍스트를 완전히 초기화
    - 영속성 컨텍스트의 모든 엔티티를 준영속 상태로 만듦
    - 영속성 컨텍스트 틀은 유지하지만 내용은 비워 새로 만든 것과 같은 상태
  • close()
    - 영속성 컨텍스트를 종료
    - 해당 영속성 컨텍스트가 관리하던 영속성 상태의 엔티티들은 모두 준영속 상태로 변경

- 엔티티를 영속 상태로 전환하는 방법

  • merge(entity)
    - 준영속 상태의 엔티티를 다시 영속 상태로 변경(병합)
    - 파라미터로 전달된 엔티티의 식별자 값으로 영속성 컨텍스트를 조회하고 엔티티가 없다면, DB에서 조회
    - 만약 DB에서도 없다면 새로운 엔티티를 생성하여 병합
    - 병합은 save(저장) 또는 update(수정) 기능 수행

2-4. 삭제(Remove)

- 엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제.

em.remove(member);

3. 특징

- 엔티티 매니저(Entity Manager)를 생성할 때 영속성 컨텍스트(Persistence Context)도 생성(1:1)

- 엔티티 매니저를 통해 해당 영속성 컨텍스트에 접근, 관리 할 수 있음

  • 엔티티를 식별자 값(@id로 매핑한 값)으로 구분
  • 영속성 상태에는 식별자 값이 반드시 있어야 한다.(없으면 예외 발생)

- 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티(Entity)를 DB에 반영

  • 영속성 상태에서 값을 여러 번 바꾸어도 마지막 트랜잭션을 커밋하는 순간 값으로 반영
  • 플러시(flush)

- 영속성 상태의 엔티티는 모두 영속성 컨텍스트에서 관리

  • 내부 캐시(1차 캐시)에 저장
  • 동일성 보장
  • 트랜잭션을 지원하는 쓰기 지연
  • 변경 감지
  • 지연 로딩

- 아래와 같이 간단한 Member 엔티티를 예로 들어보겠습니다.

@Entity
@Getter
@Setter
public class Member {

    @Id
    private String id;
    
    private String name;
    private String phoneNumber;
}
//	엔티티 생성(비영속=순수객체)
Member member = new Member();
member.setId("member1");
member.setName("개발자");
member.setPhoneNumber("01012345678");

Member member2 = new Member();
member2.setId("member2");
member2.setName("이순신");
member2.setPhoneNumber("01099995555");

//	엔티티 영속
EntityManager em;
em.persist(member);	//	id = member1
em.persist(member2);	//	id = member2

3-0. 1차 캐시

- 영속성 컨텍스트는 내부에 캐시(Cache)가 존재(1차 캐시)

- 영속 상태의 엔티티는 해당 캐시에 저장

- 캐시는 Map 형태로 구성되어 있으며, 키(Key)는 @Id로 매핑한 식별자이며 값(Value)은 엔티티 인스턴스

  • 1차 캐시의 키는 식별자 값
  • 식별자 값은 DB(데이터베이스) 기본키와 매핑
  • 영속성 컨텍스트에서 데이터를 저장하고 조회하는 모든 기준은 DB 기본 키 값

3-1. 엔티티 조회

Member member = em.find(Member.class, "member1");	//	id = member1

- 1차 캐시에서 해당 Key에 대한 엔티티가 있는지 조회

  • 엔티티 조회 시 우선 1차 캐시에서 식별자 값으로 엔티티를 찾음.
  • 엔티티가 존재하면 DB 조회를 하지 않고, 메모리에 있는 1차 캐시에서 해당 엔티티를 조회.

 

- 엔티티가 1차 캐시에 없다면, DB에서 조회

  • 엔티티가 1차 캐시에 없으면 엔티티 매니저는 DB 를 조회해서 해당 엔티티를 1차 캐시에 저장.
  • 1차 캐시에 저장된 후 영속성 상태의 엔티티를 반환.

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

Member m1 = em.find(Member.class, "member1");
Member m2 = em.find(Member.class, "member1");

System.out.println(a == b)	//	true (동일성 보장)

3-2. 엔티티 등록

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

// 데이터 변경 시 트랜잭션을 시작.
transaction.begin();

em.persist(member1);
em.persist(member2);
// 여기까지는 INSERT 쿼리를 DB에 보내지 않음.

// 커밋하는 순간 DB에 INSERT 쿼리를 보냄.
transaction.commit();

- 트랜잭션(Transaction)을 지원하는 쓰기 지연

  • 엔티티 매니저는 Transaction(트랜잭션)을 커밋하기 전까지 DB에 엔티티를 저장하지 않음.
  • 엔티티 매니저 안에 존재하는 SQL 저장소에 INSERT SQL 을 별도로 저장.
  • 트랜잭션이 커밋이 될 때 SQL 저장소에 있는 모든 INSERT SQL 을 DB에 요청.

- 플러시(flush)

  • 트랜잭션이 커밋될 때 엔티티 매니저는 flush 를 실행.
  • 트랜잭션 커밋 요청 -> 엔티티 매니저 flush 실행 -> DB에 쿼리 요청(동기화) -> DB 커밋
    - 쓰기 지연 SQL 저장소에 모인 모든 쿼리를 DB에 요청한다.
  • 트랜잭션 범위 안에서 실행되며, 등록 쿼리를 아무리 생성해도 트랜잭션 커밋을 하지 않으면 DB에 저장되지 않음.

3-3. 엔티티 수정

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

// 데이터 변경 시 트랜잭션을 시작.
transaction.begin();
  
// 엔티티 조회(영속성)
Member member1 = em.find(Member.class, "member1");

// 엔티티 데이터 수정
memberA.setName("Developer");
memberA.setPhoneNumber("01052528282");

// 커밋하는 순간 DB에 UPDATE 쿼리를 보냄.
transaction.commit();

- JPA 에서는 엔티티를 수정할 때는 엔티티를 조회하여 데이터를 변경하여 저장.(update() 메소드가 없음)

- 변경 감지 기능을 통해 DB에 자동 반영.(DB Update)

참고!!!
변경 감지는 영속성 컨테스트(엔티티 매니저)가 관리하는 영속 상태의 엔티티에만 적용

- 수정 순서

  1. 트랜잭션 커밋 요청.(엔티티 매니저 내부에서 먼저 플러시 호출)
  2. 엔티티 스냅샷과 비교하여 변경된 엔티티를 찾음.
  3. 변경된 엔티티가 발견되면 Update 쿼리를 생성하여 쓰기 지연 SQL 저장소에 저장.
  4. 다시 플러시를 호출하면서 쓰기 지연 저장소의 쿼리를 DB에 요청.
  5. DB 커밋.

- 업데이트의 기본 전략

  • JPA의 기본 전략은 모든 엔티티의 필드를 업데이트하는 것이다.
  • 모든 필드를 사용하면 수정 쿼리가 항상 같다.
  • 동일한 쿼리를 보내면 DB는 이전에 파싱 된 쿼리에 대해서는 재사용을 한다.

3-4. 엔티티 삭제

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

// 데이터 변경 시 트랜잭션을 시작.
transaction.begin();
  
// 엔티티 조회(영속성)
Member member1 = em.find(Member.class, "member1");

// 엔티티 데이터 삭제
em.remove(member1);

// 커밋하는 순간 DB에 DELETE 쿼리를 보냄.
transaction.commit();

- 엔티티를 삭제하기 위해서도 먼저 삭제 대상의 엔티티 조회가 필요.

- DB에서 바로 삭제되는 것이 아니라, 영속성 컨텍스트(엔티티 매니저)에서만 제거.

- DELETE 쿼리를 쓰기 지연 SQL 저장소에 저장.

- 트랜잭션 커밋 시 쿼리를 DB 에 요청.

참고!!!
em.remove(member1)를 호출하는 순간 member1은 영속성 컨텍스트에서 제거되어, 더 이상 수정할 수 없다.

참고

- https://data-make.tistory.com/609

- https://velog.io/@neptunes032/JPA-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%9E%80

- https://ultrakain.gitbooks.io/jpa/content/chapter3/chapter3.4.html

반응형
profile

JaeWon's Devlog

@Wonol

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!