Spring 숙련 230303 #1 JPA / 영속성 컨텍스트

김춘복·2023년 3월 3일
0

Spring 공부

목록 보기
7/14

JPA

입문주차에서 Spring Data JPA(JPA를 랩핑해서 사용하기 편하게 하주는 라이브러리)를 사용했다. 숙련 주차에서는 공부를 위해 JPA만 사용하던 시절의 코드를 학습한다.

  • Spring Data JPA를 쓴다면 Entity를 생성해서 repository 인스턴스에 .find()만 하면 간단하게 끝나지만 JPA만 쓴다면
  1. Entity를 만들고
  2. EntityManagerFactory를 만들고
  3. 거기에서 EntityManager를 만들고
  4. 매니저 인스턴스를 통해 저장과 조회 같은 메서드를 사용한다.

  • 하나의 큰일을 여러 스레드가 동시에 처리하면 동시성 문제가 생길 수 있다. 이를 방지하기위해 특정 리소스나 정보는 공유하지 못하게 하는 등의 처리가 필요하다.

  • EntityManager에는 공유하면 안되는 특정 리소스나 정보가 있고, 여러 스레드가 하나의 엔티티 매니저를 이용 할 수 없도록 처리해야한다. 그래서 EntityManagerFactory에서 필요할 때마다 여러개의 EntityManager를 만드는 구조로 설계되었다.

  • 하나의 Database에 하나의 EntityManagerFactory가 매핑된다. 같은 EMF에서 나온 EM는 같은 DB에 접속한다. EM은 EMF를 통해 사용자의 요청 하나당 하나씩 생성되어 DB커넥션풀을 통해 DB에 CRUD를 요청한다. 하나의 트랜잭션에는 한개의 EM만 존재한다. 즉, 멀티쓰레드에서는 EM의 공유가 불가능하다.

  • Spring Boot에는 application.properties를 통해 옵션 조정이 가능하고 EMF를 자동으로 생성한다.


영속성 컨텍스트

  • 영속성 컨텍스트 : Persistence Context

  • Entity를 영구 저장하는 환경. 어플리케이션이 DB에서 꺼내온 데이터 객체를 보관하는 역할.
    영속성 컨텍스트는 Entity Manager를 통해 Entity를 조회하거나 저장할때 보관/관리한다.
    Entity Manager마다 개별적으로 부여되는 어떤 논리적 공간같은 개념.

  • 기본적인 자바(J2SE)에서는 EM : 영속성컨텍스트가 1 : 1이지만,
    Spring 같은 컨테이너 환경(J2EE)에서는 N : 1이다(하나의 컨텍스트에 여러 EM이 접근가능).

JPA Entity의 상태

  • 비영속(New) : 영속성 컨택스트와 관계가 없는 새로운 상태. DB와 관련없고 java객체인 상태.
    Entity를 막 생성해서 영속화 하기 전 단계.
// 엔티티를 생성
Member minsook = new Member();
member.setId("minsook");
member.setUsername("민숙");
  • 영속(Managed) : Entity Manager를 통해 Entity가 영속성 컨텍스트에 저장되어 관리되는 상태. 데이터의 생성, 변경 등을 JPA가 추적하면서 필요하면 DB에 반영.
em.persist(entity); // 엔티티 매니저를 통해 영속성 컨텍스트에 엔티티를 저장
  • 준영속(Detached) : 영속성 컨텍스트에서 관리되다가 분리된 상태
// 특정 엔티티를 영속성 컨택스트에서 분리. 1차캐시, 쓰기지연SQL저장소 정보 제거. DB에 저장불가
em.detach(entity); 
// 영속성 컨텍스트를 비우기. 모든 Entity를 준영속상태로. 틀은 유지 내용은 비움
em.clear();       
// 영속성 컨택스트를 종료. 영속상태의 모든 Entity를 준영속으로
em.close();		

// 준영속 상태의 entity를 다시 영속상태로 전환 및 병합
em.merge(entity);
  • 삭제(Removed) : 영속성 컨텍스트에서 삭제된 상태.
em.remove(entity)

영속성 컨텍스트의 설계

  • 영속성(Persistence) : 데이터들이 프로그램이 종료되어도 사라지지 않고 어떤 곳에 저장되는 것이 필요한데, 이 개념이 바로 영속성이다.

