JPA

JPA기본기 다지기_JPA 내부구조

JANNNNNN 2024. 3. 25. 17:19

1.JPA에서 가장 중요한 2가지

  • 객체와 관계형 데이터베이스 매핑하기
  • ➡️영속성 컨텍스트(PersistenceContext) 

JPA에서는 엔티티매니저 팩토리에서 유저의 요청이 올 때마다 엔티티 매니저를 별도로 생성해야한다.

이 구조로 DB와 연결된다.

2.영속성 컨텍스트란?

  • JPA를 이해하는데 가장 중요한 용어
  • 엔티티를 영구 저장하는 환경이라는 뜻
  • EntityManager.persist(entity);
  • 영속성 컨텍스트는 논리적인 개념(눈에 보이지 않습니다.)
  • 엔티티 매니저를 통해서 영속성 컨텍스트에 접근하는 것!
  • J2SE 환경에서는 엔티티 매니저와 영속성 컨텍스트가 1:1
  • J2EE, 스프링 프레임워크 같은 컨테이너 환경에서는 엔티티 매니저와 영속성 컨텍스트가 N:1
  • 같은 트랜잭션이면 같은 영속성 컨텍스트에 접근하게 됩니다.

➡️엔티티 매니저 = 영속성 컨텍스트

3.엔티티의 생명주기

비영속(new/transient)

  • 영속성 컨텍스트와 전혀 관계가 없는 상태 즉, JPA와 관련이 없는 상태
  • Member 객체를 생성만 한 상태

영속(managed)

  • 영속성 컨텍스트에 저장된 상태
  • 객체를 생성하고 저장한 상태(영속성 컨텍스트에 의해서 객체가 관리(managed)되는 상태)

준영속(detached)

  • 영속성 컨텍스트에 저장되었다가 분리 (관리를 포기한) 상태
  • em.detach(member);

삭제(removed)

  • 삭제된 상태
  • em.remove(member);

🤔❓왜 이렇게 중간에 영속성 컨텍스트라는게 있는거

4. 영속성 컨텍스트의 이점

왜 이렇게 중간에 영속성 컨텍스트라는게 존재하는가?

Member를 쿼리를 통해서 DB에 넣어버리면 되지,

왜 중간상태가 있어야하는가?라고 생각할 수 있습니다.

그렇기에 이것에 대한 이 점은 아래와 같습니다.

4.1 1차 캐시

PersistenceContext에는 내부에 1차 캐시가 있습니다.

일반적인 캐시가 아니라 영속성컨텍스가 생성되고 없어질 때 까지만 잠깐 존재하는 메모리입니다.

  • 1차 캐시에서 조회를 하면?

em.find()에서 DB로 바로가는 것이 아니라 1차 캐시를 먼저 탐색합니다.

  • 존재하면 바로 반환
  • 1차 캐시에 없으면 데이터베이스에서 조회하고
    조회된 내용을 1차 캐시에 저장 후에 반환합니다.

4.2 영속 엔티티의 동일성(identity) 보장

1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을

데이터베이스가 아닌 어플리케이션 차원에서 제공합니다.

4.3 트랜잭션을 지원하는 쓰기 지연(버퍼 기능)

  • persist(memberA) 명령의 경우 memberA를 1차 캐시에 저장하면서
    동시에 INSERT SQL을 생성해서 쓰기 지연 SQL 저장소에 말아둡니다.
  • 이후에 persist(memberB) 명령의 경우도 위와 같이 동작하고, 아직 DB에 넣지 않습니다.
  • 이후에 commit() 명령을 해야 쓰기 지연 SQL 저장소에 있던
    INSERT SQL 쿼리 2개를(옵션에 따라 동시에 혹은 하나씩) DB에 넣습니다.
  • 쓰기 지연 SQL 저장소에 있던 쿼리들을 날리는 과정을 flush라고 합니다.
    • 하지만 flush를 한다고 1차캐시의 내용들이 지워지는 것이 아니라 쿼리를 보내서 DB와 싱크를 맞추는 역할을 합니다.
      • 개발단계에서는 보통 SQL 쿼리가 어떻게 날라가는지 궁금할 때 em.flush()를 사용합니다. 
    • commit()  flush와 commit 두 가지 일을 동시에 합니다.

