-
Notifications
You must be signed in to change notification settings - Fork 0
JPA 영속성 컨텍스트
YoungMinKim edited this page Jul 18, 2021
·
2 revisions
- 영속성 컨텍스트란?
- JPA에서 가장 중요한 영속성 컨텍스트에 대한 지식 습득
- 1차 캐시와 쓰기 지연 저장소에 대한 부분 이해
- JPQL, 플러시
- `IDE` : Intellij
- `WAS` : Tomcat 9
- `DataBase` : H2
- `Build` : Maven
-
객체와 관계형 데이터베이스 매핑하기(Object Relational Mapping)
- 객체와 관계형 데이터베이스를 어떻게 매핑할것인가?
- DB를 어떻게 설계하고, 객체를 어떻게 설계해서 이거를 중간에서 JPA로 매핑해서 사용 할 것인가?
-
영속성 컨텍스트
- 실제 JPA가 내부에서 어떻게 동작 하는가?
JPA를 이해하기 위해서는 영속성 컨텍스트를 이해해야 한다
- JPA를 이해하는데 가장 중요한 용어
-
엔티티를 영구 저장하는 환경이라는 뜻
- 영속성 컨텍스트는 눈에 보이지 않는다.
- 영속성 컨텍스트는 엔티티 매니저를 통해 접근한다.
-
EntityManager.persist(entity);
- 여기서 persist 메서드는 DB에 저장하는게 아니라, 영속성 컨텍스트에 저장하는 것을 의미
- (중요) 엔티티 매니저를 생성하면 그 안에 1:1로 영속성 컨텍스트가 생성이 된다.
- 엔티티 매니저 안에 눈에 보이지 않는 공간이 생긴다 생각.
-
비영속 (new/transient)
- 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태
//영속성 컨텍스트와 전혀 관계과 없는 새로운 상태 Member member = new Member(); member.setId(105L); member.setName("HelloJPA");
-
영속 (managed)
- 영속성 컨텍스트에 관리되는 상태
Member member = new Member(); member.setId(105L); member.setName("HelloJPA"); //1. 영속성 컨텍스트에 관리되는 상태 //2. em.persist() 호출시 영속성 컨텍스트의 1차 캐시와, 쓰기 지연 SQL 저장소에 해당 쿼리가 저장된다 //3. Transaction commit시 쿼리가 날라간다 em.persist(member);
-
준영속 (detached)
- 영속성 컨텍스트에 저장되었다가 분리된 상태
Member member = new Member(); member.setId(105L); member.setName("HelloJPA"); //영속성 컨텍스트에 관리되는 상태 //em.persist() 호출시 영속성 컨텍스트의 1차 캐시와, 쓰기 지연 SQL 저장소에 해당 쿼리가 저장된다 em.persist(member); em.detached(member); //영속성 컨텍스트의 상태를 준영속으로 변경 및 분리
-
삭제 (remove)
- 삭제된 상태
Member member = new Member(); member.setId(105L); member.setName("HelloJPA"); //영속성 컨텍스트에 관리되는 상태 //em.persist() 호출시 영속성 컨텍스트의 1차 캐시와, 쓰기 지연 SQL 저장소에 해당 쿼리가 저장된다 em.persist(member); em.remove(member);
- 애플리케이션과 DB사이에 보이지 않는 어떤 부분이 존재한다?
- 1차 캐시
- 동일성 보장
- 트랜잭션을 지원하는 쓰기 지연 (transaction write-behind)
- 변경 감지 (Dirty Checking)
- 지연 로딩 (Lazy Loading)
- 영속성 컨텍스트는 내부에 1차 캐시를 가지고 있다.
//비영속 상태
Member member = new Member();
member.setId("member1");
member.setUserName("회원1");
//엔티티를 영속
em.persist(member);
//1차 캐시
+--------------------+
|@id Entity |
|"member1" member1|
|"member2" member2|
| |
+--------------------+
- 조회를 하는경우
- JPA는 우선 1차 캐시에 있는 값을 뒤진다.
- em.find(Member.class, "member1");
- 만약 member1 값이 영속성 컨텍스트의 1차 캐시 영역에 존재한다면, DB에 접근하지 않고 1차 캐시에서 값을 가져온다
- 만약 1차 캐시에 해당 key값(member2)이 없을 시 DB 조회를 한 후에 해당 데이터를 member2의 Entity에 저장한다
- 위 같은 상황을 통해 member2를 조회 시 DB가 아닌 1차 캐시에서 Entity를 조회한다
- 위 상황은 1차 캐시에 @id member2가 없다는 가정하에 생각을 이어 나가야 한다
- 1차 캐시는 하나의 트랜잭션이 끝나면 소멸, 찰나의 순간에서만 효과가 있다
- JPA는 우선 1차 캐시에 있는 값을 뒤진다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction(); // 트랜잭션을 얻는다
tx.begin(); // 트랜잭션 시작 -> DB 트랜잭션 시작
Member a = em.find(Member.class, "member1"); //여기서 가져온 Entity는 DB에서 가져온 값으로, 1차캐시 영역에 저장이 된다
Member b = em.find(Member.class, "member1"); //여기서 가져온 Entity는 DB가 아닌, 1차 캐시에서 값을 가져온다
- 첫번째 라인은 DB에서 데이터를 가져온다.
- 두번째 라인은 영속성 컨텍스트 안의 1차 캐시에서 해당 데이터를 가져온다.
Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 True
- Java Collection에서 값을 꺼내 비교를 하면 True를 반환 하는 것처럼, JPA는 동일한 Entity의 동일성을 보장한다.
트랜잭션을 지원하는 쓰기 지연:
- 영속성 컨텍스트 안에는 1차 캐시뿐만 아니라, 쓰기 지연 SQL 저장소가 존재.
1. persist(memberA)를 넣으면 memberA가 1차 캐시로 들어가고 동시에 JPA가 해당 엔티티를 분석.
2. INSERT 쿼리를 생성 후 쓰기 지연 SQL 저장소에 저장해둔다.
3. 하나의 트랜젝션 내에서 commit 명령어가 호출이 될 경우 쓰기 지연 SQL 저장소에 있는 쿼리가 DB에 날라간다.
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction(); // 트랜잭션을 얻는다
tx.begin(); // 트랜잭션 시작 -> DB 트랜잭션 시작
em.persist(memberA);
em.persist(memberB);
//여기까지 INSERT SQL을 DB에 보내지 않는다
//커밋하는 순간 데이터베이스에 INSERT SQL을 보낸다
tx.commit(); // [트랜잭션] 커밋
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction(); // 트랜잭션을 얻는다
tx.begin(); // 트랜잭션 시작 -> DB 트랜잭션 시작
Member memberA = em.find(Member.class, 150L); //1차 캐시에 저장
memberA.setName("ZZZZZ"); //스냅샷 떠 둔 부분이 1차 캐시에 존재할거임
//em.persist(member) 이런 코드가 있어야 하지 않을까??
tx.commit();
+------------------------------------+
|@id Entity 스냅샷 |
|"member1" member1 memberA 스냅샷 |
|"member2" member2 memberB 스냅샷 |
| |
+------------------------------------+
- 값을 읽어온 최초의 상태(DB, 값을 넣은 상황)를 스냅샷으로 떠둔다
- JPA는 Transaction Commit이 발생하면 **flush()**를 호출한다.
- 해당 Entity와 최초로 생성된 Entity의 스냅샷을 비교 한다.
- MemberA가 변경된 것을 JPA가 감지 후 UPDATE SQL 생성 -> 쓰기 지연 SQL 저장소에 저장
- Commit시 해당 쿼리가 실행이 된다.
- JPA는 DB Transaction commit시 내부적으로 flush()가 호출 된다.
- 후에 들어온 Entitiy와 **스냅샷(Entity 최초 스냅샷)**을 비교한다.
- 변경 사항이 있으면 쓰기 지연 SQL 저장소에 쿼리를 저장해둔다.
- DB 반영
영속성 컨텍스트의 변경 내용을 데이터베이스에 반영
- 변경 감지
- 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송(등록, 수정, 삭제 쿼리)
- 플러시는 영속성 컨텍스트를 비우지 않음
- 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화
- 트랜잭션이라는 작업 단위가 중요 -> 커밋 직전에만 동기화 하면됨
- em.flush() - 수동 호출 (즉시 쿼리가 날라간다)
- 트랜잭션 커밋 - 플러시 자동 호출
- JPQL 쿼리 실행 - 플러시 자동 호출
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);
//실제 쿼리는 안날라간다 여기서
//중간에 JPQL 실행
//flush가 안 날라가면 데이터 조회 시 예외가 발생할 것이다
query = em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();
- JPQL이 있을 시에는 **무조건 flush를 날린다
- 위 같은 상황에서는 flush가 실행 되어야 하는 상황이다
em.setFlushMode(FlushModeType.COMMIT)
- FlushModeType.AUTO: 커밋이나 쿼리를 실핼할 때 플러시 (가급적 AUTO로 사용)
- FlushModeTYpe.COMMIT: 커밋할 때만 플러시
- 영속 -> 준영속
-
1차 캐시에 올라간 상태 -> 영속 상태
- 조회를 했는데 1차 캐시에 없는 경우 해당 Entity를 1차 캐시에 적재
- 영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)
- 영속성 컨텍스트가 제공하는 기능을 사용 못함
-
em.detach(entity)
- 특정 엔티티만 준영속 상태로 전환
-
em.clear()
- 영속성 컨텍스트를 완전히 초기화
-
em.close()
- 영속성 컨텍스트를 종료