Home 5. EntityManager, 영속 컨텍스트, 트랜잭션
Post
Cancel

5. EntityManager, 영속 컨텍스트, 트랜잭션

01. EntityManager와 영속 컨텍스트

1
2
3
4
5
6
7
8
9
10
11
12
13
14
EntityManager entityManager = emf.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();

try{
  transaction.begin();
  Sight sight = entityManager.find(Sight.class, 1L);
  sight.setDetail(new SightDetail("오전9시~오후5시", "연중무휴", "10대 주차가능"));
  transaction.commit();
}catch(Exception ex) {
  transaction.rollback();
  throw ex;
}finally{
  entityManager.close();
}

위 코드에서 entityManager.find()로 얻어온 객체는 영속 객체이다. 영속 객체는 DB에 보관된 데이터에 매핑되는 메모리상의 객체 를 의미한다. find() 메서드를 통해 읽어온 엔티티 객체는 DB에서 읽어온 객체이므로 영속 객체에 해당한다.

save()를 통해 새로운 객체를 추가하면 해당 객체는 영속 객체가 되고 EntityManager가 관리한다. 그리고 트랜잭션 커밋 시점에 영속 객체를 DB에 반영한다.

EntityManager는 영속 객체를 관리할 때 영속 컨텍스트라는 집합을 사용한다. 일종의 메모리 저장소로 EntityManager가 관리하는 엔티티 객체를 보관한다. DB에서 읽어온 엔티티 객체를 영속 컨텍스트에 보관하고 save()로 저장한 엔티티 객체 역시 영속 컨텍스트에 보관한다.

EntityManager는 트랜잭션 커밋 시점에 영속 컨텍스트에 보관된 영속 객체의 변경 내용을 추적해서 DB에 반영한다. 데이터가 변경된 객체는 update 쿼리를 이용해서 변경하고, 새롭게 추가된 객체는 insert 쿼리를 이용해 삽입, 삭제된 객체는 delete 쿼리를 이용해서 삭제한다.

JPA는 영속 컨텍스트에 보관한 엔티티를 구분할 때 식별자를 이용한다. 즉 영속 컨텍스트는 엔티티 타입+식별자를 키로 사용하고 엔티티를 값으로 사용하는 데이터 구조를 갖는다.

1.1 영속 컨텍스트와 캐시

EntityManager 입장에서 영속 컨텍스트는 동일 식별자를 갖는 엔티티에 대한 캐시 역할을 한다.

예를 들어 한 EntityManager에서 객체에 대해 동일한 식별자를 갖는 엔티티를 두 번 이상 조회하면 첫 번째 find()에서는 select 쿼리를 실행하지만 두 번째 find()에서는 select 쿼리를 실행하지 않는다.(영속 컨텍스트에 보관된 객체를 리턴)

이 캐시는 영속 컨텍스트와 관련되어 있으므로 EntityManager 객체를 종료하기 전까지만 유효하다.

02. EntityManager의 종류

1)애플리케이션 관리 EntityManager

애플리케이션 관리 EntityManager는 EntityManagerFactory 생성과 종료, 이를 이용하여 EntityManager를 생성하고 종료를 처리한다. 애플리케이션 코드에서 EntityManager의 생성과 종료를 책임지므로 EntityManager를 사용한 뒤에는 close()를 호출해서 EntityManager를 반드시 종료시켜야 한다.

2)컨테이너 관리 EntityManager

컨테이너 관리 EntityManager는 JEE 컨테이너에서 EntityManagerFactory와 EntityManager의 라이프사이클을 관리한다. JEE 컨테이너가 EntityManager를 생성하고 종료하는 과정을 처리하기 때문에 애플리케이션 코드는 컨테이너가 제공하는 EntityManager를 사용해서 필요한 기능만 구현하면 된다. EntityManager는 @PersistenceContext 애노테이션을 사용하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class WithdrawService {
  //JEE 컨테이너는 @PersistenceContext 애노테이션이 적용된 필드에 컨테이너가 관리하는 EntityManager 객체를 주입한다.
  @PersistenceContext
  EntityManager em;

  //EntityManager가 트랜잭션에 참여하기 때문에 애플리케이션 코드에서는 트랜잭션을 직접 관리하지 않는다.
  @Transactional
  public void withdraw(String email) {
    User user = em.find(User.class, email);
    if(user == null) {
      throw new UserNotFoundException();
    }
    em.remove(user);
  }
}
//컨테이너 관리 EntityManager에 대해 close()를 실행하면 에러가 발생한다.

03. 트랜잭션 타입

3.1 자원 로컬 트랜잭션 타입

JPA가 제공하는 EntityTransaction을 이용하는 방식으로 이 타입을 사용하려면 persistence.xml 파일에 영속단위의 transaction-type을 RESOURCE_LOCAL로 지정하면 된다.

