본문 바로가기
Backend/JPA

[JPA] 영속성 관리 (Entity Manager / Persistence Context / 엔티티 생명주기)

by 비전공자 기록광 2022. 9. 29.
반응형

JPA에서 가장 중요한 개념 중 하나인 Persistence Context와 EntityManager에 대해 정리해보겠다.

 

JPA에 대해 다시 설명해본다면 ORM 기술 표준이다.

ORM은 객체와 관계형 데이터베이스를 매핑하는 것을 말한다.

 

즉 JPA는 자바 객체와 RDB의 테이블 엔티티를 매핑해 준다고 보면 된다.

JPA의 EntityManager가 각 엔티티를 Persistence Context(영속성 컨텍스트)로 관리한다.

 

 

Persistence Context 

영속성 컨텍스트는 엔티티를 영구 저장하는 환경으로 스프링 컨테이너에 등록된 bean을 찾아서 주입해 주는 역할을 한다.

이는 각 엔티티를 식별자 값으로 구분한다. 그래서 우리가 엔티티를 생성할때 무조건 id를 부여해주는 이유이기도 하다

 

 

엔티티에는 생명주기가 존재한다.

  • 비영속 상태 (New / Transient)
  • 영속 상태 (Persistent)
  • 준영속 상태 (Detached)
  • 삭제 (Removed)

 

생명주기와 영속성관리는 실습을 통해 이해하는게 쉽다.

 

간단한 강의 클래스를 만들어줬다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
 
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Entity
public class Course {
 
    @Id
    @GeneratedValue
    private Long id;
 
    private String name;
 
    private String author;
 
 
    public Course(String name, String author) {
        this.name = name;
        this.author =author;
    }
}
cs

 

CourseRepository를 만들어주고 EntityManger를 주입받았다.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import com.udemy.jpa_study.indepth.domain.Course;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
 
import javax.persistence.EntityManager;
 
@Repository
@Transactional
public class CourseRepository {
 
    @Autowired
    EntityManager em;
 
    public void playWithEntityManager() {
        Course course1 = new Course("EntityManager 111", "em");      // 객체를 생성한 상태 > 비영속
        em.persist(course1);                                         // 객체를 저장한 상태 > 영속
 
        Course course2 = new Course("EntityManager 222", "em");
        em.persist(course2);
 
        Course course3 = new Course("EntityManager 333", "em");
        em.persist(course3);       // 엔티티매니저는 1차 캐시에 위의 엔티티를 저장하고 쓰기 지연 SQL 저장소에 쌓아 놓음
 
        em.flush();            // flush()를 통해 쓰기 지연 SQL 저장소의 쿼리들을 DB로 보냄
    }
 
}
cs

 

일단 세개의 강의 객체를 만들어줬다. new 객체를 만든 직후의 상태가 바로 비영속 상태이다. 생성만 됐지 저장이 되지 않은 것이다. 이 상태로는 영속성 관리가 되지않는다.

 

persist로 각 강의를 저장해줬다. 이 상태가 영속 상태이다.

엔티티가 영속성 컨텍스트에 저장되어 관리되고 추적되는 상태이다. 

여기서 엔티티매니저가 엔티티를 1차 캐시에 저장하고 쓰기 지연 SQL 저장소에 해당 저장 쿼리를 쌓아 놓는다.

 

그리고 flush( )를 통해 쓰기 지연 SQL 저장소의 쿼리들을 한번에 DB로 보내 처리하게 된다.

위에 처럼 직접 flush를 불러와 처리할 수도 있고 트랜잭션이 끝나면 자동으로 flush 처리가 되기도 한다.

 

 

세개의 강의가 잘 들어갔다.

 

이제 이 영속상태의 엔티티들을 가지고 dirty checking을 확인해본다.

세 강의의 이름을 다르게 바꿔줬는데

그 전에 강의2번을 detach해줘 준영속 상태로 만들어줬다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import com.udemy.jpa_study.indepth.domain.Course;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
 
import javax.persistence.EntityManager;
 
@Repository
@Transactional
public class CourseRepository {
 
    @Autowired
    EntityManager em;
 
    public void playWithEntityManager() {
        Course course1 = new Course("EntityManager 111", "em");      // 객체를 생성한 상태 > 비영속
        em.persist(course1);                                         // 객체를 저장한 상태 > 영속
 
        Course course2 = new Course("EntityManager 222", "em");
        em.persist(course2);
 
        Course course3 = new Course("EntityManager 333", "em");
        em.persist(course3);       // 엔티티매니저는 1차 캐시에 위의 엔티티를 저장하고 쓰기 지연 SQL 저장소에 쌓아 놓음
 
        em.flush();            // flush()를 통해 쓰기 지연 SQL 저장소의 쿼리들을 DB로 보냄
 
       // course1, course2, course3 모두 영속 상태
        em.detach(course2);      // 엔티티매니저에서 엔티티를 분리함 (관리를 그만둠) > 준영속 상태
 
        course1.setName("EntityManager 111 - updated");          // merge 안했는데 알아서 update됨 : 영속 상태이기 떄문 > dirty checking
        course2.setName("EntityManager 222 - updated");          // 준영속상태의 course2는 update 안됨
        course3.setName("EntityManager 333 - updated");
 
        em.flush();
    }
 
}
cs

 

