출처
https://www.inflearn.com/course/ORM-JPA-Basic/dashboard
목차
JPA에서 가장 중요한 2가지
- 객체와 관계형 데이터베이스 매핑 (Object Relational Mapping)
- 영속성 컨텍스트 이해 ⭐
해당 글은 2번 영속성 컨텍스트에 관해 작성하는 글입니다.
JPA 기본 동작 원리 이해: 엔티티 매니저 팩토리와 엔티티 매니저
- 웹 애플리케이션이 시작되면, EntityManagerFactory가 생성됩니다.
- 이후 사용자의 요청이 들어올 때마다 EntityManagerFactory에서 각각의 EntityManager를 생성합니다.
- EntityManager는 커넥션 풀의 커넥션을 통해 DB에 접근하여 사용자 요청을 처리합니다.
DB에 데이터를 저장
EntityManager를 통해 DB에 사용자 데이터를 저장할 때 다음과 같이 작성합니다.
EntityManager.persist(entity);
이때, 사용되는 개념이 영속성 컨텍스트입니다.
영속성 컨텍스트?
- "엔티티를 영구 저장하는 환경" 이라는 뜻입니다.
- 영속성 컨텍스트는 논리적인 개념입니다.
- 눈에 보이지 않습니다.
- EntityManager.persist(entity); 와 같이 사용합니다.
- 사실, 해당 코드를 작성하면 entity 가 DB에 저장이 된다고 생각할 수 있으나, 실제로 정확한 의미는 DB가 아니라 엔티티 영속성 컨텍스트에 영속화한다는 뜻입니다.
- 즉, 엔티티 매니저를 통해 영속성 컨텍스트에 엔티티를 저장한다고 이해하면 됩니다.
- DB에는 영속성 컨텍스트를 통해 접근합니다.
- 영속성 컨텍스트 → DB
- JPA를 이해하는 데 가장 중요한 용어입니다.
엔티티 매니저와 영속성 컨텍스트의 관계
J2SE 환경
- 엔티티 매니저를 생성하면, 일대일로 영속성 컨텍스트가 생성이 됩니다.
- 쉽게 이야기하면, EntityManager 안에 보이지 않는 PersistenceContext 공간이 생성된다고 생각하면 됩니다.
J2EE, 스프링 프레임워크와 같은 컨테이너 환경
- 여러 EntityManager가 한 개의 PersistentContext를 공유합니다.
엔티티의 생명주기
영속성 컨텍스트 내의 엔티티의 생명주기는 다음과 같이 구분됩니다.
- 비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 새로 생성된 상태
- 영속(managed): 영속성 컨텍스트에 저장되어 관리되는 상태
- 준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제(removed): 삭제(제거)된 상태
비영속(new/transient)
- 객체를 생성만 했을 때의 상태입니다.
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
영속(managed)
- 영속성 컨텍스트에 저장하여 관리되는 상태입니다.
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
EntityManager em = emf.createEntityManager(); //emf는 EntityManagerFactory
em.getTransaction().begin(); //트랜잭션 시작
//객체를 저장한 상태(영속)
em.persist(member);
준영속(detached)
- 영속 → 준영속
- 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached) 되는 것을 의미합니다.
- 영속성 컨텍스트가 제공하는 기능을 사용하지 못합니다.
- persist()한 후, detach() 하면 영속성 컨텍스트에서 분리됩니다.
//엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);
준영속 상태로 만드는 방법
- em.detach(entity) : 특정 엔티티만 준영속 상태로 전환. 즉, 해당 엔티티는 데이터베이스 트랜잭션을 실행해도 아무런 일이 일어나지 않는다.
- em.clear() : 영속성 컨텍스트 전체를 초기화
- em.close() : 영속성 컨텍스트 종료
삭제(removed)
- 실제 DB에 삭제를 요청한 상태입니다.
//객체를 삭제한 상태(삭제)
em.remove(member);
영속성 컨텍스트의 이점
- 1차 캐시
- 동일성(identity) 보장
- 트랜잭션을 지원하는 쓰기 지연(transactional write-behind)
- 변경 감지(Dirty Checking)
- 지연 로딩(Lazy Loading)
엔티티 조회, 1차 캐시
//엔티티를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//엔티티를 영속
em.persist(member);
1차 캐시에서 조회
- 영속성 컨텍스트에 조회하고자 하는 엔티티가 저장되어 있다면 DB가 아닌 1차 캐시에서 조회합니다.
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//1차 캐시에 저장됨
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
데이터베이스에서 조회
- 만약, 조회하고자 하는 엔티티가 1차 캐시에 없다면, 데이터베이스에서 조회하여 1차 캐시에 저장한 후 반환합니다.
Member findMember = em.find(Member.class, "member2");
- 찾고자 하는 member2가 1차 캐시에 없습니다.
- DB에서 조회합니다.
- 조회한 member2를 1차 캐시에 저장합니다.
- member2를 반환합니다.
- 이후에 다시 member2를 조회한다면, DB 조회없이 1차 캐시에 있는 member2가 반환됩니다.
참고 사실 큰 이점이 되지 않는다.
엔티티 매니저는 보통 데이터베이스 트랜잭션 단위로 생성됩니다. 따라서, 트랜잭션이 끝날 때 영속성 컨텍스트도 종료됩니다. 즉, 고객의 요청이 들어오고 끝나면 영속 컨텍스트를 지웁니다.
이득이 되는 순간은 고객의 요청이 들어오고 끝나기까지 굉장히 짧은 찰나의 순간입니다. 하지만, 비지니스 로직이 굉장히 복잡한 경우엔 도움이 될 수도 있습니다.
참고로, 여러 명의 고객이 사용하거나, 애플리케이션 전체에서 공유하는 캐시는 JPA에서 2차 캐시라고 부릅니다.
영속 엔티티의 동일성 보장
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a==b); //동일성 비교 true
- 영속성 컨텍스트를 사용하지 않는다면, em.find를 통해 조회한 객체의 동일성을 비교하면 false가 반환됩니다.
- 하지만, 영속성 컨텍스트를 사용하면 동일 트랜잭션 내에서는 같은 객체를 반환하여 true가 반환됩니다.
- 즉, 1차 캐시로 반복 가능한 읽기(REPEATABLE READ) 등급의 트랜잭션 격리 수준을 데이터베이스가 아닌 애플리케이션 차원에서 제공합니다.
엔티티 등록 - 트랜잭션을 지원하는 쓰기 지연
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
//엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 한다.
transaction.begin(); //[트랜잭션] 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 데이터베이스에 보내지 않는다.
//커밋하는 순간, 데이터베이스에 INSERT SQL을 보낸다.
transaction.commit(); //[트랜잭션] 커밋
엔티티 수정 - 변경 감지(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(); //[트랜잭션] 커밋
변경 감지 (Dirty Checking)
- 객체를 수정 후 em.update(member); 와 같은 코드를 작성하지 않아도 스스로 감지하여 수정 SQL을 생성합니다.
- 마치, 자바 컬렉션에서 객체를 꺼낸 후 수정해서 다시 넣지 않는 것과 동일한 로직으로 동작합니다.
- commit()을 호출하면, 내부적으로 flush()가 호출됩니다.
- 1차 캐시 안에는 스냅샷이 있는데, 스냅샷이란 최초로 영속성 컨텍스트에 들어온 상태를 의미합니다. 해당 스냅샷과 들어온 엔티티를 비교합니다.
- 엔티티와 스냅샷과 비교하여 변경된 부분을 UPDATE SQL로 생성합니다.
- UPDATE SQL을 데이터베이스에 반영합니다.
- 커밋이 완료됩니다.
엔티티 삭제
//삭제 대상 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
em.remove(memberA); //엔티티 삭제
플러시(flush)
영속성 컨텍스트의 변경 내용을 데이터베이스에 반영합니다.
플러시가 발생한다면?
- 엔티티와 스냅샷를 비교하여 변경 감지
- 수정된 부분을 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (등록, 수정, 삭제 쿼리)
플러시를 발생시키는 방법
- em.flush() 호출 - 직접 호출
- 트랜잭션 커밋 - 플러시 자동 호출
- JPQL 쿼리 실행 - 플러시 자동 호출
참고 플러시를 직접 호출해도 1차 캐시는 유지가 됩니다. 단지, 변경된 내용이 DB에 반영이 되는 것 뿐입니다.
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();
- 현재 memberA, memberB, memberC를 데이터베이스 삽입하는 코드를 작성하였습니다.
- 그런데, 중간에 JPQL을 통해 해당 객체를 조회하는 코드를 작성하면 어떻게 될까요?
- JPQL은 SQL로 변경되어 실행됩니다.
- 따라서, memberA와 memberB와 memberC는 아직 데이터베이스에 반영이 되지 않았기 때문에 조회가 되지 않습니다.
- 따라서, JPQL은 기본 모드가 플러시를 자동 호출하는 것입니다.
참고 아래의 코드는 커밋 시점에 INSERT SQL만 실행됩니다. (1차 캐시에서 조회하기 때문에 SELECT SQL이 실행되지 않습니다.)
em.persist(memberA); em.find(Member.class, 1000L); tx.commit();
플러시 모드 옵션 설정
- FlushModeType.AUTO : 커밋이나 쿼리를 실행할 때 플러시합니다.(기본값)
- FlushModeType.COMMIT : 커밋할 때만 플러시합니다.
em.setFlushMode(FlushModeType.COMMIT)
플러시 정리
- 영속성 컨텍스트를 비우지 않습니다.
- 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 것입니다.
- 트랜잭션이라는 작업 단위가 중요합니다. → 커밋 직전에만 동기화하면 됩니다.
'Backend > JPA' 카테고리의 다른 글
[JPA] 다양한 연관관계 매핑 (0) | 2023.12.18 |
---|---|
[JPA] 연관관계 매핑 (0) | 2023.12.18 |
[JPA] 엔티티 매핑 (0) | 2023.12.18 |
[JPA] Hello JPA (0) | 2023.12.02 |
[JPA] JPA 소개 (0) | 2023.12.01 |