[Spring] JPA의 영속성 컨텍스트(feat. EntityManagerFactory&EntityManager)

조진우·2022년 11월 25일
0

Spring

목록 보기
1/4
post-thumbnail

영속성 컨텍스트를 알기 위해서는 EntityManagerFactory와 EntityManager를 알아야 한다.


💡 EntityManagerFactory & EntityManager


  • EntityManagerFactory는 이름 그대로 EntityManager를 만드는 공장이다.
  • EntityManagerFactory는 애플리케이션에서 한 개만 생성된다.
  • EntityManager는 EntityManagerFactory를 이용하여 생성된다.
  • EntityManagerFactory는 여러 스레드가 동시에 접근해도 안전하므로 다른 스레드간 공유할 수 있다.
  • EntityManager는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간 공유를 하면 안된다.

JPA의 경우 다음과 같은 구조로 동작한다.

  1. 사용자가 요청
  2. EntityManagerFactory를 통해 EntityManager를 생성한다.
  3. EntityManager는 DB Connection Pool을 이용하여 DB와 커넥션

EntityManager는 데이터베이스 커넥션을 바로 사용하는 것이 아니다. 꼭 데이터베이스와 연결이 필요한 시점에 사용한다.


💡 영속성 컨텍스트(Persistence Context)


  • JPA를 이해하는데 가장 중요한 용어이다.
  • Entity를 영구저장하는 환경이라는 의미이다.
  • 애플리케이션과 DB 사이에서 객체를 보관하는 역할을 한다.
  • 애플리케이션에 1개의 EntityManagerFactory가 존재하며 DB에 접근하는 트랜잭션이 생길 때 마다 스레드 별로 EntityManager를 생성하여 영속성 컨텍스트에 접근한다.
  • EntityManager는 영속성 컨텍스트 내에서 Entity를 보관하고 관리힌다.
  • 영속성 컨텍스트는 EntityManager를 생성할 때 만들어진다.
  • Entity는 EntityManager를 통해 영속성 컨텍스트에 접근하고 관리된다.
  • entityManager.persist(entity);를 통해 영속성 컨텍스트에 저장할 수 있다.

영속성 컨텍스트의 값이 DB에 반영되는 경우
1. flush를 통해 개발자가 직접 반영하는 경우
2. Transaction이 끝나서 해당 쿼리가 commit 되는 시점
3. 복잡한 조회 조건에 JPQL Query가 실행되는 시점

(사전에 영속성 콘텍스트에 추가한 데이터들이 flush()되지 않아서 DB에 업데이트 되지 않았다면 JPQL쿼리문을 수행하는데 오류가 발생할 수 있기에 JPQL 쿼리문을 실행하기 전에 자동으로 flush()를 호출한다.)



💡 EntityManager


  • EntityManager는 영속성 컨텍스트 내에서 Entity를 보관 및 관리한다.
  • EntityManager는 JPA에서 제공하는 인터페이스로 Bean으로 등록되어 있어 Autowired로 사용이 가능한다.
@Autowired
private EntityManager entityManager;
  • EntityManager는 Entity Cache를 갖고있다.



💡 Entity 생명주기


  • 비영속 (new/transient) :

    • 영속성 컨텍스트와 전혀 관계가 없는 상태
    • Entity 객체를 생성하였지만, 아직 영속성 컨텍스에 저장하지 않은 상태
  • 영속(managed) :

    • Entity가 영속성 컨텍스트에 저장 및 관리되는 상태
    • 영속 상태가 되었다고 바로 DB에 저장되지 않고, 트랜잭션의 커밋 시점에 영속성 컨텍스트에 있는 정보들을 DB에 쿼리로 날리게 된다.
  • 준영속(detached) :

    • 영속성 컨텍스트에 저장되었다가 분리된 상태
    • Entity를 준영속 상태로 만드려면 entityManager.detach()를 호출하면 된다.
    • 준영속 상태의 특징 : 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않는다.
  • 삭제(removed) :

    • 영속성 컨텍스트와 DB에서 해당 Entity를 삭제하여 삭제된 상태이다.
// 비영속 상태 = 단순 객체 생성
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

EntityManager em = emg.createEntityManager();
em.getTransaction().begin();

// 영속 상태가 된다.
em.persist(member);

// 준영속 상태로 만들기 = 회원 엔티티를 영속성 컨텍스트에서 분리
em.detach(member);

// 삭제
em.remove(member);



💡 영속성 컨텍스트 특징 4가지


  1. 1차 캐시
  2. 동일성 보장
  3. 트랜잭션을 지원하는 쓰기 지연
  4. 변경 감지

1) 1차캐시