4.4 변경 감지(Dirty Checking)

em.update(member)같은 것 없이 엔티티가 수정이 어떻게 가능할까??

왜 이렇게 되는가?

JPA는 트랜잭션이 커밋되는 시점에 1차 캐시 뿐만 아니라 스냅샷도 생성합니다.

commit()명령으로 flush를 하면 영속성 컨텍스트에 의해 관리되는 엔티티들을

스냅샷과 비교해서 바뀐 부분이 있으면 UPDATE 쿼리를 만들어서 DB에 보내고 commit을 합니다.

이렇게 하는 이유

Java 컬렉션에서 값을 가져와서 변경해도 다시 컬렉션에 값을 담지 않습니다.

그래도 컬렉션의 값이 바뀝니다. 그것과 똑같은 컨셉입니다.

마치 Java 컬렉션에서 값을 가져와서 변경하는 것 처럼하기 위해 이런 방식으로 처리합니다.

5. Flush

Flush는 영속성 컨텍스트의 변경 내용을 데이터 베이스에 반영시킵니다.

Flush 발생

  • 변경 감지합니다.
  • 수정된 엔티티 쓰기 지연 SQL 저장소에 등록합니다.
  • 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송합니다.(등록, 수정, 삭제 쿼리)

영속성 컨텍스트를 flush 하는 방법

  • em.flush() - 직접 호출
  • 트랜잭션 커밋 - 플러시 자동 호출
  • JPQL 쿼리 실행 - 플러시 자동 호출

JPQL 쿼리 실행시 flush가 자동으로 호출되는 이유

이 상황에서는 flush가 안되었기 때문에 DB에서 반영이 안되어있습니다.

그래서 JPA에서는 JPQL을 실행하면 flush가 자동으로 호출되도록 했습니다.

(MyBatis나 Spring JDBC와 함께 사용할 때는 flush를 직접 해줘야 한다.)

Flush는

  • 영속성 컨텍스트를 비우지 않습니다.(비우는 것은 Clear)
  • 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화하는 것이 flush의 목적입니다.
  • flush가 가능한 이유는 DB에 트랜잭션이라는 작업 단위가 있기 때문입니다.
    -> 커밋 직전에만 동기화하면 됨

6. 준영속 상태

  • 영속 -> 준영속
  • 영속 상태의 Entity가 영속성 컨텍스트에서 분리(detached)된 상태
  • 영속성 컨텍스트가 제공하는 기능을 사용 못합니다.

준영속 상태로 만드는 방법

  • em.detach(entity)
    특정 엔티티만 준영속 상태로 전환
  • em.clear()
    영속성 컨텍스트를 완전히 초기화
  • em.close()
    영속성 컨텍스트를 종료

준영속 상태면 지연 로딩을 사용하지 못합니다.

  • 지연 로딩을 쓰려면 영속성 컨텍스트가 살아있어야 합니다.
  • 영속성 컨텍스트가 죽어있는데 지연 로딩이 적용된 객체를 터치하면
    LazyInitializationException 에러가 발생
  • 영속성 컨텍스트가 DB커넥션 등을 다 들고있기 때문

프록시와 즉시로딩(EAGER) 주의

  • 가급적 지연 로딩(LAZY)을 사용해야합니다.
  • 즉시 로딩을 적용하면 예상하지 못한 SQL이 발생합니다.
  • 즉시 로딩은 JPQL에서 N+1 문제를 일으킵니다.
  • @ManyToOne, @OneToOne은 기본이 즉시 로딩 -> LAZY로 설정
  • @OneToMany, @ManyToMany는 기본이 지연 로딩