[JPA] JPA 동작 방식

밀크야살빼자·2023년 5월 8일
0

엔티티

  • 데이터베이스의 테이블에 대응하는 클래스이다. -> 하나의 테이블을 클래스로 구현한 것이다.

엔티티 매니저 팩토리

  • 엔티티 매니저 인스턴스를 관리해주는 주체이다. -> Entity의 구현체를 생명주기를 관리한다.

엔티티 매니저

  • 영속성 컨텍스트에 접근하여 엔티티에 대한 데이터베이스 작업을 제공한다.
  • Entity와 Persistence Context 사이에 존재한다.
    중간에 위치하는 이유는 1차적으로 끝날 과정에 하나의 단계를 추가함으로써 중간 처리단계를 만들 수 있기 때문이다. 객체 데이터를 Persistence Context에 바로 저장하면 데이터에 대한 후처리를 진행할 수 없다.
  • find() : 영속성 컨텍스트에서 엔티티를 검색하고 영속성 컨텍스트에 없을 경우 데이터베이스에서 데이터를 찾아 영속성 컨텍스트에 저장한다.
  • persist() : 엔티티를 영속성 컨텍스트에 저장한다.
  • remove() : 엔티티 클래스를 영속성 컨텍스트에서 삭제한다.
  • flush() : 영속성 컨텍스트에 저장된 애용을 데이터베이스에 반영한다.

영속성 컨텍스트(Persistence Context)

  • 엔티티를 영구 저장하는 환경으로 엔티티 매니저를 통해 영속성 컨텍스트에 접근한다.(Entity를 영속화 시키는 것이다.)
    DB에 저장하는 것이 Entity를 영속화시키기 위한 하나의 방법이기 때문이다.
  • JPA의 내부 흐름 또는 동작 원리 그 자체이다.

엔티티 생명주기

생명주기내용
비영속성(new)new 키워드를 통해 생성된 상태로 영속성 컨텍스트와 관련이 없는 상태이다.
Entity가 생성되고 Entity Manager에게 등록되지 않은 상태이다.
영속(managed)엔티티가 영속성 컨텍스트에 저장된 상태로 영속성 컨텍스트에 의해 관리되는 상태, 영속 상태에서 데이터베이스에 저장되지 않으며, 트랜잭션 커밋 시점에 데이터베이스에 반영한다.
Entity가 영속성 컨텍스트에 등록되어 있는 상태를 의미한다.
준영속 상태(detached)영속성 컨텍스트에 엔티티가 저장되었다가 분리된 상태이다.
삭제 상태(removed)영속성 컨텍스트와 데이터베이스에서 삭제된 상태이다.
  • 영속 상태가 되었다고해서 DB에 바로 저장되는 것이 아니다.
  • DB에 저장되는 시점은 persist()를 commit()한 다음, 해당 Transacion이 온전히 종료됐을 때다.

영속성 컨텍스트 사용 시 이점
애플리케이션과 데이터베이스 사이에서 영속성 컨텍스트라는 중간 계층을 만들어 버퍼링, 캐싱 등을 할 수 있기때문이다.

1차 캐시

영속성 컨텍스트에는 1차 캐시가 존재하며 Map<KEY,VALUE>로 저장한다.
entityManager.find() 메소드 호출 시 영속성 컨텍스트의 1차 캐시를 조회한다(1차 캐시를 우선 조회해야 응답속도가 훨씬 빠르기 때문이다.) 엔티티가 존재할 경우 해당 엔티티를 반환하고, 엔티티가 없으면 데이터베이스에서 조회 후 1차 캐시에 저장 및 반환한다.
Entity를 Persistence Context에 등록하면 1차 캐시에 등록이 된다.

public class A {
   public static void main(String args[]) {
   
      EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPA이름");
      EntityManager em = emf.createEntityManager();
      
      em.getTransaction().begin();
      em.persist(userA); 
      em.find(userA); // Entity Manager의 1차 캐시에서 Entity를 조회
   }
}

Entity Manager는 하나의 트랜잭션이 시작하면서 생성되고 종료되면서 삭제되기 때문에 1차 캐시는 하나의 트랜잭션 안에서만 사용되는 캐시다. 애플리케이션 전체가 공유하는 캐시는 2차 캐시라고 부른다.

동일성 보장

하나의 트랜젝션에서 같은 키 값으로 영속성 컨텍스트에 저장된 엔티티 조회 시 같은 엔티티 조회를 보장한다. 바로 1차 캐시에 저장된 엔티티를 조회하기 때문에 가능하다.

