LUMI_dev

Entity - #4 영속성 컨텍스트의 기능 ★ 2. 쓰기 지연 저장소 (ActionQueue),em.flush();,Dirty Checking (변경 감지) 본문

스파르타 코딩 클럽 | 자바 심화 과정/Spring Master (입문 주차)

Entity - #4 영속성 컨텍스트의 기능 ★ 2. 쓰기 지연 저장소 (ActionQueue),em.flush();,Dirty Checking (변경 감지)

luminous_dev 2025. 2. 3. 22:58

이것을 공부하기 위해 트랜잭션 공부함 

 

JPA는 트랜잭션처럼 SQL을 모아서 한번에 DB에 반영함

 

이를 구현하기 위해,

쓰기 지연 저장소로 SQL을 모아두고 있다가 트랜잭션 Commit 후 한번에 DB에 반영  

 


실습)  쓰기 지연 저장소

@Test
@DisplayName("쓰기 지연 저장소 확인")
void test6() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {
        Memo memo = new Memo();
        memo.setId(2L);
        memo.setUsername("Robbert");
        memo.setContents("쓰기 지연 저장소");
        em.persist(memo);

        Memo memo2 = new Memo();
        memo2.setId(3L);
        memo2.setUsername("Bob");
        memo2.setContents("과연 저장을 잘 하고 있을까?");
        em.persist(memo2);

        System.out.println("트랜잭션 commit 전");
        et.commit();
        System.out.println("트랜잭션 commit 후");

    } catch (Exception ex) {
        ex.printStackTrace();
        et.rollback();
    } finally {
        em.close();
    }

    emf.close();
}

 

 

em > actionQueue> insertions > executables에 Insert할 memo#2, memo#3 Entity 객체 2개 들어가 있음

 

트랜잭션 commit 후 actionQueue에 있던 insertions 데이터 사라짐

 

기록에서도 커밋 전까지는 SQL 요청 없다가 커밋 후 Insert SQL 2개가 순서대로 요청됨 


트랜잭션 commit 후의 추가적인 동작

em.flush();

 : 영속성 컨텍스트의 변경 내용들을 DB에 반영하는 역할

 : 쓰기 지연소의 SQL들을 DB에 요청하는 역할 수행 

 

실습) flush() 동작 확인

@Test
@DisplayName("flush() 메서드 확인")
void test7() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {
        Memo memo = new Memo();
        memo.setId(4L);
        memo.setUsername("Flush");
        memo.setContents("Flush() 메서드 호출");
        em.persist(memo);

        System.out.println("flush() 전");
        em.flush(); // flush() 직접 호출
        System.out.println("flush() 후\n");
        

        System.out.println("트랜잭션 commit 전");
        et.commit();
        System.out.println("트랜잭션 commit 후");

    } catch (Exception ex) {
        ex.printStackTrace();
        et.rollback();
    } finally {
        em.close();
    }

    emf.close();
}

 

콘솔

 

em.flush() 메서드가 호출되자 바로 DB 쓰기 지연 저장소의 SQL이 요청됨 

트랜잭션이 commit된 후의 SQL 기록은 보이지 않음

→ 이미 쓰기 지연 저장소의 SQL이 요청 되었기 때문에 더 이상 요청할 SQL이 없어서


트랜잭션을 설정하지 않고 .flush() 메서드를 호출하면?

no transaction is in progress라는 메세지와 함께 TransactionRequiredException 오류 발생 

데이터 변경 SQL을 DB에 요청 및 반영하기 위해서는 트랜잭션 필요


변경 감지 (Dirty Checking)

Q. 영속성 컨텍스트에 저장된 Entity가 변경될 때마다 Update SQL이 쓰기 지연 저장소에 저장된다면?

→ 하나의 Update SQL로 처리할 수 있는 상황을 여러번 Update SQL을 요청하게 되기 때문에 비효율적

 

Q. 그러면 JPA는 Update 어떻게 처리?

 

변경하려는 데이터 있으면 먼저 데이터 조회 ▶해당 Entity 객체 데이터 변경하면 자동 Update SQL 생성 DB 반영 

  • 영속성 컨텍스트에 Entity 저장할 때 최초 상태 (LoadedState) 저장 
  • 트랜잭션이 커밋되고 em.flush();가 호출되면 현재 Entity 상태와 저장한 최초 상태 비교 
  • 변경된 내용이 있으면
    • Update SQL 생성
    • 쓰기 지연 저장소에 저장 + 모든 쓰기 지연 저장소의 SQL, DB에 요청
    • DB의 트랜잭션이 커밋되면서 반영 

이러한 과정, Dirty Checking  (변경 감지)

 

실습) Dirty Checking  (변경 감지)

@Test
@DisplayName("변경 감지 확인")
void test8() {
    EntityTransaction et = em.getTransaction();

    et.begin();

    try {
        System.out.println("변경할 데이터를 조회합니다.");
        Memo memo = em.find(Memo.class, 4);
        System.out.println("memo.getId() = " + memo.getId());
        System.out.println("memo.getUsername() = " + memo.getUsername());
        System.out.println("memo.getContents() = " + memo.getContents());

        System.out.println("\n수정을 진행합니다.");
        memo.setUsername("Update");
        memo.setContents("변경 감지 확인");

        System.out.println("트랜잭션 commit 전");
        et.commit();
        System.out.println("트랜잭션 commit 후");

    } catch (Exception ex) {
        ex.printStackTrace();
        et.rollback();
    } finally {
        em.close();
    }

    emf.close();
}

 

 

   
entityInstance  Entity 객체의 현재 상태
entityEntry > loadedState  조회했을 때 즉, 해당 Entity의 최초 상태

 

수정 진행하고 트랜잭션 commit 후 update SQL 요청