Spring/Spring Data JPA

영속성 & 반영속성 컨텍스트 (엔티티 영구 저장 환경)

밍구밍구밍 2024. 5. 5. 11:50

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. 영속 컨테스트 매커니즘

※ 출처 : JPA ORM 표준

step1. 위의 그림과 같이 memberA 와 memberB 를 persist() 하였다. 하지만 persist() 시점에는 두 개의 데이터가 1차 캐시에 저장함과 동시에 INSERT SQL 을 생성하는 것을 볼 수 있다. 

step2. 1차 캐시 저장과 INSERT SQL 생성을 하고 나면 대기 하였다가 commit() 메서드가 호출되는 시점에 최종적으로 DB 에 저장 되는 원리이다.

 

※ 첨부 : JPA ORM 표준

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() 호출 시 sql 쿼리가 동작하면서 DB 에 반영된다. 그후 "====" 가 출력

위의 코드와 출력결과에서 알 수 있듯이 flush() 메서드를 사용하면 

 

의문점? 

- 강의내용에서 flush() 를 사용하면 영속성컨테스트에 있는 쓰기지연저장소에 있는 UPDATE 쿼리들(등록, 수정, 삭제)의 내용이 데이터베이스에 전달 되는 과정이라고 설명 하고 있는데 그렇다면 실제로 DB 에 저장 되는 게 아닌건지 왜냐하면 commit() 메서드를 제거하고 flush() 메서드만 남았을 때 DB 에 데이터가 전달 되지 않았다.. 아니면 flush() 는 데이터 베이스에 update 쿼리들이 전달되기 위한 중간 과정인건지..?

 

>> JPA에서 flush() 메서드는 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업을 수행합니다. 이 메서드를 호출하면 JPA는 현재 영속성 컨텍스트에 있는 모든 변경 내용을 데이터베이스에 즉시 반영합니다. 그러나 이 메서드를 호출한다고 해서 트랜잭션을 커밋하지는 않습니다. 트랜잭션을 커밋하려면 commit() 메서드를 호출해야 합니다. flush()를 호출하면 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하므로, 데이터베이스에서 다른 세션 또는 트랜잭션에서 변경된 내용을 볼 수 있게 됩니다.

 

※ 정리 : 플러시 flush()

- 영속성 컨텍스트를 비우는 것이 아니다

- 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화 = 커밋(저장)이 아님! (저장은 commit() 이 수행함)

- 트랜잭션이라는 작업 단위가 중요 -> commit() 직전에만 동기화 하면 된다.

 

4. 준영속 상태

초기 DB 에 저장된 DATA

아래 코드와 같이 데이터를 수정해보자

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() : 영속성 컨테스트를 종료