DB 결과를 보면 flush 해주기 전 준영속상태가 되어버린 강의2번만 update가 되지 않았다.

두 강의는 merge 해주지 않았는데도 update 되었다.

 

엔티티매니저가 엔티티의 초기 상태인 스냅샷과 비교하여 변경감지를 해 알아서 update 해준것이다.

 

 

그런데 여기서 flush 해주기 전에 강의3을 refresh 한다면

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import com.udemy.jpa_study.indepth.domain.Course;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
 
import javax.persistence.EntityManager;
 
@Repository
@Transactional
public class CourseRepository {
 
    @Autowired
    EntityManager em;
 
    public void playWithEntityManager() {
        Course course1 = new Course("EntityManager 111", "em");      // 객체를 생성한 상태 > 비영속
        em.persist(course1);                                         // 객체를 저장한 상태 > 영속
 
        Course course2 = new Course("EntityManager 222", "em");
        em.persist(course2);
 
        Course course3 = new Course("EntityManager 333", "em");
        em.persist(course3);       // 엔티티매니저는 1차 캐시에 위의 엔티티를 저장하고 쓰기 지연 SQL 저장소에 쌓아 놓음
 
        em.flush();            // flush()를 통해 쓰기 지연 SQL 저장소의 쿼리들을 DB로 보냄
 
       // course1, course2, course3 모두 영속 상태
        em.detach(course2);      // 엔티티매니저에서 엔티티를 분리함 (관리를 그만둠) > 준영속 상태
 
        course1.setName("EntityManager 111 - updated");          // merge 안했는데 알아서 update됨 : 영속 상태이기 떄문 > dirty checking
        course2.setName("EntityManager 222 - updated");          // 준영속상태의 course2는 update 안됨
        course3.setName("EntityManager 333 - updated");
 
        em.refresh(course3);            // 원래 영속되어 있던 원본 엔티티를 다시 불러옴
        em.flush();
    }
 
}
cs

 

변경 감지 하지 않고 스냅샷 상태로 불러와 업데이트가 되질 않는다.

 

 

준영속 상태를 만들때 엔티티매니저의 모든 엔티티를 초기화하는 clear( )와 엔티티매니저 자체를 종료하는 close( ) 도 있다. 

 

 

영속성 컨텍스트를 통해 엔티티매니저는 해당 이점을 챙길 수 있다.

  • 1차 캐시 : Transaction 동안에만 살아 있는 캐시 (영속상태의 엔티티 저장)  : 요청이 들어오면 1차 캐시에서 먼저 찾고 없으면 DB로 요청함
  • 동일성 보장 : 같은 엔티티임을 확인
  • 트랜잭션을 지원하는 쓰기 지연 : Transaction 동안 요청이 여러번 오는 경우 쓰기 지연 SQL 저장소에 쌓아두고 한번에 DB에 보냄 (flush, commit)
  • 변경 감지 (Dirty checking) : 영속상태의 엔티티와 스냅샷(최초 상태)을 비교해 변경사항이 있다면 자동으로 update 명령 날림 
  • 지연 로딩 (Lazy Loading) : 연관관계가 있는 엔티티 실사용 전까지 DB 조회를 지연함으로 효율성 높임 > 그 전까지 가짜 객체 (프록시 객체)를 둠 
    • em.getReference( )

 

 


코드

https://github.com/recordbuffer/TIL/tree/54e7dd29574361f656cf078edc7ab729533d15fc/Spring_Boot/jpa_study/src/main/java/com/udemy/jpa_study/indepth

 

GitHub - recordbuffer/TIL: Today I Learned

Today I Learned. Contribute to recordbuffer/TIL development by creating an account on GitHub.

github.com

참고

https://www.udemy.com/share/101XoW3@elgoPx-8G1T8cav5XKpLRW3l1YMiA0G9QVjj9LVY4DZHANcC8MBEdqpgFoppJBXcag==/

 

Master Hibernate and JPA with Spring Boot in 100 Steps

Learn Hibernate, JPA (Java Persistence API) and Spring Data JPA using Spring and Spring Boot

www.udemy.com

http://www.kyobobook.co.kr/product/detailViewKor.laf?ejkGb=KOR&mallGb=KOR&barcode=9788960777330&orderClick=LAG&Kc= 

 

자바 ORM 표준 JPA 프로그래밍 - 교보문고

스프링 데이터 예제 프로젝트로 배우는 전자정부 표준 데이터베이스 프레임 | ★ 이 책에서 다루는 내용 ★■ JPA 기초 이론과 핵심 원리■ JPA로 도메인 모델을 설계하는 과정을 예제 중심으로

www.kyobobook.co.kr

https://youtu.be/PMNSeD25Qko

 

반응형

댓글