그리고 EntityTransaction을 이용하여 트랜잭션을 시작(begin())하고, 커밋(commit())한다. 트랜잭션 커밋 시점에 영속 컨텍스트로 추적한 변경 내역을 DB에 반영하기 때문에 트랜잭션 없이 엔티티 객체를 수정하는 경우 변경 내역이 DB에 저장되지 않는다.

3.2 JTA 트랜잭션 타입

이 타입을 사용하려면 persistence.xml 파일에 영속단위의 transaction-type을 JTA로 지정하면 된다. 이 타입을 사용하면 JPA에서 트랜잭션을 관리하지 않는다. EntityManager를 JTA 트랜잭션에 참여시켜 트랜잭션을 관리한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
UserTransaction utx = (UserTransaction) new InitialContext().lookup("java:comp/UserTransaction");
utx.begin();

EntityManager em = emf.createEntityManager();
em.joinTransaction(); //JTA 트랜잭션에 참여한다.

try{
  User user = em.find(User.class, email);
  if(user == null){
    throw new UserNotFoundException();
  }
  em.remove();
  utx.commit();
}catch(Exception e) {
  try{
    utx.rollback();
  }catch(SystemException e) {
  }
  throw new RuntimeException(ex);
}finally{
  em.close();
}

컨테이너 관리 EntityManager는 반드시 JTA 트랜잭션 타입을 사용해야 한다. 명시적으로 joinTransaction를 사용하지 않아도 JTA 트랜잭션 시작 이후 EntityManager를 생성하면 자동으로 트랜잭션에 참여한다.

04. EntityManager의 영속 컨텍스트 전파

보통 서비스는 트랜잭션을 관리하는 주체가 된다. 즉 서비스 메서드의 시작 시점에 트랜잭션을 시작하고 서비스 메서드의 종료 시점에 트랜잭션을 커밋한다.

엔티티를 수정하는 경우 find()메서드와 save()메서드가 사용하는 EntityManager는 같은 메서드에서 생성한 EntityManager와 같아야 한다. 서로 다른 EntityManager를 사용하는 경우 영속 컨텍스트와 트랜잭션을 공유하지 않으므로 원하는 쿼리가 실행되지 않는다.

EntityManager를 전파하는 가장 쉬운 방법은 find(), save() 메서드에 EntityManager 객체를 메서드 인자로 전달하는 것이다.

4.1 ThreadLocal을 이용한 애플리케이션 관리 EntityManager의 전파

ThreadLocal은 쓰레드 단위로 객체를 공유할 때 사용하는 클래스이다. 이 클래스를 사용하면 한 메서드에서 호출하는 메서드가 동일한 객체를 공유할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class EMF {
  private static EntityManagerFactory emf;
  private static ThreadLocal<EntityManager> currentEm = new ThreadLocal<>();

  //.....

  public static EntityManager currentEntityManager() {
    EntityManager em = currentEm.get();
    if(em == null) {
      em = emf.createEntityManager();
      currentEm.set(em);
    }
    return em;
  }

  public static void closeCurrentEntityManager() {
    EntityManager em = currentEm.get();
    if(em != null) {
      currentEm.remove();
      em.close();
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class JoinService{
  private UserRepository userRepository = new UserRepository();

  public void join(User user) {
    EntityManager em = EMF.currentEntityManager();
    try{
      em.getTransaction().begin();
      User found = userRepository.find(user.getEmail());
      if(found != null) {
        throw new DuplicateEmailException();
      }
      userRepository.save(user);
      em.getTransaction().commit();
    } catch(Exception e) {
      em.getTransaction().rollback();
      throw ex;
    } finally {
      EMF.closeCurrentEntityManager();
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class UserRepository{
  public User find(String email) {
    EntityManager em = EMF.currentEntityManager();
    return em.find(User.class, email);
  }

  public void save(User user) {
    EntityManager em = EMF.currentEntityManager();
    em.persist(user);
  }

  public void remove(User user) {
    EntityManager em = EMF.currentEntityManager();
    em.remove(user);
  }

  //.....
}

4.2 컨테이너 관리 EntityManager의 전파

컨테이너 관리 EntityManager는 컨테이너가 알아서 EntityManager를 전파해준다. @PersistenceContext 애노테이션을 사용하면 현재 트랜잭션에 참여하는 EntityManager를 구할 수 있다.

컨테이너 관리 EntityManager는 항상 JTA 트랜잭션 타입을 사용해야 하므로 @PersistenceContext로 구한 EntityManager는 JTA를 이용한 글로벌 트랜잭션에 참여한다.

This post is licensed under CC BY 4.0 by the author.