이 글은 2021년 5월에 작성되었으며 블로그를 이전하며 옮기게 되었습니다.
시작하기 앞서 저의 글에 대한 피드백이나 지적은 언제나 환영입니다 😊
영속성 컨텍스트란?
- 엔티티를 영구 저장하는 환경이라는 뜻
- 애플리케이션과 데이터베이스 사이에서 객체를 보관하는 가상의 데이터베이스 같은 역할을 한다.
- 엔티티 매니저를 통해 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 콘텍스트에 엔티티를 보관하고 관리한다.
em.persist(member);
엔티티 매니저를 사용해 member(회원 엔티티)를 영속성 콘텍스트에 저장한다는 의미이다.
영속성 콘텍스트의 특징
- 엔티티 매니저를 생성할 때 하나 만들어진다.
- 엔티티 매니저를 통해 영속성 콘텍스트에 접근하고 관리할 수 있다.
엔티티의 생명주기
- 비영속 (new / transient)
: 영속성 콘텍스트와 전혀 관계가 없는 새로운 상태 - 영속 (managed)
: 영속성 콘텍스트에 관리되는 상태 - 준영속 (detached)
: 영속성 컨텍스트에 저장되어있다가 분리된 상태 - 삭제 (removed)
: 삭제된 상태
비영속
: 객체를 생성한 상태, 아직 영속성 컨텍스트에 저장하지 않은 상태.
Member member = new Member();
영속
: 객체를 저장한 상태, 영속성 컨텍스트에 의해 관리된다
em.persist(member);
준영속
: 회원 엔티티를 영속성 콘텍스트에서 분리, 준영속 상태
em.detach(member);
- 영속 -> 준영속
- 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)
- 영속성 콘텍스트가 제공하는 기능을 사용하지 못함
: (1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 어떠한 기능도 동작하지 않음.) //특정 엔티티만 준영속 상태로 전환 -> 영속성 컨텍스트에서 분리하는 것 em.detach(member);
//영속성 콘텍스트를 완전히 초기화 -> 관리되던 엔티티는 준영속 상태가 됨
em.clear();
//영속성 컨텍스트를 종료 -> 관리되던 엔티티는 준영속 상태가 됨
em.close();
#### 삭제
: 객체를 삭제한 상태
```java
em.remove(member);
- DB에서도 삭제가 된다.
영속성 콘텍스트의 이점
- 1차 캐시
- 동일성(identity) 보장
- 트랜잭션을 지원하는 쓰기 지연 (transactional write-behind)
- 변경 감지(Dirty Checking)
- 지연 로딩(Lazy Loading)
엔티티 조회, 1차 캐시
//1차 캐시에 저장됨
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
데이터베이스에서 조회
Member findMember2 = em.find(Member.class, "member2");
영속 엔티티의 동일성 보장
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true
변수들은 서로 다른 메모리에서 생성되기 때문에 동일성 비교를 했을 때 동일성이 보장되지 않지만
영속된 엔티티들은 영속성 콘텍스트가 관리해주므로 1차 캐시에 있는 같은 엔티티를 반환받았기 때문에 같은 인스턴스 이므로 동일성이 보장된다
동일성 비교 : 실제 인스턴스가 같다
==
을 사용해 비교한다.동등성 비교 : 실제 인스턴스는 다를 수 있지만 인스턴스가 가지고 있는 값이 같다.
->equals() 메서드를
사용하여 비교한다
엔티티 등록, 트랜잭션을 지원하는 쓰기 지연
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
em.persist(memberA); em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); // [트랜잭션] 커밋
즉, 내부 SQL저장소에 쿼리문들을 모아뒀다가 트랜잭션이 커밋될 때 모아둔 쿼리문을 DB에 보낸다.
: 이것을 트랜잭션을 지원하는 쓰기 지연이라고 한다.
엔티티 수정, 변경 감지(Dirty Checking)
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin(); // [트랜잭션] 시작
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setUsername("hi");
memberA.setAge(10);
//em.update(member) 이런 코드가 있어야 하지 않을까?
transaction.commit(); // [트랜잭션] 커밋
:위처럼 update 같은 코드가 있어야 하지 않을까?
결과부터 말하자면 없어도 된다.
영속성 콘텍스트가 관리해주는 엔티티는
플러시 시점에 1차 캐시(스냅숏)와 비교해 변경된 점이 있다면 자동으로 update쿼리문을 날려준다.
엔티티 삭제
//삭제 대상 엔티티 조회
Member memberA = em.find(Member.class, “memberA");
em.remove(memberA); //엔티티 삭제, DB에서도 삭제됨
플러시
- 영속성 콘텍스트의 변경내용을 DB에 반영하는 것
플러시 발생
- 변경 감지
- 수정된 엔티티 내부 SQL저장소에 등록
- 내부 SQL저장소의 쿼리를 데이터베이스에 전송(등록, 수정, 삭제 쿼리)
영속성 콘텍스트를 플러시 하는 법
em.flush()
로 직접 호출하는 방법- 트랜잭션 커밋 시점에 플러시가 자동으로 호출된다.
- JPQL쿼리 실행 시 플러시가 자동으로 호출된다.
JPQL쿼리 실행 시 플러시가 자동으로 호출???
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//중간에 JPQL 실행
query = em.createQuery("select m from Member m", Member.class);
List<Member> members= query.getResultList();
: 플러시 모드 옵션을 보면 알 수 있다.
em.setFlushMode(FlushModeType.AUTO)
: 커밋이나 쿼리를 실행할 때 플러시 (defualt값)em.setFlushMode(FlushModeType.COMMIT)
: 커밋할 때만 플러시
플러시는?
- 영속성 콘텍스트를 비우지 않는다!
- 영속성 콘텍스트의 변경내용을 데이터베이스에 동기화하는 것!
- 트랜잭션이라는 작업 단위가 중요하다! -> 커밋 직전에만 동기화하면 된다.
글을 마치며.
JPA에서는 가장 중요한 부분이 2개 있다고 합니다.
바로 영속성 관리와 연관관계 매핑입니다.
그중 중요한 한 가지 영속성 관리에 대해 정리해봤습니다.
영속성 콘텍스트의 내부 동작 방식을 이해하며 굉장히 흥미로웠습니다.
그리고 이 내부 동작 방식을 이해하지 못하면 JPA를 활용한 개발은 암기가 될 것 같다고 생각이 들었습니다.
긴 글 읽어주셔서 감사합니다.😊
'JPA' 카테고리의 다른 글
JPA 값타입 (0) | 2022.03.25 |
---|---|
JPA 프록시와 연관관계 관리 (0) | 2022.03.25 |
JPA 상속관계 매핑과 고급매핑 (0) | 2022.03.25 |
JPA 다양한 연관관계 매핑 (0) | 2022.03.25 |
JPA 연관관계 매핑과 연관관계 주인 (0) | 2022.03.25 |
댓글