허원철의 개발 블로그

JPA - Persistence Context (영속성 컨텍스트) 본문

web

JPA - Persistence Context (영속성 컨텍스트)

허원철 2017. 3. 16. 08:41
이번 글은 JPA을 개념적으로 이해해본 내용에 대한 글입니다.
 

 
JPA를 개념적인 측면에서 제대로 이해하고 사용한 것은 아니였습니다. 대략적으로 사용해보고, 여러 삽질을 겪어보고, 그 때 마다 레퍼런스문서를 찾아보고 구글링을 통해 해답을 얻었습니다(참 바보같은 짓이죠...ㅠ). 그러다보니 개념적인 부분을 많이 놓치고 있는 것 같아 한 번 정리를 하고자합니다.
 
※ KSUG에서 발표하신 자료를 우연히 보게되어 내용을 조금씩 정리하고 제가 알고 있는 개념을 추가해서 작성한 것 입니다. ( 참고 : 2015 - KSUG Conference 영상 )
 
 
JPA가 어떻게 생겨났는가...?
 
- EJB라는 것이 존재했는데 너무 어렵고 쓰기 힘들었다고 합니다. 그래서 어느 개발자가 하이버네이트를 만들었고..EclipseLink, OpenJpa 등등이 생기면서 이걸 하나의 공통적인 스펙(인터페이스)으로 만들자고 하여 생긴 것이 JPA 입니다.
 
 
그래서 JPA가 뭐하는 것인가..?
 
- JPA는 xBatis랑은 달리 OOP(Object-Oriented Programming : 객체지향 프로그래밍)의 강점을 살리고자 하는 ORM(Object Relational Mapping) 프레임워크 입니다.
 
※ 물론, xBatis로 OOP의 강점을 살리면서 할 수 있지만, mapper 성격이 강하기 때문에 그렇게 까지는 안합니다.
 
 
JPA를 하기에 앞서 알아야할 개념
 
1) Persistence Context (영속성 컨텍스트) ★★★
: Server Side와 Database Side 사이에 엔티티를 저장하는 논리적인 영역이라고 할 수 있습니다.
 
- 이런 영역이 있음으로서 이룰 수 있는 이점?
 
① 1차 캐시
 - 이해하기 쉽게 설명을 하자면, 영속성 컨텍스트에 Map과 같은 객체가 존재하여 임시로 객체를 저장해줍니다.
 
② 동일성 보장
 - Collection에서 객체를 빼오듯이 같은 객체를 반환하게 되면 새로운 객체가 나오는 것이 아니라 동일한 객체가 반환됩니다.
 
③ 변경 감지
 - 영속성 상태의 객체는 객체의 데이터가 변경이 되면, 자동 update 됩니다.
 - EntityManager에서 flush가 되고, commit이 됩니다.
 
※ flush가 언제되나??
⑴ 강제 Flush : EntityManager.flush()
⑵ 트랜잭션이 끝났을 때 :영속성 컨텍스트는 트랜잭션으로 범위로 만들어지기 때문입니다.
⑶ JPQL Query 실행 : JPQL은 실제 Database Side에서 데이터를 가져오기 때문에 동기화를 위해 JPQL Query가 실행 전에 flush 됩니다.
 
④ 트랜잭션으로 인한 쓰기 지연
 - 영속성 컨텍스트는 트랜잭션 범위 안에서 동작합니다.
 - J2EE 스펙의 기본전략입니다.
 
※ 각각의 Thread는 서로 다른 트잭잭션을 이용하기 때문에 서로 다른 영속성 컨텍스트를 가집니다.
 
⑤ 지연로딩
 - N + 1 쿼리라고 하는데, 엔티티와 관계가 맺어진 엔티티의 데이터를 가져 올 수 있습니다.
 
 ※ 지연로딩의 경우, 오히려 성능 저하의 원인이 될 수 있으므로 충분한 설정을 거쳐야 합니다.
 
2) EntityManagerFactory
: EntityManager를 관리하기 위한 객체입니다. 
 
3) EntityManager
: Database Side에 엔티티를 반영하기 위한 객체입니다.
 

① 비영속(new / transist)
 - 일반적인 인스턴스이라고 볼 수 있습니다. ( new Entity() )
 
② 영속(managed)
 - EntityManager에 persist를 통해 인트스턴스를 영속성 컨텍스트에 넣어진 상태입니다.
 
③ 준영속(detached)
 - 영속성 컨텍스트로부터 나온 객체입니다.
 - detach, clear, close 등으로 가능합니다.
 - 거의 비영속 상태라고 볼 수 있습니다
 - 지연로딩이 불가능합니다.
 - 식별자(@Id)가 존재합니다.
 - merge 로 다시 영속성 상태로 만들 수 있습니다.
 
④ 삭제(removed)
 - Database Side에서 지워졌지만 영속성 컨텍스트에서는 존재하는 상태입니다.
 
 
예제로 배워보는 Persistence Context
 
- 예시를 위해 영속성으로 쓰일 데이터와 준영속성으로 쓰일 데이터를 준비합니다.
 
