Spring

EntityManagerFactory, EntityManager, 영속성 컨텍스트와 영속성 컨텍스트의 특징

윤밥밥 2025. 2. 22. 17:28

[목차]

  • EntityManagerFactory란?
  • EntityManager란?
  • 영속성 컨텍스트란?
  • 영속성 컨텍스트의 생명주기
  • 영속성 컨텍스트의 주기능
  • 영속성 컨텍스트의 특징
    • 트랜잭션 안이 아니면 영속성 컨텍스트로 관리되지 않는다.
    • 영속성 컨텍스트는 Map으로 관리되며 key는 기본키를 사용한다.

 

[본론]

EntityManagerFactory란?

EntityManagerFactory란 EntityManager를 생성하는 역할을 한다.

Spring Application이 실행될 때 딱 한번만 생성되며, 이 후 EntityManager가 필요하면 같은 EntityManagerFactory를 사용하여 생성한다.

  • 딱 한 번만 생성하는 이유는 생성비용이 크기 때문이다.
  • 한 EntityManagerFactory는 여러 EntityManager를 생성할 수 있다.

EntityManager란?

EntityManager란 JPA에서 엔티티의 생명주기를 관리하는 역할을 한다.

EntityManager가 EntityManagerFactory를 통해 생성되면 EntityManager는 딱 하나의 영속성 컨텍스트를 생성해 내부적으로 저장하고 있다.

참고로 이 영속성 컨텍스트는 EntityManager를 생성할 때 생성자를 통해 자동으로 생성되며, 이 후 변경, 삭제할 수 없다.

따라서 영속성 컨텍스는 불변이고, 오직 EntityManager 삭제를 통해서만 영속성 컨텍스트 또한 삭제된다.

영속성 컨텍스트란?

영속성 컨텍스트란 JPA에서 엔티티를 관리하는 1차 캐시 역할을 한다.

서버에서 DB에 정보를 요청하면 가져온 정보를 영속성 컨텍스트에 보관하여 재사용 및 관리할 수 있게 한다.

서버에는 하나의 EntityManagerFactory가 존재한다.

쓰레드 별로 트랜잭션을 시작하게 되면EntityManagerFactory에서 EntityManager를 생성하여 쓰레드에 제공하며, 이 EntityManager는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.

 

영속성 컨텍스트의 생명 주기

JPA에서 엔티티는 4가지의 상태를 통해 생명 주기가 관리 됩니다.

  • 비영속(Transient)
  • 영속(Persistent)
  • 준영속(Detached)
  • 삭제(Removed)

1. 비영속(Transient)

비영속이란 엔티티를 생성자를 통해 생성했지만 아직 EntityManager에서 관리하지 않는 상태를 의미한다.

Member member = new Member();
member.setName("shine"); //엔티티를 변경해도 DB에 반영되지 않는다.

2. 영속(Persistent)

영속이란 EntityManager를 통해 persist(객체) 를 하면 영속성 컨텍스트에 엔티티가 저장되어 관리되는 상태를 의미한다.

1차 캐시에 저장되고, 트랜잭션이 끝날 때 변경사항은 DB에 반영(Flush)된다.

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

Member member = new Member();
member.setName("홍연");

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

em.getTransaction().commit(); // 트랜잭션 커밋 시 DB 반영

persist를 하면 바로 DB에 저장되지 않고 영속성 컨텍스트에 저장되며 이후 commit or flush가 되면 DB에 저장된다.

3. 준영속(Detached)

영속된 엔티티를가 영속성 컨텍스트에서 분리된 상태를 의미한다. 영속성 컨텍스트에서 분리되면 JPA가 변경감지를 수행하지 않는다.

detach() , clear() , close() 등을 호출해 영속성 컨텍스트에서 분리할 수 있다.

  • detach(엔티티) : 엔티티를 영속성 컨텍스트에서 분리
  • clear() : 영속성 컨텍스트를 비우면, 관리되던 엔티티는 준영속이 된다.
  • close() : 영속성 컨텍스트를 종료하면, 관리된던 엔티티는 준영속이 된다.
em.detach(member); // 준영속 상태

4. 삭제(Removed)

영속성 컨텍스트에서 제거함을 의미한다.

트랜잭션이 commit되면 DB에서 이 엔티티를 제거한다.

   // user엔티티를 영속성 컨텍스트와 DB에서 삭제
    em.remove(user);

 

영속성 컨텍스트의 주 기능

