[JPA] 영속성 컨텍스트

hyng·2022년 1월 5일
0

영속성 컨텍스트란?

  • 엔티티를 영구 저장하는 환경이다.
    애플리케이션과 데이터베이스 사이에 존재하는 가상의 데이터 베이스 같은 역할을 한다.

  • JPA의 핵심 내용은 엔티티가 영속성 컨텍스트에 포함되어 있냐 아니냐로 갈린다.

  • 엔티티 매니저를 통해 접근할 수 있다.

  • Spring Data JPA를 사용한다면 기본 옵션(EntityManager 활성화)이다.

엔티티 생명주기

1. 비영속

영속성 컨텍스트와 전혀 관계가 없는 상태.

Member member = new Member();

2. 영속

영속성 컨텍스트에 저장된 상태.

엔티티가 영속 상태가 되는 경우는 다음과 같이 두 가지 경우이다.

1) persist()

트랜잭션 커밋 시점에 INSERT 쿼리 문이 데이터베이스로 날아간다.

em.persist(member);

2) find()

find() 호출 시, 다음과 같은 조회의 흐름을 따른다.

_1. 영속성 컨텍스트 내부 1차 캐시에서 엔티티를 찾는다.

2-1. 있으면 메모리에 있는 1차 캐시에서 엔티티를 조회.(4이동)

2-2. 없다면 데이터베이스에서 조회.(3이동)

3. 조회한 데이터로 엔티티를 생성해 1차 캐시에 저장.

4. 조회한 엔티티를 반환_

3. 준영속

영속성 컨텍스트에 저장되었다가 분리된 상태.

상태 변경 검사(Dirty Checking)의 대상은 영속성 컨텍스트가 관리하는 엔티티이기 때문에.

준영속 상태에서 트랜잭션을 커밋 하면 데이터베이스에 반영되지 않는다.

em.detach(member);

4. 삭제

삭제된 상태. DB에서도 날린다.

em.remove(member);

3) 영속성 컨텍스트가 가지는 기능

애플리케이션과 데이터베이스 사이에 영속성 컨텍스트를 굳이 두는 이유는 영속성 컨텍스트가 다음의 기능들을 가지기 때문이다.

​1. 1차 캐시

1차 캐시는 영속성 컨텍스트 내부에 존재하는 캐시이다. 엔티티를 영속성 컨텍스트에 저장하는 순간, 영속 상태의 엔티티를 키(pk 값), 값(엔티티 인스턴스)로 저장한다.

1차 캐시가 가지는 이점은 엔티티 조회 시에 있다.

1. 영속성 컨텍스트 내부 1차 캐시에서 엔티티를 찾는다.

2-1. 있으면 메모리에 있는 1차 캐시에서 엔티티를 조회.(4이동)

2-2. 없다면 데이터베이스에서 조회.(3이동)

3. 조회한 데이터로 엔티티를 생성해 1차 캐시에 저장.

4. 조회한 엔티티를 반환

1차 캐시에 엔티티가 존재하면 데이터베이스를 조회하지 않는다.
데이터베이스에서 조회, 1차 캐시에 저장한 뒤 동일 트랜잭션 내에서 동일 엔티티를 조회할 경우 1차 캐시에서 조회한다.

2. 동일성 보장

영속 엔티티의 동일성을 보장한다.

Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a==b) //true

3. 변경 감지(Dirty Checking)

엔티티를 조회하고 엔티티의 값을 변경하면 변경이 감지되어 엔티티의 변경사항이 데이터베이스에 반영된다.

즉, 엔티티 객체의 값만 변경하면 별도로 update 쿼리를 날릴 필요가 없다.

1차 캐시에 저장할 때 해당 엔티티의 상태 그대로 스냅샷을 만들어 놓고 트랜잭션이 끝나는 시점에 엔티티와 스냅샷을 비교해서 변경사항이 있으면 UPDATE 쿼리를 데이터베이스로 전달한다. (UPDATE 쿼리 또한 INSERT 쿼리와 마찬가지로 커밋 시점에 데이터베이스로 전달된다.)

생성되는 UPDATE 쿼리는 기본적으로 모든 필드를 업데이트하는데 변경 부분만 UPDATE 하도록 할 수도 있다. (해당 내용은 아래 링크 참조)
https://jojoldu.tistory.com/415

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

persist() 시 영속성 컨텍스트에 저장되는 엔티티의 INSERT 쿼리문이 데이터베이스에 곧바로 보내지는 것은 아니다.

쓰기 지연 SQL 저장소라는 곳에 쿼리(INSERT, UPDATE, DELETE, ... )를 쌓아 놓고 트랜잭션 종료 시점에 쿼리들을 데이터베이스에 전달한다.(이것을 flush라고 한다.)

그럼 플러시를 하게 되면 1차 캐시가 지워지나?
=> 지워지지 않는다.

플러시는 변경 내용을 데이터베이스에 반영하는 것(싱크를 맞추는)이기 때문에 지워지지 않는다.

플러시를 하는 방법은 다음과 같이 세 가지가 있다.

1. em. flush()

2. 트랜잭션 종료 시점에 자동 호출

3. JPQL 쿼리 실행 시 자동 호출

플러시가 되지 않은 시점에서, JPQL로 쿼리를 날리려고 하면 문제가 생길 수 있기 때문에 JPQL 실행 전에 flush()를 호출해 변경사항을 데이터베이스에 반영한다.

em.persist(memberA);
em.persist(memberA);
em.persist(memberA);// 중간에 JPQL 실행
query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();

참고

스프링 부트와 AWS로 혼자 구현하는 웹 서비스
https://jojoldu.tistory.com/415
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://ict-nroo.tistory.com/130

profile
공부하고 알게 된 내용을 기록하는 블로그

0개의 댓글