public class A {
   public static void main(String args[]) {
   
      EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPA이름");
      EntityManager em = emf.createEntityManager();
      
      em.getTransaction().begin();
      em.persist(userA); 
      
      User a = em.find(userA);
      User b = em.find(userA);
      System.out.println(a == b); // true 반환
   }
}

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

영속성 컨텍스트에는 쓰기 지연 SQL 저장소가 존재한다. entityManager.persist()를 호출하면 1차 캐시에 저장되는 것과 동시에 쓰기 지연 SQL 저장소에 SQL문이 저장된다. 이렇게 SQL을 쌓아두고 트랜잭션을 커밋하는 시점에 저장된 SQL문들이 flush되면서 데이터베이스에 반영된다.

public class A {
   public static void main(String args[]) {
   
      EntityManagerFactory emf = Persistence.createEntityManagerFactory("JPA이름");
      EntityManager em = emf.createEntityManager();
      EntityTransaction transaction = em.getTransaction(); // 트랜잭션 생성
      
      transaction.begin(); // 트랜잭션 시작 
      
      em.persist(userA);
      em.persist(userB); 
      
      transaction.commit(); // SQL이 DB에게 전달되는 시점
   }
}
  1. em.persist(userA)가 실행된다.
  2. insert 쿼리가 생성되며 persistence context 내부에 쓰기 지연 SQL 저장소에 쌓인다.
  3. em.persist(userB)가 실행된다.
  4. insert 쿼리가 생성되며 persistence context 내부의 쓰기 지연 SQL 저장소에 쌓인다.
  5. transaction.commit()이 실행된다
  6. 쓰기 지연 SQL 저장소에 쌓여있던 쿼리 명령어가 DB에게 전달된다 -> flush
  7. DB에서 전달받은 쿼리 명령어를 실행하고 결과를 저장한다. -> commit

commit()을 수행하기 전까지 Entity Manager는 SQL을 작성하지도, DB에게 전달하지도 않는다.

flush()란

  • 영속성 컨텍스트의 변경 내용을 DB에 반영하는 것이다. -> 영속성 컨텍스트가 가지고 있는 SQL을 DB에게 전달하는 것이다.

  • 역할

    • 변경 감지한다.
    • 변경된 Entity의 내용을 쓰기 지연 SQL 저장소에 등록한다.
    • 쓰기 지연 SQL 저장소에 등록되어 있는 쿼리를 DB에게 전달한다.
  • 사용법

    • 직접 호출 방법 : EntityManager.flush();
    • 트랜잭션 커밋(플러시 자동호출) : EntityTransaction.commit();
    • JPQL 쿼리 실행(플러시 자동호출) : EntityTransaction.createQuery();

쓰기 지연 기능을 사용하는 이유
DB와의 네트워킹 횟수가 있기때문이다. 실제로 DB에서 한 번의 커밋은 하나의 트랜잭션을 의미한다.(DB의 값이 변경되는 작업의 처리 단위를 의미하는 것이다.)
쿼리 하나당 한 번의 커밋을 수행한다는 것은 10개의 쿼리를 수행하기 위해 10번의 커밋을 필요하다는 것이다.(10번의 DB 통신이 필요하다) 그래서 쿼리를 10개씩 묶어서 처리하면 DB와 한 번의 통신을 필요로 한다.-> 네트워킹 횟수가 줄어들게 되고 이로 인해 서비스 또는 시스템의 부하가 감소하게 된다.
Hibernate에서는 hibernate.jdbc.batch_size 옵션을 이용하여 설정할 수 있다.(persistence.xml에서 설정한다)

변경 감지

JPA는 1차 캐시에 데이터베이스에서 처음 불러온 엔티티의 스냅샷 값을 갖고 있다. 그리고 1차 캐시에 저장된 엔티티와 스냅샷을 비교 후 변경 내용이 있다면 UPDATE SQL문을 쓰기 지연 SQL 저장소에 담아둔다. 데이터베이스에 커밋 시점에 변경 내용을 자동으로 반영한다. -> 따로 update문을 호출할 필요가 없다.

📜참고

https://velog.io/@dev_zzame/JPA-%EB%8F%99%EC%9E%91-%EB%B0%A9%EC%8B%9D
https://developer-pi.tistory.com/307?category=1036893
https://devraphy.tistory.com/513

profile
기록기록기록기록기록

0개의 댓글