영속성 컨텍스트는 1차 캐시 역할을 하면서 DB의 성능을 높이려 한다. 하지만 캐시로 인해 DB와 영속성 컨텍스트의 일관성이 깨질 수 있기에 이와 관련된 기능 또한 제공한다.

주 기능

  • 1차 캐시
  • 변경 감지
  • 지연 로딩
  • 플러시
  • 엔티티 동일성 보장

1. 1차 캐시

같은 트랜잭션 내에서 find() 를 호출하면 DB 조회 전에 영속성 컨텍스트(1차 캐시)에서 가져온다.

동일한 엔티티를 여러 번 조회해도 쿼리가 한번만 실행된다.

2. 변경 감지

persist() 없이 엔티티 값을 변경해도 commit() 시 자동으로 UPDATE 쿼리를 실행한다.

여기서 이를 수행하는 방법은 엔티티를 find할 시점에 스냅샷을 저장해두고, commit이 발생하기 전에 스냅샷과 비교하여 엔티티의 변경을 감지한다.

이 후 감지된 변경사항에 대해 commit될 때 UPDATE 쿼리를 실행한다.

3. 지연 로딩

엔티티에서 연관관계를 가진 필드에 대해 fetch = FetchType.LAZY 설정 시, 엔티티를 조회할 때 연관 엔티티를 즉시 가져오지 않고 실제 연관 엔티티가 조회될 때 가져오게 된다.

따라서 영속 상태가 아닌 엔티티의 연관관계를 가져오려하면 LazyInitializationException 이 발생하게 된다.

4.플러시

영속성 컨텍스트의 변경 사항을 DB에 반영하는 것.

  • commit() , flush() , JPQL 실행 시 자동으로 호출된다.

5. 엔티티 동일성 보장

같은 @Transactional 내에서 같은 find()로 가져온 엔티티는 동일한 엔티티임을 보장해준다.

예를 들어

Member m1 = em.find(Member.class, 1L);
Member m2 = em.find(Member.class, 1L);

System.out.println(m1 == m2); // true (같은 인스턴스)

위와 같이 id가 1인 Member를 두 번 가져왔을 때

일반적으로 객체의 eqauls는 Object의 equals에 따라

위와 같이 객체의 주소 값을 비교하게 된다.

따라서 만약 JPA가 가져온 데이터를 new Member()를 통해 객체를 생성 및 필드를 주입해서 준다면 위의 두 Member 객체는 서로 달라야 한다.

하지만 영속성 컨텍스트는 find()를 호출할 때 바로 DB에서 조회해오지 않고, 영속성 컨텍스트 1차 캐시에서 먼저 조회해오기 때문에, 기존의 객체를 그대로 반환하게 되어 같음을 보장한다.

참고로 트랜잭션이 다르면 다른 EntityManager를 사용하기에 영속성 컨텍스트 또한 다르다.

따라서 동일성을 보장하지 않는다.

 

영속성 컨텍스트의 특징

  1. 트랜잭션 안이 아니면 영속성 컨텍스트로 관리되지 않는다.
  2. 영속성 컨텍스트는 Map으로 관리되며 key는 기본키를 사용한다.

1. 트랜잭션 안이 아니면 영속성 컨텍스트로 관리되지 않는다.

영속성 컨텍스트는 트랜잭션이 시작될 때 생성 및 관리되므로, 트랜잭션 밖에서는 영속성 컨텍스트로 데이터가 관리되지 않는다.

허나 실제 테스트 하다보면 트랜잭션 밖에서 영속성 컨텍스트로 데이터가 관리되는 것을 종종 볼 수 있다.

이는 OSIV가 on인 경우 그럴 수 있다.

 

상세 내용은 아래 링크를 참조하시면 됩니다.

https://yooooonshine.tistory.com/54

2. 영속성 컨텍스트는 Map으로 관리되며 Key는 기본키를 사용한다.

영속성 컨텍스트에서는 Entity를 구별하면서 저장하기 위해 엔티티의 식별자인 Key를 사용하여 저장한다.

따라서 Map<Key, Object> 형태로 저장되며, 조회는 무조건 Key를 사용해야만 조회할 수 있다.

이로 인해 findByName 등을 통해 조회하면 영속성 컨텍스트 1차 캐시에서 조회하지 않고 바로 DB에서 조회해온다.

 

상세 내용은 아래 링크를 참조하시면 됩니다.

https://yooooonshine.tistory.com/55