JPA EntityManager와 동시성

저자는 JPA 프로그래밍 책을 학습 하던 중 EntityManager를 여러 스레드에서 동시에 접근하면 동시성(Concurrency) 문제가 생긴다는 문장을 읽었다.

Entity Manager는 여러 스레드가 동시에 접근하면 동시성 문제가 발생하므로 스레드 간에 절대 공유하면 안 된다. — JPA 프로그래밍 p.91

동시성에 대한 두가지 물음을 가지게 되었다.

  • 동시성 문제는 무엇 인가?
  • 동시성 문제를 피해 어떻게 사용해야 하나?

동시성 무엇이 문제인가?

JPA(Java Persistence API) 구현체 중 하나인 Hibernate 문서에서는 EntityManager에 대한 동시성 문제를 아래 처럼 기술 하고 있다.

5.1.4. Common concurrency control issues
Never use the anti-patterns entitymanager-per-user-session or entitymanager-per-application (of course, there are rare exceptions to this rule, e.g. entitymanager-per-application might be acceptable in a desktop application, with manual flushing of the persistence context). Note that some of the following issues might also appear with the recommended patterns, make sure you understand the implications before making a design decision:
An entity manager is not thread-safe. Things which are supposed to work concurrently, like HTTP requests, session beans, or Swing workers, will cause race conditions if an EntityManager instance would be shared. If you keep your Hibernate EntityManager in your HttpSession (discussed later), you should consider synchronizing access to your Http session. Otherwise, a user that clicks reload fast enough may use the same EntityManager in two concurrently running threads. You will very likely have provisions for this case already in place, for other non-threadsafe but session-scoped objects.
An exception thrown by the Entity Manager means you have to rollback your database transaction and close the EntityManager immediately (discussed later in more detail). If your EntityManager is bound to the application, you have to stop the application. Rolling back the database transaction doesn't put your business objects back into the state they were at the start of the transaction. This means the database state and the business objects do get out of sync. Usually this is not a problem, because exceptions are not recoverable and you have to start over your unit of work after rollback anyway.
The persistence context caches every object that is in managed state (watched and checked for dirty state by Hibernate). This means it grows endlessly until you get an OutOfMemoryException, if you keep it open for a long time or simply load too much data. One solution for this is some kind batch processing with regular flushing of the persistence context, but you should consider using a database stored procedure if you need mass data operations. Some solutions for this problem are shown in Chapter 7, Batch processing. Keeping a persistence context open for the duration of a user session also means a high probability of stale data, which you have to know about and control appropriately.
출처 : https://access.redhat.com/documentation/en-US/Red_Hat_JBoss_Web_Server/1.0/html/Hibernate_Entity_Manager_Reference_Guide/transactions.html#transactions-basics

요약 하자면 EntityManger를 애플리케이션별(entitymanager-per-application) 혹은 사용자 세션별(entitymanager-per-user-session)로 절대 사용하지 말라고 하고 있고 대표적으로 3가지 이유를 제시하고 있다.

  • EntityManager는 스레드에 안전하지 않기 때문에 멀티 스레드 환경 예를 들면 HTTP requests, session beans, Swing works 같은 곳에서 공유하여 사용하면 경쟁 상태(Race condition) 가 발생할 수 있다.
  • EntityManager에서의 발생할 수 있는 예외(Exception)은 데이터 베이스 트랜젝션(database transaction)이 롤백(rollback) 해야 한다는 의미이고 만약 EntityManager를 애플리케이션 단위라면 애플리케이션이 정지해야 한다.
  • 영속성 컨텍스트(persistence context)는 객체(여기서는 객체는 Entity)의 상태를 캐시에 저장하고 있기 때문에 OutOfMemoryException가 발생 할때 까지 끝없이 커질 수 있다.

동시성 문제를 피해 어떻게 사용해야 하나?

동시성 문제를 피해 사용하는 방식은 크게 두가지로 나눌 수 있다.

Application-Managed Entity Managers

이 방식은 클래스 Method에서 생성하여 사용하거나 EntityManager를 Thread-safe 하게 관리하는 클래스를 직접 만들어 사용 한다.

출처 : Method에서 EntityManager 사용 from JPA 프로그래밍
출처 : Thread Safe한 Entity Manager 사용 샘플 코드 from Stackoverflow

Container-Managed Entity Managers

이 방식은 EntityManager 생성을 Container(JBoss, Spring 등)에게 위임 하고, 필요 할 때 마다 javax.persistence.PersistenceContext annotation 을 사용 하여 의존성 주입(Dependency Injection) 받아 사용한다.

@PersistenceContext
EntityManager em;