JPA에서 가장 중요한 2가지
- 객체와 관계형 데이터베이스 매핑 (Object Relational Mapping)
- 영속성 컨텍스트
영속성 컨텍스트
- JPA를 이해하는데 가장 중요한 용어
- "엔티티를 영구 저장하는 환경" 이라는 뜻
EntityManager.persist(entity);
persist()
는 DB에 저장한다는 것이 아니라 entity를 영속성 컨텍스트에 저장하는 것- 논리적인 개념으로 눈에 보이지 않는다.
- EntityManager를 통해 영속성 컨텍스트에 접근
※ 환경별 영속성 컨텍스트
(1) J2SE 환경
EntityManager와 영속성 컨텍스트가 1:1
(2) J2EE, 스프링 프레임워크 같은 컨테이너 환경
EntityManager와 영속성 컨텍스트가 N:1
엔티티의 생명주기
생명주기 한눈에 보기
비영속 (new/transient)
영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
// 객체를 생성한 상태 (비영속)
Member member = new Member();
member.setId(100L);
member.setName("HelloJPA");
영속 (managed)
영속성 컨텍스트에 관리되는 상태
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
// 객체를 생성한 상태 (비영속)
Member member = new Member();
member.setId(100L);
member.setName("HelloJPA");
// 객체를 저장한 상태 (영속)
em.persist(member);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
준영속 (detached)
영속성 컨텍스트에 저장되었다가 분리된 상태
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
// 객체를 생성한 상태 (비영속)
Member member = new Member();
member.setId(100L);
member.setName("HelloJPA");
// 객체를 저장한 상태 (영속)
em.persist(member);
// 영속성 컨텍스트에서 분리 (준영속)
em.detach(member);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
삭제 (removed)
삭제된 상태
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
// 객체를 생성한 상태 (비영속)
Member member = new Member();
member.setId(100L);
member.setName("HelloJPA");
// 객체를 저장한 상태 (영속)
em.persist(member);
// 객체를 삭제한 상태 (삭제)
em.remove(member);
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
영속성 컨텍스트의 이점
내부에 1차 캐시 존재
엔티티를 영속하게 되면 EntityManager 내 1차 캐시 공간에 key
와 entity
로 저장된다. 이때 저장된 엔티티를 조회할 땐 1차 캐시에서 조회를 하기 때문에 DB에 접근하지 않지만 저장되지 않은 엔티티를 조회할 경우 1차 캐시에 없는 것을 확인 후 DB에 접근하여 조회하고 조회된 값을 1차 캐시에 저장 후 해당 값을 반환한다.
1-1. 영속 후 조회 예제
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
// 1차 캐시에서 조회 (영속 후 조회)
// DB에 접근하지 않고 1차 캐시에 있는 영속된 값을 바로 조회하기 때문에
// select 쿼리가 실행되지 않는다.
Member member = new Member();
member.setId(101L);
member.setName("HelloJPA");
em.persist(member);
Member findMember = em.find(Member.class, 101L);
System.out.println("findMember.id = " + findMember.getId());
System.out.println("findMember.name = " + findMember.getName());
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
1-2. 동작 결과
1-3. 동작 방식
2-1. 비영속 조회
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
// 1차 캐시에서 조회 (비영속 조회)
// 같은 PK를 가진 값 중복 조회 시
// 1차 캐시에 값이 없어 최초 1회 DB에 접근하여 select 쿼리를 실행하고
// 이후 같은 값 조회 시 최초 1회 조회 후 1차 캐시에 저장했기 때문에 select 쿼리가 실행되지 않는다.
Member findMember1 = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101L);
System.out.println("findMember1.id = " + findMember1.getId());
System.out.println("findMember1.name = " + findMember1.getName());
System.out.println("findMember2.id = " + findMember2.getId());
System.out.println("findMember2.name = " + findMember2.getName());
em.close();
emf.close();
2-2. 동작 결과
2-3. 동작 방식
※ EntityManager는 트랜잭션 단위로 생성, 소멸되기 때문에 복잡한 비지니스 로직이 아닐 경우 성능상 큰 이점을 얻지는 못한다.
영속 엔티티의 동일성 보장
같은 트랜잭션 내에서 동일 값 조회 후 ==
비교 시 동일하다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
// 영속 엔티티의 동일성 보장
Member findMember1 = em.find(Member.class, 101L);
Member findMember2 = em.find(Member.class, 101L);
System.out.println("result = " + (findMember1 == findMember2)); // true
em.close();
emf.close();
엔티티 등록 (트랜잭션을 지원하는 쓰기 지연)
엔티티를 영속할 때 바로 DB에 insert 쿼리를 보내지 않고 트랜잭션 commit 시 insert 쿼리를 보낸다.
동작 방식
※ EntityManager 내에는 쓰기 지연 SQL 저장소가 있어서 엔티티 등록 시 엔티티는 1차 캐시에 저장되고 insert 쿼리는 쓰기 지연 SQL 저장소에 저장되며 트랜잭션 commit()
이 요청되면 EntityManager는 DB에 insert 쿼리를 flush 하고 commit 한다.
※ persistence.xml
내 <property name="hibernate.jdbc.batch_size" value="10" />
옵션을 통해 지정된 사이즈 만큼의 쿼리를 모아서 네트워크를 통해 쿼리를 보내고 commit 한다.
엔티티 수정 (변경 감지 : Dirty Checking)
Java Collection
처럼 값을 꺼낸 후 변경할 값으로 set
만 해주면 값이 변경된다. update 쿼리는 트랜잭션 commit 시 보낸다.
동작 방식
※ 트랜잭션 commit 시 EntityManager의 flush()
가 호출되고 1차 캐시 내에 있는 엔티티와 스냅샷(값을 조회해 온 시점의 정보)을 비교한다. 비교 결과 변경된 값이 있으면 쓰기 지연 SQL 저장소에 update 쿼리를 생성하여 저장하고 DB에 update 쿼리를 flush 하고 commit 한다.
엔티티 삭제
엔티티 수정 동작 방식과 동일하고 delete 쿼리가 보내지게 된다.
지연 로딩 (Lazy Loading)
추후 학습 예정
플러시
플러시란?
영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 것
※ 데이터베이스에 반영되었다고 해서 1차 캐세이 값이 지워지는 것은 아니고 쓰기 지연 SQL 저장소에 있는 쿼리를 보내고 실행하는 것 뿐이다. (영속성 컨텍스트를 비우지 않음)
※ JPA는 기본적으로 데이터를 맞추거나 동시성에 대한 것은 전부 데이터베이스 트랜잭션에 위임하여 사용한다.
플러시 발생
트랜잭션 commit 시 플러시 발생하며 발생 시 변경 감지, 수정된 엔티티 쓰기 지연 SQL 저장소에 등록, 쓰기 지연 SQL 저장소의 쿼리(등록, 수정, 삭제)를 데이터베이스에 전송 한다.
영속성 컨텍스트를 플러시하는 방법
1-1. 직접 호출 방법
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
// 플러시
Member member = new Member(200L, "member200");
em.persist(member);
// 플러시 직접 호출
// 트랜잭션 commit 시점에 insert 쿼리가 보내지는게 아니라
// 직접 호출 시에 insert 쿼리가 보내지고 실행된다. (DB commit 이전까지 실행)
em.flush();
System.out.println("=======");
// 트랜잭션 commit 시점에 DB에 쿼리 전송
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
1-2. 동작 결과
2. 자동 호출
트랜잭션 commit, JPQL 쿼리 실행 시 자동 호출 하여 플러시 된다.
※ JPQL 실행 시 조회 결과 오류가 발생할 수 있어 JPQL은 기본적으로 플러시를 자동 호출 한다.
플러시 모드 옵션
EntityManager.setFlushMode(FlushModeType.COMMIT)
을 통해 플러시 모드를 설정할 수 있다.
FlushModeType.AUTO
: commit 이나 쿼리를 실행할 때 플러시(기본값)FlushModeType.COMMIT
: commit 할때만 플러시
준영속 상태
준영속 상태란?
영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached) 되는 것으로 영속성 컨텍스트가 제공하는 기능을 사용하지 못한다. (변경 감지 등..)
준영속 상태로 만드는 방법
EntityManager.detach(entity)
: 특정 엔티티만 준영속 상태로 전환EntityManager.clear()
: 해당 EntityManager 내 있는 영속 상태(엔티티)를 초기화EntityManager.close()
: 영속성 컨텍스트 종료
준영속 상태 예제
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
tx.begin();
try {
// 준영속 상태
// 영속 상태의 member의 name을 변경 후
// 준영속 상태로 만들어서 update 쿼리가 실행되지 않는다.
Member member = em.find(Member.class, 200L);
member.setName("AAAA");
em.detach(member);
// 트랜잭션 commit 시점에 DB에 쿼리 전송
tx.commit();
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
'Dev > JPA' 카테고리의 다른 글
[JPA] 실습 - 요구사항 분석과 기본 매핑 (0) | 2021.08.03 |
---|---|
[JPA] 엔티티 매핑 (0) | 2021.08.02 |
[JPA] JPA 시작하기 (0) | 2021.07.17 |
[JPA] JPA 소개 (0) | 2021.07.13 |
[JPA] 강좌 소개 (0) | 2021.07.13 |
댓글