영속성 & 반영속성 컨텍스트 (엔티티 영구 저장 환경)
1. 영속 컨텍스트 코드
try {
// 비영속 상태
Member member = new Member();
member.setId(101L);
member.setName("HelloJPA");
// 영속 상태 (실제 DB에 저장되는 것이 아니다.)
System.out.println("=== before ===");
em.persist(member); // 1차 캐시에 저장 되는 것
System.out.println("=== after ===");
Member findmember = em.find(Member.class, 101L);
System.out.println("findmemberId = " + findmember.getId());
System.out.println("findmemberName = " + findmember.getName());
tx.commit(); // 실제 데이터가 DB에 저장 되는 시점
persist() 가 실행되는 시점에는 DB 에 저장되기 전인 1차 캐시 에 저장 되는 것이다. (영속 상태)
최종적으로 DB에 저장되는 시점은 commit() 메서드가 실행 되었을 때이다.
** 실제 코드의 결과를 보면 insert SQL이 pesist() 메서드 이후에 실행 되는 것을 볼 수 있다
( 이말은 즉, 위의 코드에서 데이터를 조회하는 em.find() 메서드에서 출력된 101 과 HelloJPA 는 실제 DB 에 저장 된 상태의 조회된 결과가 아닌 1차 캐시라는 임시 저장소(?) 에 저장되있는 값을 조회 한 것이다.)
2. 영속 컨테스트 매커니즘
step1. 위의 그림과 같이 memberA 와 memberB 를 persist() 하였다. 하지만 persist() 시점에는 두 개의 데이터가 1차 캐시에 저장함과 동시에 INSERT SQL 을 생성하는 것을 볼 수 있다.
step2. 1차 캐시 저장과 INSERT SQL 생성을 하고 나면 대기 하였다가 commit() 메서드가 호출되는 시점에 최종적으로 DB 에 저장 되는 원리이다.
step 3. 위의 그림과 같이 commit() 시점에 1차캐시와 쓰기 지연 SQL 저장소를 포함하고 있는 영속 컨텍스트(entityManager) 에서 DB로 데이터가 저장되는 것을 확인 할 수 있다.
3. flush() 란?
- 변경 감지
- 수정된 엔티티 쓰기 지연 SQL 저장소에 등록
- 쓰기 지연 SQL 저장소의 쿼리를 데이터베이스에 전송 (등록, 수정, 삭제 쿼리)
* 영속성 컨텍스트를 플러시 하는 방법
1) em.flush() - 직접 호출
2) 트랜잭션 커밋 - 플러시 자동 호출
3) JPQL 쿼리 실행 - 플러시 자동 호출
em.flush()
try {
// 영속
Member member = new Member(300L, "member300");
em.persist(member);
em.flush(); // flush 강제 호출 (SQL 쿼리) == DB 데이터 강제 반영
System.out.println("=======================");
tx.commit(); // 실제 데이터가 DB에 저장 되는 시점
}
위의 코드와 출력결과에서 알 수 있듯이 flush() 메서드를 사용하면
의문점?
- 강의내용에서 flush() 를 사용하면 영속성컨테스트에 있는 쓰기지연저장소에 있는 UPDATE 쿼리들(등록, 수정, 삭제)의 내용이 데이터베이스에 전달 되는 과정이라고 설명 하고 있는데 그렇다면 실제로 DB 에 저장 되는 게 아닌건지 왜냐하면 commit() 메서드를 제거하고 flush() 메서드만 남았을 때 DB 에 데이터가 전달 되지 않았다.. 아니면 flush() 는 데이터 베이스에 update 쿼리들이 전달되기 위한 중간 과정인건지..?
>> JPA에서 flush() 메서드는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업을 수행합니다. 이 메서드를 호출하면 JPA는 현재 영속성 컨텍스트에 있는 모든 변경 내용을 데이터베이스에 즉시 반영합니다. 그러나 이 메서드를 호출한다고 해서 트랜잭션을 커밋하지는 않습니다. 트랜잭션을 커밋하려면 commit() 메서드를 호출해야 합니다. flush()를 호출하면 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하므로, 데이터베이스에서 다른 세션 또는 트랜잭션에서 변경된 내용을 볼 수 있게 됩니다.
※ 정리 : 플러시 flush()
- 영속성 컨텍스트를 비우는 것이 아니다
- 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화 = 커밋(저장)이 아님! (저장은 commit() 이 수행함)
- 트랜잭션이라는 작업 단위가 중요 -> commit() 직전에만 동기화 하면 된다.
4. 준영속 상태
아래 코드와 같이 데이터를 수정해보자
1. em.find(Member.class, 1L) : ID 1번의 데이터를 찾고 member 로 객체를 생성한다.
2. member.setName("MemberAA") : member.SetName() 으로 기존 MemberA 데이터를 MemberAA 로 수정하려고 한다.
3. em.detach(member) 문을 사용하여 영속상태를 해제 하였다.
try {
// 영속
Member member = em.find(Member.class, 1L);
member.setName("MemberAA");
em.detach(member); // 영속상태 해제
System.out.println("=======================");
tx.commit(); // 실제 데이터가 DB에 저장 되는 시점
}
출력 code 및 데이터 베이스
SELECT 쿼리는 출력이 되었다. 그러나..
데이터 베이스의 ID 1번의 값은 그대로 이다.
* 준영속 상태로 만드는 법 3가지
1) em.detach() : 특정 엔티티만 준영속 상태로 전환
2) em.clear() : 영속성 컨텍스트를 완전히 초기화
3) em.close() : 영속성 컨테스트를 종료