Jpa

Jpa EntityManager 설명 (영속성 컨텍스트와 연관)

무대포 개발자 2021. 4. 2. 22:13
728x90
반응형

1. Entity

  • 테이블과 매칭되는 개념
  • ORM 을 이루는 기반 개념 중 하나. 객체와 RDB 간 연결시켜주는 존재

2. EntityManager

  • 엔티티를 관리하는 역할을 한다.
  • 엔티티 매니저 내부에는 영속성 컨텍스트가 있으며, 이를 통해 엔티티 관리.
  • 여러 엔티티 매니저가 하나의 영속성 컨텍스트를 공유 가능
  • EntityManager 는 Thread-Safe 를 보장해야 한다. 동일한 EntityManager 를 가지고 멀티 스레드 환경에서 호출한다면 데이터가 어떻게 변경될지 모름.

3. 영속성 컨텍스트, dirty checking

  • 잠깐 영속성 컨텍스트의 개념을 짚고 넘어가면 엔티티를 영구히 저장하는 공간
  • 영속성 컨텍스트는 관리하고 있는 엔티티의 변화를 추적하고, 한 트랜잭션 내에서 변화가 일어나면 엔티티에 마킹한다. 그리고 트랜잭션이 끝나는 시점에 마킹한 것을 DB 에 반영해준다.
    • 이를 dirty checking 이라 한다.
  • 이렇게 dirty checking 을 함으로써 여러 이점이 있겠지만 영속성 컨텍스트가 중간에서 쿼리들을 모았다가 한 번에 반영해주니 리소스 감소가 있음.
    • 이를 쓰기 지연이라 함.
  • 단, 영속성 컨텍스트 에서 관리 하지 않는 Entity 일 경우 위 동작을 안하겠지

4. Spring 환경에서 동일 Transaction 에서의 EntityManager 와 다른 Transaction 에서의 EntityManager

  • 동일 Transaction 에서 EntityManager 를 여러 번 호출해서 처리하면 같은 영속성 컨텍스트 에서 처리된다.
  • 다른 Transaction 이라면 다른 영속성 컨텍스트에서 처리된다.
  • 아래 예시를 보면 쉽게 알 수 있음. test 메소드를 보면 처음 findAll 을 한 뒤에, member 를 한 번 저장하고, 다시 findAll 을 하는 것이다.
    • test 메소드에 @Transactional 이 걸려있으면, 같은 영속성 컨텍스트에서 처리되기에 save 한 멤버가 조회가 된다.
    • test 메소드에 @Transactional 이 안걸려있으면, 같은 영속성 컨텍스트가 아니기에 save 한 멤버가 조회가 안된다.
  • 결론을 얘기하면 Spring 에서 여러 EntityManager 가 같은 트랜잭션일 경우 같은 영속성 컨텍스트에 접근한다.
    // MemberService.java
    public List<Member> test() {
        List<Member> members = memberRepository2.findAll();
        members.forEach(member -> {
            log.info("before member : {}, order.size : {}", member, member.getOrders().size());
        });

        Long id = saveMember(Member.builder().name("test").build());

        members = memberRepository2.findAll();
        members.forEach(member -> {
            log.info("after member : {}, order.size : {}", member, member.getOrders().size());
        });

        return members;
    }   

    // MemberRepository2.java
    private final EntityManager em;

    public void save(Member member) {
        em.persist(member);
    }

    public Member findOne(Long id) {
        return em.find(Member.class, id);
    }

    public List<Member> findAll() {
        return em.createQuery("select m from Member m", Member.class)
                .getResultList();
    }

5. Spring 환경 에서 EntityManager 는 Thread-Safe 한가?

  • 아래 블로그 내용 결론만 정리하면 Spring 에서는 EntityManager 를 Proxy 를 통해 감싸며, EntityManager 메소드를 호출할 때마다 Proxy 를 통해 EntityManager 를 생성한다. 즉, Thread-Safe 하다.
// Create a new EntityManager for use within the current transaction.
logger.debug("Opening JPA EntityManager");
EntityManager em = null;
// Sync 를 맞출 필요 없는 Transaction 이라면 EntityManager 를 UNSYNC 하게 만듬.  
if (!synchronizedWithTransaction) {
    try {
        em = emf.createEntityManager(SynchronizationType.UNSYNCHRONIZED, properties);
    }
    catch (AbstractMethodError err) {
        // JPA 2.1 API available but method not actually implemented in persistence provider:
        // falling back to regular createEntityManager method.
    }
}
// Sync 처리해서 Transaction 을 만듬.
if (em == null) {
    em = (!CollectionUtils.isEmpty(properties) ? emf.createEntityManager(properties) : emf.createEntityManager());
}
try {
    // Use same EntityManager for further JPA operations within the transaction.
    // Thread-bound object will get removed by synchronization at transaction completion.
    emHolder = new EntityManagerHolder(em);
    if (synchronizedWithTransaction) {
        Object transactionData = prepareTransaction(em, emf);
        TransactionSynchronizationManager.registerSynchronization(
                new TransactionalEntityManagerSynchronization(emHolder, emf, transactionData, true));
        emHolder.setSynchronizedWithTransaction(true);
    }
    else {
        // Unsynchronized - just scope it for the transaction, as demanded by the JPA 2.1 spec...
        TransactionSynchronizationManager.registerSynchronization(
                new TransactionScopedEntityManagerSynchronization(emHolder, emf));
    }
    TransactionSynchronizationManager.bindResource(emf, emHolder);
}
catch (RuntimeException ex) {
    // Unexpected exception from external delegation call -> close EntityManager and rethrow.
    closeEntityManager(em);
    throw ex;
}

6. EntityManagerFactory

  • EntityManager 를 만드는 것이며, 스레드 간에 공유가 안되게 돼있음. 싱글톤 방식임.

7. Reference

Hits