JPA 내부구조
JPA를 공부하면서 정리를 한 내용들 입니다.
-참고 김영한 개발자님 : [토크ON세미나] JPA 프로그래밍 기본기 다지기 - JPA 내부구조 | T아카데미 |
6강. JPA 내부구조
1.JPA에서 가장 중요한 2가지
-
객체와 관계형 데이터베이스 매핑하기
-
영속성 컨텍스트(PersistenceContext)
2.영속성 컨텍스트란?
JPA
를 이해하는데 가장 중요한 용어- 엔티티를 영구 저장하는 환경이라는 뜻
EntityManager.persist(entity)
;- 영속성 컨텍스트는 논리적인 개념(눈에 보이지 않습니다.)
- 엔티티 매니저를 통해서 영속성 컨텍스트에 접근
J2SE
환경에서는 엔티티 매니저와 영속성 컨텍스트가 1:1J2EE
, 스프링 프레임워크 같은 컨테이너 환경에서는 엔티티 매니저와 영속성 컨텍스트가 N:1- 같은 트랜잭션이면 같은 영속성 컨텍스트에 접근하게 됩니다.
Java SE(Standard Edition) : 자바 스탠다드 에디션은 가장 보편적으로 쓰이는 자바 API집합체입니다.
Java EE(Enterprise Edition) : 자바 엔터프라이즈 에디션은 자바를 이용한 서버측 개발을 위한 플랫폼입니다. Java EE는 표준 플랫폼인 Java SE를 사용하는 서버를 위한 플랫폼입니다.
3.엔티티의 생명주기
비영속(new/transient)
- 영속성 컨텍스트와 전혀 관계가 없는 상태
Member
객체를 생성만 한 상태
//객체를 생성한 상태(비영속)
Member member = new Member();
member.setId("memberId");
member.setUsername("회원1");
영속(managed)
- 영속성 컨텍스트에 저장된 상태
- 객체를 생성하고 저장한 상태(영속성 컨텍스트에 의해서 객체가 관리(
managed
)되는 상태)
Member member = new Member();
member.setId("memberId");
member.setUsername("회원1");
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
//객체를 저장한 상태(영속)
em.persist(member);
준영속(detached)
- 영속성 컨텍스트에 저장되었다가 분리 (관리를 포기한) 상태
em.detach(member)
;
삭제(removed)
- 삭제된 상태
em.remove(member)
;
4.영속성 컨텍스트의 이점
왜 이렇게 중간에 영속성 컨텍스트
라는게 존재하는가?
Member
를 쿼리를 통해서 DB에 넣어버리면 되지,
왜 중간상태가 있어야하는가?라고 생각할 수 있습니다.
그렇기에 이것에 대한 이 점은 아래와 같습니다.
1차 캐시
PersistenceContext
에는 내부에 1차 캐시
가 있습니다.
일반적인 캐시가 아니라 영속성컨텍스가 생성되고 없어질 때 까지만 잠깐 존재하는 메모리입니다.
- 1차 캐시에서 조회를 하면?
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");
//1차 캐시에 저장됌
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");
em.find()
에서 DB
로 바로가는 것이 아니라 1차 캐시를 먼저 탐색합니다.
-
존재하면 바로 반환
-
1차 캐시에 없으면 데이터베이스에서 조회하고
조회된 내용을 1차 캐시에 저장 후에 반환합니다.
영속 엔티티의 동일성(identity) 보장
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); // 동일성 비교 true (1차캐시가 있기 때문에)
1차 캐시로 반복 가능한 읽기(REPEATABLE READ)
등급의 트랜잭션 격리 수준을
데이터베이스가 아닌 어플리케이션 차원에서 제공합니다.
트랜잭션을 지원하는 쓰기 지연(버퍼 기능)
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();
em.persist(memberA);
em.persist(memberB);
transaction.commit(); // 커밋
-
persist(memberA)
명령의 경우memberA
를1차 캐시
에 저장하면서
동시에INSERT SQL
을 생성해서쓰기 지연 SQL 저장소
에 말아둡니다. -
이후에
persist(memberB)
명령의 경우도 위와 같이 동작하고, 아직 DB에 넣지 않습니다. -
이후에
commit()
명령을 해야쓰기 지연 SQL 저장소
에 있던INSERT SQL
쿼리 2개를(옵션에 따라 동시에 혹은 하나씩) DB에 넣습니다. -
쓰기 지연 SQL 저장소
에 있던 쿼리들을 날리는 과정을flush
라고 합니다.- 하지만
flush
를 한다고 1차캐시의 내용들이 지워지는 것이 아니라 쿼리를 보내서 DB와 싱크를 맞추는 역할을 합니다. commit()
이flush
와commit
두 가지 일을 합니다.
- 하지만
변경 감지(Dirty Checking)
EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
// 엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // [트랜잭션] 시작
// 영속 엔티티 조회
Member memberA = em.find(Member.class, "memberA");
// 영속 엔티티 데이터 수정
memberA.setName("hjs");
memberA.setAge(10);
// em.update(member) 이런 코드가 있어야 하지 않을까?
transaction.commit(); // [트랜잭션] 커밋
왜 이렇게 되는가?
JPA
는 트랜잭션이 커밋되는 시점에 1차 캐시
뿐만 아니라 스냅샷
도 생성합니다.
commit()
명령으로 flush
를 하면 영속성 컨텍스트에 의해 관리되는 엔티티들을
스냅샷과 비교해서 바뀐 부분이 있으면 UPDATE 쿼리
를 만들어서 DB에 보내고 commit
을 합니다.
이렇게 하는 이유
Java 컬렉션에서 값을 가져와서 변경해도 다시 컬렉션에 값을 담지 않습니다.
그래도 컬렉션의 값이 바뀝니다. 그것과 똑같은 컨셉입니다.
마치 Java 컬렉션에서 값을 가져와서 변경하는 것 처럼하기 위해 이런 방식으로 처리합니다.