[JPA] 영속성 컨텍스트란? 그리고 영속성 관리
안녕하세요!
키크니 개발자 입니다. 🦒
인프런 김영한님 강의를 보면서 정리하는 글입니다.
1. 영속성 컨텍스트란?
JPA를 이해하는데 가장 중요한 용어이며, 엔티티를 영구 저장하는 환경이라는 뜻을 가지고 있습니다.
DB에 저장을 한다는 것이 아니라 영속성 컨텍스트를 통해서 엔티티를 영속화 한다는 뜻입니다.
영속화란? 영속성 컨텍스트에 관리되는 상태로 만드는 것을 말합니다.
EntityManager.persist(entity);
영속성 컨텍스트는 엔티티 매니저를 통해서 접근합니다.
2. 엔티티의 생명주기
- 비영속(new/transient)
영속성 컨텍스트와 전혀 관계가 없는 새로운 상태를 의미합니다.
객체를 생성한 상태로 볼 수 있습니다.
Member member = new Member();
member.setId("rinikim");
member.setUsername("키크니개발자");
- 영속(managed)
영속성 컨텍스트에 관리되는 상태를 의미합니다.
영속상태가 된다고 해서 DB에 저장되지 않습니다.
트랜잭션이 commit 하는 시점에 영속성 된 객체가 DB로 쿼리가 날라가게 됩니다.
Member member = new Member();
member.setId("rinikim");
member.setUsername("키크니 개발자");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
em.persist(member);
💡 em.getTransaction().begin(); : 엔티티 매니저에서 트랜잭션을 실행할 수 있도록 begin() 메서드를 시작합니다.
그리고 실제 데이터베이스에 변경 사항을 반영할 때 commit을 날리는데 commit을 날릴 때 정상적으로 데이터베이스에 변경 사항이 반영 되면 커넥션을 종료합니다.
💡em.persist(member); : 객체를 저장한 상태입니다. 즉, 영속상태를 의미합니다.
- 준영속(detached)
영속성 컨텍스트에 저장되었다가 분리된 상태를 의미합니다.
em.detach(member);
- 삭제(removed)
객체를 삭제한 상태를 의미합니다.
em.remove(member);
3. 영속성 컨텍스트의 이점
- 1차 캐시
영속성 컨텍스트 = 1차캐시 라고 이해해도 됩니다. (영속성 컨텍스트 내부에 1차 캐시가 있기 때문입니다.)
Member member = new Member();
member.setId("rinikim");
member.setUsername("키크니개발자");
em.persist(member);
Member findMember = em.find(Member.class, "rinikim");
persist 한 후 find를 하게 되면 DB에서 값을 조회하는 것이 아니라 1차캐시에서 값을 조회하게 됩니다.
1차캐시에서 'rinikim'을 조회하여 값이 있으면 캐시값을 조회해서 보여줍니다.
만약 member2 Id가 1차 캐시에서 값을 찾지 못하면 DB에서 조회해옵니다.
이 때 1차캐시에 저장한 후 반환하는데, 이후에 member2 조회하게 되면 1차캐시에서 조회해옵니다.
하지만 이는 큰 도움은 되지 않는다고 말씀하셨습니다! 왜냐하면 영속성 컨텍스트는 대부분 트랜잭션 단위로 만들고,
DB 트랜잭션이 끝나게 되면, 영속성 컨텍스트도 종료시키기 때문입니다.
- 동일성 보장
1차 캐시로 반복 가능한 읽기 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공합니다.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); // true
- 트랜잭션을 지원하는 쓰기 지연
memberA, memberB 둘다 persist 했을 경우 Insert를 날리지 않고, 쓰기지연 SQL 저장소에 쌓아논 후 commit을 했을 때 Insert를 날린다.
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋
- 변경 감지(Dirty Checking)
JPA는 DB를 트랜잭션 commit 하는 시점에서 flush를 합니다.
그 다음 entity 스냅샷을 비교합니다.
값을 최초로 읽어온 시점(최초로 영속성 컨텍스트에 넣은 시점)을 스냅샷을 떠놓고,
비교해서 달라진 점을 쓰기 지연 SQL 저장소에 쌓아놓은 후 flush를 하면서 반영하게 됩니다.
Member member = em.find(Member.class, 150L);
member.setName("rinikim");
em.persist(member); // 이 코드는 사용하지 않아도 변경감지를 하여 update query를 날린다.
- 지연 로딩(Lazy Loading)
4. flush(플러시)
영속성 컨텍스트의 변경내용을 DB에 반영합니다. (영속성 컨텍스트의 변경내용을 데이터베이스에 동기화합니다.)
플러시 발생은 변경 감지, 수정된 엔티티 쓰기 지연 SQL 저장소에 등록, 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송합니다.
플러시를 해도 1차 캐시는 지워지지 않고 유지됩니다.
(쓰기 지연 SQL 저장소에 쌓인 데이터들이 DB에 반영이 되는 과정이라고 생각하면 됩니다.)
- 영속성 컨텍스트를 플러시하는 방법
1. em.flush() - 직접 호출
2. 트랜잭션 커밋 - 플러시 자동 호출
3. JPQL 쿼리 실행 - 플러시 자동 호출
⭐️ 참고한 곳
인프런 : 자바 ORM 표준 JPA 프로그래밍 - 기본편 (김영한님)
https://www.inflearn.com/course/ORM-JPA-Basic
https://happygrammer.tistory.com/147
https://willseungh0.tistory.com/65
배워야 할 것이 더 많은 주니어 개발자입니다. 🐣
내용 전달보다는 정리를 목적으로 포스팅을 하고 있습니다.
잘못 된 내용이나 부족한 부분은 댓글로 주시면 감사드리겠습니다.