김영한 님의 [자바 ORM 표준 JPA 프로그래밍]을 읽고 내용을 정리한 것입니다.
EntityManager와 영속성 컨텍스트
mybatis처럼 직접 SQL을 작성하거나 JPA를 활용한 개발을 할 때는 눈에 보이는 것 이상의 차이가 있다.
가장 사소한 예시를 들어보자면 CRUD로 불리는 가장 기본적인 4가지 동작 중 UPDATE는 JPA에 존재하지 않는다.
그럼에도 JPA는 업데이트 동작을 잘 수행한다.
영속성 컨텍스트가 데이터의 변경을 감지하여 자동으로 update 쿼리를 실행하기 때문이다.
JPA는 EntityManager
와 영속성 컨텍스트
를 통해 데이터의 상태 변화를 감지하고 필요한 쿼리를 자동으로 수행한다.
Spring boot + Spring Data를 사용하여 막 JPA를 배우는 상황이면 EntityManager
를 한 번도 못 봤을 수 있는데,
Application이 시작될 때 EntityManager
를 자동으로 bean에 등록하고 우리가 알지 못하는 사이에 가져다 사용하고 있다.
Entity Manager 생성
// name에 persistance unit name을 등록할 수 있다.
EntityManagerFactory emf = Persistance.createEntityManagerFactory("name");
EntityManager em = emf.createEntityManager();
EntityManager
를 생성할 땐 가급적 팩토리를 활용한다.
EntityManagerFactory
는 Thread safe한 처리가 되어 있으나, EntityManager
는 그렇지 않기 때문에 스레드 간 공유에 주의해야 한다.
영속성 컨텍스트 (Persistence Context)
엔티티를 영구 저장하는 환경이다.
Java 영역에서 데이터를 관리하여 DB 접근을 최적화하는 역할을 담당한다.
생명 주기
엔티티는 4가지 상태가 존재한다.
-
비영속
- 영속성 컨텍스트와 연관이 없는 상태
-
영속
- 영속성 컨텍스트에서 관리 중인 상태
-
준영속
- 영속성 컨텍스트에 저장되어 있었으나 분리된 상태
-
삭제
- 영속성 컨텍스트에서 완전히 삭제된 상태
비영속
@Entity 어노테이션을 갖는 엔티티 인스턴스를 막 생성했을 때는 영속성 컨텍스트에서 관리하지 않는다.
EntityManager
의 persist 메소드를 사용하여 영속 상태로 변경할 수 있다.
em.persist(someEntity);
영속
EntityManager
를 통해 데이터를 영속성 컨텍스트에 저장했다.
JPA는 일반적으로 id 필드가 존재하지 않으면 예외를 뱉어내는데, 영속 상태의 엔티티를 관리하기 위해서다.
id로 데이터를 관리하기 때문에 꼭 필요한 것이다.
이 상태가 되면 몇 가지의 장점을 갖게 된다.
-
1차 캐시
em.find(key)
를 호출하면 영속성 컨텍스트에 캐시된 데이터를 먼저 찾는다.- 캐시된 데이터가 없다면 DB에 접근하여 데이터를 로드하고 1차 캐시 데이터에 저장한다.
- 1차 캐시의 존재로 Java 영역에서
REPEATABLE READ
등급의 트랜잭션 격리 수준을 활용한다.
-
동일성 보장
-
JPA를 통해 불러온 데이터는 모두 캐시 데이터에 저장되기 때문에 같은 id를 가진 데이터는 같은 데이터이다.
- 일반적으로 Java에서 '같다'라는 기준은 Identity(hashcode) / Equality(equals)이다.
SomeEntity a = em.find(SomeEntity.class, "1"); SomeEntity b = em.find(SomeEntity.class, "1"); // a == b : true (Identity) // a.equals(b) : true (Equality)
-
-
트랜잭션 지원하는 쓰기 지연
- Transaction이 시작된 이후 JPA가 생성한 쿼리는 모두 쓰기 지연 저장소에 저장된다.
- commit이 수행되면 저장된 모든 쿼리를 실행한다.
-
변경 감지
- SQL을 직접 활용하여 개발하면 update문을 수행할 때 매우 귀찮은 점이 있다.
- 컬럼 1개, 2개, 3개, ... N개를 수정해야 할 때를 모두 쿼리로 작성해야 하는 것이다.
- 이렇게 되면 비즈니스 로직은 SQL에 의존할 수밖에 없다.
- JPA는 데이터를 저장하기 전 영속성 컨텍스트에 저장된 데이터가 있는지 확인한다.
- 동일 데이터가 존재하면 update, 없으면 insert를 수행한다(upsert).
- JPA가 실제로 수행하는 쿼리는 모든 컬럼을 변경한다.
- 컬럼이 굉장히 많은(30개 이상) 테이블이 아니면 성능에 크게 영향을 미치지 않는다.
- 엔티티 클래스에
@DynamicUpdate
를 붙여주면 SET절에 변경된 데이터만 삽입된다.
-
지연 로딩
준영속
원래 영속 상태였으나 영속성 컨텍스트에서 분리되어 더 이상 관리하지 않는 데이터가 된 상태이다.
영속 상태의 엔티티를 detach 시키거나 영속성 컨텍스트 자체가 초기화 / 종료되면 컨텍스트 내부의 모든 데이터는 준영속 상태가 된다.
관리되지는 않는 상태이지만 JPA의 지원을 받지 못할 뿐, 정상적인 데이터를 갖는 인스턴스이다.
// 1.
em.detach(someEntity);
// 2.
em.close();
// 3.
em.clear();
삭제
엔티티를 영속성 컨텍스트와 DB 양쪽에서 모두 삭제한다.
em.remove(someEntity);
플러시 (flush)
영속성 컨텍스트의 변경 내용을 DB에 반영하는 절차이다.
플러시를 수행하면 아래 순서대로 동작한다.
- 데이터의 변경을 감지한다.
- 생성된 쿼리를 쓰기 지연 저장소에 등록한다.
- commit되면 저장되어 있던 쿼리를 모두 수행한다.
em.flush()
를 활용하면 직접 플러시할 수 있다.
플러시 모드
-
@FlushModeType.AUTO
(default)- commit이나 쿼리 실행할 때 플러시
-
@FlushModeType.COMMIT
- commit할 때만 플러시
종료
영속성 컨텍스트를 종료하려면 EntityManager
의 close 메소드를 호출한다.
em.close();
병합
준영속 상태의 데이터는 병합 기능을 사용하여 다시 영속 상태로 돌릴 수 있다.
SomeEntity entity = em.find(key);
em.detach(entity);
em.merge(entity);
'Java > JPA' 카테고리의 다른 글
JPA 사용 시 주의할 점 (1) | 2021.07.22 |
---|---|
[JPA] 다양한 연관관계 매핑 (0) | 2020.04.07 |
[JPA] 연관관계 매핑 기초 (1) | 2020.02.26 |
[JPA] 엔티티 매핑 (0) | 2020.02.22 |