728x90
1. 영속성 컨텍스트
1.1. jpa에서 가장 중요한 2가지
- 객체와 관계형 데이터베이스 매핑하기 : 실제 설계적인 측면 (정적 측면), 객체와 db를 어떻게 연결하는지
- 영속성 컨텍스트(entity Manager의 1차 cache라고 봐도 무방 - spring에선 약간 다르다.) : JPA 내부 동작 방식 및 메커니즘
1.2. 엔티티 매니저 팩토리와 엔티티 매니저
1.3. 영속성 컨텍스트
- 엔티티를 영구 저장하는 환경
- 영속성 컨텍스트는 논리적인 개념으로 눈에 보이지 않는다.
- 엔티티 매니저를 통해서 영속석 컨텍스트에 접근 가능하다.
- 영속성 context 내부에 1차 캐시, 1차 캐시 내부에 스냅샷, 쓰기 지연 SQL 저장소 존재
EntityManager.persist(entity); //db에 저장하는 것이 아니라 영속성 컨텍스트라는 곳에 저장
1.4. 영속성 컨텍스트의 이점
- 1차 캐시 - 개별적이다. 10명의 고객이 동시 접속 시, 10개의 별도 캐시를 가진다.
- 동일성 보장 - java의 컬랙션과 같다고 보면 된다.
- 트랜잭션을 지원하는 쓰기 지연 - 쓰기 지연 SQL 저장소
- 변경 감지 - dirty check : update에 유용하게 사용
- 지연 로딩 - lazy loading : 실무에서 중요하게 여기는 기능 select query를 나중에 날림
2. 엔티티 생명주기
- 비영속 (new / transient) : 영속성 컨텍스트와 전혀 관계 없는 새로운 상태
- 영속 (managed) : 영속성 컨텍스트에 관리되는 상태
- 준영속 (detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
- 삭제 (removed) : 삭제된 상태
3. 1차 캐시 & 조회
3.1. 1차 캐시에서 조회
3.2. 데이터베이스에서 조회 (1차 캐시에서 못찾았을 경우)
- entityMnager는 tx 단위로 만들고 tx가 끝났을 때 종료 된다.
= 고객 biz가 끝나면 영속성 컨텍스트를 지운다는 의미 == 1차 캐시도 날라감 - 영속성 context는 여러명의 고객이 사용하는 cache가 아니다
-> 여러명의 고객이 공유하는 cache는 2차 cache라고 한다. - 1차 캐시는 db의 1개 tx에서만 효과가 있기 때문에 실질적으로 성능상 이점을 얻지 못한다.
- 정말 biz 로직이 복잡할 경우 도움이 되고 성능상 이점 보다는 컨셉적인 이득이 크다. (객체지향적 코드 작성 가능하게 해줌)
4. 영속 엔티티의 동일성 보장
- 1차 캐시로 반복 가능한 읽기 등급의 트랜잭션 격리 수준을 db가 아니 application 차원에서 제공
Member a = em.find(Member.class, "member1);
Member b = em.find(Member.class, "member1);
Suytem.out.printlb (a == b); // true
5. 트렌잭션을 지원하는 쓰기 지연 - 등록, 변경, 삭제
- 현재는 등록을 기준으로 설명
em.persist(memberA);
em.persist(memberB);
// 여기까지 insert SQL을 db에 보내지 않는다.
transaction.commit();
// commit()하는 순간 insert sql을 보낸다. - 예외 존재. Identity 설정의 경우
6. 엔티티 수정 - 변경 감지(dirty check)
- 참고
- persist(): 새로운 엔티티를 영속성 컨텍스트에 저장(1차 캐시에 저장)하고, 쓰기 지연 SQL 저장소에 INSERT SQL을 생성하여 추가. 스냅샷 비교 없이 영속 상태의 엔티티를 DB에 삽입
- delete(): 엔티티를 영속성 컨텍스트에서 삭제(1차 캐시에서 삭제)하고, 쓰기 지연 SQL 저장소에 DELETE SQL을 생성하여 추가합니다. 스냅샷 비교 없이 엔티티를 DB에서 삭제
- update(): JPA에서는 엔티티의 변경 감지(Dirty Checking)를 사용하여 수정된 엔티티를 자동으로 감지하고, 쓰기 지연 SQL 저장소에 UPDATE SQL을 추가. 스냅샷 비교를 통해 어떤 필드가 변경되었는지 확인하고 변경된 필드만을 업데이트
- persist(): 새로운 엔티티를 영속성 컨텍스트에 저장(1차 캐시에 저장)하고, 쓰기 지연 SQL 저장소에 INSERT SQL을 생성하여 추가. 스냅샷 비교 없이 영속 상태의 엔티티를 DB에 삽입
7. 3~6 까지 학습 code
package hellojpa;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityTransaction;
import javax.persistence.Persistence;
public class JpaChap3 {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager(); // database의 connection 하나 받았다고 생각하셈
EntityTransaction tx = em.getTransaction(); // jpa는 tx가 필수
tx.begin(); // tx 시작
try {
/*
// 비영속
Member member = new Member();
member.setId(100L);
member.setName("HelloJPA");
// 영속 - 이 때 db에 저장이 되는 것이 아니다.
System.out.println("=== BEFORE ===");
em.persist(member); // 만약 db에 저장되지 않기 때문에 before after 사이에 query가 생성되어 db에 날라가지 않는다.
System.out.println("=== AFTER ===");
tx.commit(); // commit하는 시점에 영속성 context에 존재하는 값이 db에 날라간다.
*/
/*
// 비영속
Member member = new Member();
member.setId(101L);
member.setName("HelloJPA");
// 영속 - 이 때 db에 저장이 되는 것이 아니다.
em.persist(member); // 만약 db에 저장되지 않기 때문에 before after 사이에 query가 생성되어 db에 날라가지 않는다.
// 1차 캐시 - 영속성 컨텍스트에서 값을 찾는 것이기 때문에 select query가 출력되지 않는다.
Member findMember = em.find(Member.class, 101L);
System.out.println("findMember.getId() = " + findMember.getId());
System.out.println("findMember.getName() = " + findMember.getName());
tx.commit(); // commit하는 시점에 영속성 context에 존재하는 값이 db에 날라간다.
*/
/*
// 101L이 저장되고 나서 tx가 종료 된 상태 이후, 다시 조회 하는 과정
* tx 종료시, 영속성 context 또한 종료 되기 때문에 entity manager 종료, 1차 cache 종료 된다.
* 1st 조회는 1차 cache에 아무것도 없기 때문에 db를 찾아서 select 쿼리를 날리게 되고
* 2nd 조회시, 1차 cache를 통해서 조회 함. select 쿼리 안날림
// 1차 cache - 처음에 없는 경우
Member findMember1 = em.find(Member.class, 101L); // select query 날림
Member findMember2 = em.find(Member.class, 101L); // 1차 캐시 이용, select query 안날림
// 영속 엔티티의 동일성 보장
System.out.println("result = " + (findMember1 == findMember2));
tx.commit(); // commit하는 시점에 영속성 context에 존재하는 값이 db에 날라간다.
*/
/*
엔티티 등록 - tx를 지원하는 쓰기 지연
Member member1 = new Member(150L, "A");
Member member2 = new Member(160L, "A");
System.out.println("=== 1st persist start===");
em.persist(member1);
System.out.println("=== 1st persist finish ===");
System.out.println("=== 2nd persist start===");
em.persist(member2);
System.out.println("=== 2nd persist finish ===");
tx.commit(); // 커밋 시점에서 insert 쿼리를 한번에 db로 보내서 성능에 도움
*/
/*
// 엔티티 수정 변경 감지
Member member = em.find(Member.class, 150L); // 초기 값 찾음
member.setName("changeName"); // 값 변경
System.out.println(" =========== ");
tx.commit(); // commit 시점에 초기값과 값 변경 값이 다르면 update 날리고 쓰기지연SQL에서 해당 sql 제거
*/
// 엔티티 삭제 - 엔티티 수정과 동일 메커니즘
Member member = em.find(Member.class, 150L);
em.remove(member); // 영속성 context에서 제거
System.out.println(" =========== ");
tx.commit(); // commit 시점에 스냅샷 확인 없이 delete 쿼리를 날려서 실제 db에서 삭제
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
8. 플러시
- insert, update, delete SQL을 db에 날리는 method()
8.1. 플러시 메커니즘
- 변경
- tx가 commit()되는 시점에 flush() 자동 발생
- 변경 감지( )
- 수정된 엔티티 쓰기지연SQL저장소에 등록
- 쓰기지연SQL저장소의 쿼리를 데이터베이스에 전송
- 등록, 제거
- tx가 commit()되는 시점에 flush() 자동 발생
- 이미 쓰기지연SQL저장소에 등록되어있는 쿼리를 데이터베이스에 전송
8.2. 영속성 컨텍스트를 플러시 하는 방법
- em.flush() - 직접 호출
- 거의 사용할 일이 없으나 test 때 사용되니깐 알아둬야함
- 트랜잭션 커밋 - 플러시 자동 호출
- JPQL 쿼리 실행 - 플러시 자동 호출
- JPQL은 그냥 sql로 번역이 되어 수행이 되는 작업이기 때문에 영속성 컨텍스트에서 값을 찾지 않고 바로 db로 부터 값을 찾게 된다.
- 따라서, JPQL이 쿼리를 조회할 수 있도록 Flush()를 발생시켜 db로 부터 값을 조회할 수 있도록 해준다.
8.3. 플러시 모드 옵션
- 내가 건들일 일이 극히 없음
- 그리고 건들이지 말고 Auto 사용하는 것을 권장
em.setFlushMode(FlushModeType.AUTO) // 기본값. 커밋이나 쿼리(jpql)를 실행할 때 플러시
em.setFlushMode(FlushModeType.COMMIT) // 커밋할 때문 플러시
8.4. 플러시 정리
- 영속성 컨텍스트를 비우지 않는다.
- 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화
= 쓰기 지연 sql 저장소 Query를 db로 넘긴다. - 트랜잭션 작업 단위가 중요하다. -> commit 직전에만 동기화 되면 된다.
8.5. 플러시 코드
- 쓰기 지연 SQL 저장소에 있는 SQL이 DB로 전송되면 해당 SQL은 쓰기 지연 SQL 저장소에서 제거
- 즉, flush 작업을 수행하면 쓰기 지연 SQL 저장소의 SQL은 DB로 전송되어 실행되며, 이후 해당 SQL은 저장소에서 제거된다.
- flush()를 명시적으로 호출한 경우와 commit()에 의한 자동 flush()가 중복 호출되는 경우
- 중복 호출은 일반적으로 문제가 되지 않는다. 이 두 번의 flush() 호출은 동일한 내용을 DB로 보내질 때, JPA는 중복 호출에 대한 최적화를 수행하여 불필요한 SQL 전송을 방지
package hellojpa;
import javax.persistence.*;
public class JpaChap3_flush {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager(); // database의 connection 하나 받았다고 생각하셈
EntityTransaction tx = em.getTransaction(); // jpa는 tx가 필수
tx.begin(); // tx 시작
try {
/*
// 엔티티 삭제 - 엔티티 수정과 동일 메커니즘
Member member = new Member(200L, "member200");
em.persist(member);
// flush()
* tx가 commit 되기 전에 미리 db에 반영을 하고싶거나 query를 미리 보고 싶은 경우 강제로 호출
* flush를 했다고 1차 캐시가 지워지는 것이 아니라 1차 캐시의 스냅샷 확인 후,
* 달라진 것이 존재 시 쓰기 지연 sql 저장소의 쿼리를 db로 보내주는 역할 까지만 수행
em.flush();
tx.commit(); // commit 시점에도 flush()가 자동 호출 되므로 flush() 중복 호출, JPA가 중복 호출에 대한 최적화 수행함 걱정 ㄴㄴ
*/
/*
// 플러시 모드 옵션
em.setFlushMode(FlushModeType.COMMIT); // jpql code 위에만 있으면 된다.
Member member = new Member(202L, "member202");
em.persist(member);
TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);
List<Member> resultList = query.getResultList();
System.out.println("resultList = " + resultList); // resultList = []
*/
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}
9. 준영속 상태
- 영속 상태 였던 것을 영속성 컨텍스트에서 빠지는 상태로 만드는 것
- 영속 상태의 엔티티가 영속성 컨텍스트에서 분리 (detached)
- 영속성 컨텍스트가 제공하는 기능을 사용 못함
9.1. 준영속 상태로 만드는 방법
- em.detach(entity) : 특정 엔티티만 준영속 상태로 전환
- em.clear() : 영속성 컨텍스트를 완전히 초기화, test시, 아주 유용
- em.close() : 영속성 컨텍스트를 종료
9.2. 준영속 상태 code
package hellojpa;
import javax.persistence.*;
public class JpaChap3_detach {
public static void main(String[] args) {
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");
EntityManager em = emf.createEntityManager(); // database의 connection 하나 받았다고 생각하셈
EntityTransaction tx = em.getTransaction(); // jpa는 tx가 필수
tx.begin(); // tx 시작
try {
/*
// sample Data
Member member = new Member(1L, "zzzz");
Member member1 = new Member(2L, "zzzz");
em.persist(member);
em.persist(member1);
tx.commit();
*/
/*
// em.detach() : 개별 영속성 컨텍스트 전체 준영속 상태로 변경
Member member = em.find(Member.class, 1L); // 영속 상태로 올라감
Member controlMember =em.find(Member.class, 2L); // detach에 영향 안받는걸 보여주기 위한 용도
// 영속 컨텍스트를 관리하기 싫어 할 때 detach()로 영속성 context에서 원하는 key-value를 날려버림
em.detach(member); // member만 1차 cache에서 준영속 상태로 날라감
member.setName("aaaa"); // dirty checking 후 변경 update query를 commit 시점에 날릴 것임.
controlMember.setName("aaaa");
System.out.println("=== === ===");
tx.commit();
*/
/*
// em.clear() : 전체 영속성 컨텍스트 전체 준영속 상태로 변경
Member member = em.find(Member.class, 1L); // 영속 상태로 올라감
Member controlMember = em.find(Member.class, 2L); // detach에 영향 안받는걸 보여주기 위한 용도
member.setName("bbbb"); // dirty checking 후 변경 update query를 commit 시점에 날릴 것임.
controlMember.setName("bbbb");
em.clear(); // 영속 컨텍스트 통으로 다 날림
System.out.println("=== === ===");
Member member1 = em.find(Member.class, 1L); // 영속성 context에 없어서 다시 쿼리 날림
Member controlMember1 = em.find(Member.class, 2L); // 동일
System.out.println("=== === ===");
tx.commit();
*/
/*
// em.clear() : 영속성 컨텍스트를 아예 날리는 거여서 이후, 관리 자체를 못함
Member member = em.find(Member.class, 1L); // 영속 상태로 올라감
Member controlMember = em.find(Member.class, 2L); // detach에 영향 안받는걸 보여주기 위한 용도
member.setName("bbbb"); // dirty checking 후 변경 update query를 commit 시점에 날릴 것임.
controlMember.setName("bbbb");
em.close(); // 영속 컨텍스트 통으로 다 날림
System.out.println("=== em 외적인 작업은 수행 됨 ===");
int i = 1;
int i2 = i + 3;
System.out.println("i2 = " + i2);
System.out.println("=== em 관련 작업 수행시, 이후 모른 line 작업 중단 ===");
Member member1 = em.find(Member.class, 1L); // 수행 안됨
Member controlMember1 = em.find(Member.class, 2L); // 수행 안됨
System.out.println("작업 중단 됨. 콘솔에 안 찍힘");
tx.commit(); // 애매한 부분...
*/
} catch (Exception e) {
tx.rollback();
} finally {
em.close();
}
emf.close();
}
}