1차캐시 (조회)

  • 영속성 컨텍스트는 내부에 1차 Cache가 존재. 영속상태의 Entity는 해당 캐시에 저장된다.
    Map형태로 되어있으며 key는 @Id로 매핑한 식별자며 DB의 기본키와 매핑. value는 Entity의 인스턴스. 즉, 영속성컨텍스트에서 데이터를 저장하고 조회하는 모든 기준은 DB 기본키 값.

  • DB를 사용하는 작업은 상대적으로 부하와 비용이 심해 작업 사용을 줄여야 할 필요가 있다.
    데이터를 조회할 때마다 SQL쿼리를 낼 수는 없다. 그래서 영속성 컨텍스트 내부에 1차캐시를 두고

  1. 조회 요청이 오면 1차 캐시를 먼저 조회한 뒤
  2. 있으면 해당 데이터를 반환하고
  3. 없으면 그때 DB로 SQL 쿼리를 보낸다.
  4. 반환하기 전에 해당 데이터를 1차캐시에 저장하고 반환한다.

쓰기 지연 SQL 저장소 (생성)

  • EM은 EM안의 쓰기 지연 SQL 저장소에 INSERT SQL을 별도로 저장하고 있다가, 트랜잭션을 커밋하면, 저장소의 모든 INSERT SQL을 DB에 요청한다. 커밋 전까지는 DB에 저장요청 x.
    트랜잭션이 커밋될때 EM은 flush를 실행한다.

  • 위와 비슷한 맥락으로 MemberA, MemberB를 생성할 때 마다 DB를 다녀오는건 비효율적.
    여러번 DB를 방문하지 않게 하기위해 영속성 컨텍스트 내부에 쓰기 지연 SQL 저장소를 두고

  1. MemberA, MemberB를 영속화하고
  2. entityManager.commit() 메서드를 호출
  3. 내부적으로 쓰기 지연 SQL 저장소에서 Flush가 일어나고
  4. 그제서야 "INSERT A" "INSERT B"같은 쓰기 전용 SQL 쿼리가 DB로 흘러들어간다.

DirtyChecking (변경감지해 자동 수정)

: JPA는 1차캐시와 쓰기 지연 SQL 저장소를 이용해 변경과 수정을 감시해준다.

  • 수정순서
  1. 1차캐시에는 해당 Entity를 조회한 시점의 데이터의 정보를 같이 저장해둔다.(스냅샷)
  2. 트랜잭션 커밋 요청이 오면 (EM 내부에서 flush호출)
  3. 스냅샷과 비교해 변경된 Entity를 찾고
  4. 변경점에 대해 UPDATE 쿼리를 생성해 쓰기 지연 SQL 저장소에 저장한 뒤
  5. 다시 flush를 호출해 저장소의 쿼리를 DB에 보내면서
  6. DB에 변경점을 커밋하게된다.
  • 업데이트의 기본전략
    entity의 모든 필드를 업데이트. 그래서 수정쿼리가 항상 같다.
    DB는 이전에 파싱된 쿼리는 재사용하기때문에 DB에 보낸 수정쿼리는 재사용된다.

데이터의 어플리케이션 단의 동일성을 보장


Spring Container의 영속성 컨텍스트 전략

  • 스프링에서는 다대일로 여러 EM이 하나의 영속성 컨텍스트에 접근 가능하다.

  • Spring Container는 트랜잭션 범위의 영속성 컨텍스트 전략을 기본으로 사용한다.
    즉, 트랜잭션의 범위와 영속성 컨텍스트의 생존 범위가 같다.
    트랜잭션을 시작할 때 영속성 컨텍스트를 생성하고 트랜잭션이 끝날때 PC를 종료한다.
    Controller까진 준영속 상태이다가 Service-Repository(트랜잭션 범위)에서 영속상태이다.

  • @Transactional을 사용해서 트랜잭션을 시작하고 종료한다.
    트랜잭션을 커밋하면 JPA는 먼저 PC를 flush해서 변경 내용을 DB에 반영한 뒤, 트랜잭션을 커밋해서 변경내용을 저장한다.
    예외가 발생하면 트랜잭션을 롤백하고 종료하는데 이때는 flush를 호출하지 않는다.

  • 트랜잭션의 원칙
    같은 트랜잭션 내에서 여러 EM을 쓰더라도, 같은 영속성 컨텍스트를 사용한다.
    같은 EM을 쓰더라도, 트랜잭션이 다르면 다른 영속성 컨텍스트를 사용한다.

profile
꾸준히 성장하기 위해 매일 log를 남깁니다!

0개의 댓글