EntityManager Factory와 EntityManager
데이터베이스를 하나만 사용하는 애플리케이션은 일반적으로 엔티티 매니저 팩토리를 하나만 생성한다. 엔티티 매니저가 필요할 때마다 엔티티 매니저 팩토리에서 생성하면 된다. 웹 애플리케이션을 개발한다고 하면 엔티티 매니저 팩토리를 통해 고객의 요청이 올 때마다 엔티티 매니저를 생성한다. 엔티티 매니저는 내부적으로 데이터베이스 커넥션 풀을 통해 DB에 접근한다.
엔티티 매니저는 엔티티를 저장, 수정, 삭제, 조회하는 등 엔티티와 관련된 모든 일을 처리한다. 따라서 엔티티를 저장하는 가상의 데이터베이스라고 생각하면 된다. 엔티티 매니저는 데이터베이스 연결이 필요한 시점까지 커넥션을 얻지 않고 보통 트랜잭션을 시작할 때 획득한다.
엔티티 매니저 팩토리는 여러 스레드가 동시에 접근해도 안전하므로 서로 다른 스레드 간에 공유할 수 있지만, 엔티티 매니저는 동시성 문제가 발생하므로 절대 공유하면 안된다.
영속성 컨텍스트
JPA의 내부 구조가 어떻게 동작하는지 이해하려면 영속성 컨텍스트를 이해해야 한다. 영속성 컨텍스트란 엔티티를 영구 저장하는 환경으로, 엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다. 영속성 컨텍스트는 논리적인 개념으로 눈에 보이지 않지만 엔티티 매니저를 통해 접근하고 관리할 수 있다.
엔티티의 생명주기
엔티티에는 4가지 상태가 존재한다.
•
비영속(new / transient)
◦
영속성 컨텍스트와 전혀 관계가 없는 상태
◦
순수한 객체 상태
•
영속(managed)
◦
엔티티 매니저를 통해 영속성 컨텍스트에 저장된 상태
◦
em.find()나 JPQL을 사용해서 조회한 엔티티도 영속성 컨테스트가 관리하는 영속 상태
•
준영속(detached)
◦
영속성 컨텍스트에 저장되었다가 분리된 상태
•
삭제(removed)
◦
영속성 컨텍스트와 데이터베이스에서 삭제된 상태
영속성 컨텍스트 2
영속성 컨텍스트의 특징
•
영속성 컨텍스트는 식별자 값으로 엔티티를 구분한다.
•
영속성 컨텍스트에 저장된 엔티티는 트랜잭션을 커밋하는 순간 데이터베이스에 반영한다. 이것을 플러시라고 한다.
영속성 컨텍스트의 장점
•
1차 캐시
◦
em.find()를 호출하면 1차 캐시에서 식별자 값으로 엔티티를 찾는다. 있을 경우 메모리에 있는 1차 캐시에서 조회하고, 없을 경우 데이터베이스를 조회한다.
◦
메모리에 있는 1차 캐시에서 바로 불러오면 성능상 이점을 누릴 수 있다.
•
동일성 보장
Member findMember1 = em.find(Member.class, 1L);
Member findMember2 = em.find(Member.class, 2L);
System.out.println("result = " + (findMember1 == findMember2)); //true
Java
복사
◦
em.find()를 반복해서 호출해도 영속성 컨텍스트는 1차 캐시에 있는 같은 엔티티 인스턴스를 반환한다.
•
트랜잭션을 지원하는 쓰기 지연
◦
엔티티 매니저는 트랜잭션을 커밋하기 직전까지 데이터베이스에 인티티를 저장하지 않고 내부 쿼리 저장소에 INSERT SQL을 모아둔다. 그리고 커밋할 때, 모아둔 쿼리를 데이터베이스에 보내는데 이것을 쓰기 지연이라고 한다.
◦
이 기능을 잘 활용하면 쿼리를 데이터베이스에 한 번에 전달해서 성능을 최적화할 수 있다.
•
변경 감지 (dirty checking)
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
//JPA의 모든 데이터 변경은 트랜잭션 안에서 실행
EntityTransaction tx = em.getTransaction();
tx.begin();
Member member = new Member(1L, "memberA", 20);
em.persist(member);
Member findmember = em.find(Member.class, "memberA");
//데이터 수정
findmember.setUsername("memberB");
findmember.setAge(10);
tx.commit();
em.close();
emf.close();
Java
복사
◦
단순히 엔티티를 조회해서 데이터만 변경하면 변경사항을 데이터베이스에 자동으로 반영한다. (변경 감지는 영속 상태의 엔티티에만 적용된다.)
◦
동작 방식은 다음과 같다.
▪
트랜잭션을 커밋하면 엔티티 매니저 내부에서 플러시가 호출된다.
▪
스냅샷과 비교해서 변경된 엔티티가 있으면 UPDATE 쿼리를 생성해서 쓰기 지연 SQL 저장소에 보낸다.
▪
쓰기 지연 저장소의 SQL을 데이터베이스에 보낸다.
▪
데이터베이스 트랜잭션을 커밋한다.
Flush
플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.
동작 방식은 먼저 변경 감지를 통해 수정된 엔티티를 찾고 수정된 엔티티는 update query를 만들어 쓰기 지연 SQL 저장소에 등록한다. 그리고 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송한다.
※ 플러시는 영속성 컨텍스트를 비우지 않는다. 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 것이다.
영속성 컨텍스트를 플러시하는 방법
•
em.flush()를 직접 호출
•
트랜잭션 커밋 시 자동 호출
•
JPQL 쿼리 실행 시 자동 호출
[참고]
자바 ORM 표준 JPA 프로그래밍