1
2
3
4
5
6
7
8
9
// 비영속성 데이터
private Customer getPersistenceContextCustomer() {
    return new Customer("heo won chul""010-xxxx-xxxx""developer");
}
 
// 준영속성 데이터
private Customer getNotPersistenceContextCustomer() {
    return new Customer(10L,"heo won chul""010-xxxx-xxxx""developer");
}
cs
 
1) 간단한 insert를 해보도록 하겠습니다. Test Case이므로 Rollback이 되기 때문에, flush는 덤으로 해보도록 하겠습니다.
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Transactional
@Test
public void test_insertA() {
//        org.hibernate.PersistentObjectException: detached entity passed to persist
//        em.persist(getNotPersistenceContextCustomer());
    em.persist(getPersistenceContextCustomer());
    em.flush();
}
 
@Transactional
@Test
public void test_insertB() {
    em.merge(getNotPersistenceContextCustomer()); // 영속성 컨텍스트에 추가
    em.flush();
}
cs
 
※ 준영속성 엔티티를 persist() 할 경우, PersistentObjectException이 발생합니다 
 
2) 영속성 컨텍스트의 존재를 보기 위해 간단한 Test Case를 살펴보도록 하겠습니다.
 
① flush (O) / clear (O)
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Transactional
@Test
public void test_insertClearAndFindAndUpdateClear() {
    em.merge(getNotPersistenceContextCustomer()); // Persistence Context 추가
    em.flush(); // Database 동기화
    em.clear(); // Persistence Context 초기화
 
    Customer customer = em.find(Customer.class, 10L);
    System.out.println(customer);
    customer.setBigo("Developer");
    em.merge(customer); // Persistence Context 추가
    em.flush(); // Database 동기화
    em.clear(); // Persistence Context 초기화
 
    Customer result = em.find(Customer.class, 10L);
    System.out.println(result);
}
cs
 
② flush (O) / clear (X)
 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Transactional
@Test
public void test_insertAndFindAndUpdate() {
    em.merge(getNotPersistenceContextCustomer()); // Persistence Context 추가
    em.flush(); // Database 동기화
 
    Customer customer = em.find(Customer.class, 10L);
    System.out.println(customer);
    customer.setBigo("Developer");
    em.merge(customer); // Persistence Context 추가
    em.flush(); // Database 동기화
 
    Customer result = em.find(Customer.class, 10L);
    System.out.println(result);
}
cs
 
① - Console
 
1
2
3
4
5
6
7
8
Hibernate: select customer0_.idx as idx1_0_0_, customer0_.bigo as bigo2_0_0_, customer0_.name as name3_0_0_, customer0_.tel as tel4_0_0_ from customer customer0_ where customer0_.idx=?
Hibernate: call next value for hibernate_sequence
Hibernate: insert into customer (bigo, name, tel, idx) values (?, ?, ?, ?)
Hibernate: select customer0_.idx as idx1_0_0_, customer0_.bigo as bigo2_0_0_, customer0_.name as name3_0_0_, customer0_.tel as tel4_0_0_ from customer customer0_ where customer0_.idx=?
Customer(idx=10, name=heo won chul, tel=010-xxxx-xxxx, bigo=developer)
Hibernate: update customer set bigo=?, name=?, tel=? where idx=?
Hibernate: select customer0_.idx as idx1_0_0_, customer0_.bigo as bigo2_0_0_, customer0_.name as name3_0_0_, customer0_.tel as tel4_0_0_ from customer customer0_ where customer0_.idx=?
Customer(idx=10, name=heo won chul, tel=010-xxxx-xxxx, bigo=Developer)
cs
 
② - Console
 
1
2
3
4
5
6
7
8
Hibernate: select customer0_.idx as idx1_0_0_, customer0_.bigo as bigo2_0_0_, customer0_.name as name3_0_0_, customer0_.tel as tel4_0_0_ from customer customer0_ where customer0_.idx=?
Hibernate: call next value for hibernate_sequence
Hibernate: insert into customer (bigo, name, tel, idx) values (?, ?, ?, ?)
 
Customer(idx=10, name=heo won chul, tel=010-xxxx-xxxx, bigo=developer)
Hibernate: update customer set bigo=?, name=?, tel=? where idx=?
 
Customer(idx=10, name=heo won chul, tel=010-xxxx-xxxx, bigo=Developer)
cs
 
이 처럼 중간에 영속성 컨텍스트에서  1차 캐시역할을 하기 때문에 find를 하더라도 값이 존재하면 실제 Database에서 값을 가져오는 것이 아니라, 영속성 컨텍스트에서 가져와 처리를 할 수 있습니다.
 

 

참고

 

EntityManagerTests

 

'web' 카테고리의 다른 글

Spring Boot - MyBatis  (396) 2017.04.13
Spring Boot + JPA + Time package Issue  (437) 2017.03.22
Spring Boot - Security + JWT  (423) 2017.02.13
Spring Boot - QueryDSL  (410) 2017.01.25
Spring Boot - jar로 Deploy(배포)하기  (405) 2017.01.19
Comments