JPA는 em.persist(member);를 실행하면 위 그림처럼 1차 캐시에 엔티티를 저장한다. 즉, 아직까지 DB에 저장된 상태는 아니다.

그 다음 아래의 코드를 실행하여 Entity를 조회하자.

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

위의 코드를 실행하면 JPA는 우선 1차 캐시에서 Entity를 찾고, 만약 찾는 Entity가 1차 캐시에 없으면 DB를 조회한다.

즉, DB에 바로 접근하는 것이 아닌, 1차 캐시에서 저장 및 관리를 함으로써 JPA의 조회 성능이 올라간다는 장점이 있다.

아래의 코드와 같이 id로 반복적을 조회하는 경우, @Transactional이 붙는다면, JPA는 처음에만 DB에서 조회하고, 다음 조회 때에는 1차 캐시에 있는 값을 반환한다.

    @Test
    void cacheFindTest() {
        System.out.println(userRepository.findById(1L).get());
        System.out.println(userRepository.findById(1L).get());
        System.out.println(userRepository.findById(1L).get());
    }

2) 동일성 보장

  • 영속성 콘텍스트는 영속 엔티티의 동일성을 보장한다.
    ※ 동일성은 값 뿐만 아니라 실제 인스턴스 자체가 같다는 뜻이다.
  • 동일성을 보장하여 Id값이 1인 user를 조회한 두번의 결과의 동일성 비교를 해본 결과 true의 결과가 나오는 것을 확인할 수 있다.
User findUser1 = entityManager.find("User.class", "1L");
User findUser2 = entityManager.find("User.class", "1L");
System.out.print(findUser1 == findUser2) // Result: true

3) 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)

  • EntityManager는 트랜잭션을 commit 하기 직전까지 내부 쿼리 저장소에 INSERT SQL 을 모아둔다.
  • 트랜잭션을 commit 할 때 모아둔 쿼리를 데이터베이스에 보내는데 이것을 트랜잭션을 지원하는 쓰기 지연(transactional write-behind) 이라 한다.

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

//엔티티 매니저는 데이터 변경시 트랜잭션을 시작
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);

//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit();

4) 변경 감지(dirty checking)

  • 엔티티의 수정이 일어나도 개발자는 영속성 컨텍스트에 따로 알려주지 않아도 영속성 컨텍스트가 알아서 변경 사항을 체크해준다. 이것을 Dirty checking이라고 한다.

  • 1차 캐시에 Entity를 저장할 때, 스냅샷 필드도 저장하여 commit이나 flush를 할 때 해당 Entity와 스냅샷을 비교하여 변경사항이 있는 경우 자동으로 UPDATE SQL을 만들어서 DB에 전송한다.

// 영속 엔티티 조회
Member memberA = em.find(Member.class, 101L);
// 영속 엔티티 데이터 수정
memberA.setName("hi");
tx.commit();

위의 코드를 실행하면 자동으로 update query 를 실행한다. 다음과 같은 동작 순서에 의해 발생한다.

  1. flush() 가 발생하면
  2. Entity와 1차 캐시의 스냅샷을 비교한다.
  3. UPDATE SQL 생성
  4. flush() -> 영속성 컨텍스트에 있는 값을 DB로 전송
  5. commit()

변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 Entity에만 적용.



💡 flush() (플러쉬)


  • flush는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다는 의미이다. (영속성 컨텍스트를 비우는 것이 아니다.)
  • flush() or 트랜잭션을 commit 하면 영속성 컨텍스트 내에 있는 SQL 저장소에 쌓인 INSER, UPDATE, DELETE들이 DB에 전송된다.
  • flush()를 실행하면 변경 감지(Dirty Checking)을 통해 스냅샷과 비교하여 변경된 Entity을 찾고, UPDATE 쿼리를 SQL 저장소에 등록한 후, SQL 저장소의 모든 쿼리를 DB에 보내어 동기화 한다.



💡 commit (커밋)


  • 커밋(commit)은 모든 작업을 최종적으로 데이터베이스에 반영하는 명령어로써 commit 명령을 수행하면 변경된 내용이 데이터베이스에 영구적으로 저장됩니다.
  • 만약 commit 명령을 수행하지 않으면 작업의 결과가 데이터베이스에 최종적으로 반영되지 않습니다.
  • commit 명령을 수행하면, 하나의 트랜젝션 과정은 종료하게됩니다.



참고자료


[참고 링크] https://velog.io/@seongwon97/Spring-Boot-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8Persistence-Context

[참고 링크] https://yeoonjae.tistory.com/entry/JPA-JPA%EC%9D%98-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%9E%80

profile
The Top of Iceblog

0개의 댓글

관련 채용 정보