ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [JPA] Dirty Checking(변경 감지)
    프로그래밍/JPA 2021. 12. 24. 03:36
    반응형

    이 글은 혼자 학습한 내용을 바탕으로 작성되었습니다.

    틀리거나 잘못된 정보가 있을 수 있습니다.

    댓글로 알려주시면 수정하도록 하겠습니다.


    1. JPA Dirty Checking

    JPA에는 수정과 관련된 메소드가 존재하지 않습니다. 그럼 JPA를 이용해서는 데이터를 수정할 수 없는 것일까요?

     

    당연히 JPA도 DB 데이터를 수정할 수 있습니다. 다만 수정 메소드가 존재하지 않을 뿐입니다.

     

    JPA를 사용하여 데이터를 수정하려면 Entity를 조회하여 조회된 Entity 데이터를 변경만 하면 데이터 베이스에 자동으로 반영이 되도록 하는 기능을 바로 Dirty Checking이라고 합니다.

     

    2. Dirty Checking 동작 방법

    Entity 객체의 데이터만 변경하면 어떻게 DB에 수정된 데이터가 반영되는지 설명드리도록 하겠습니다.

     

    JPA는 영속성 컨텍스트를 생성하여 DB와 유사하게 이용하여 Entity의 Life Cycle을 관리합니다.

     

    그럼 JPA는 영속성 컨텍스트에 Entity를 보관할 때 최초의 상태를 저장하고 있습니다. 이것을 스냅샷이라고 하며 영속성 컨텍스트가 Flush되는 시점에 스냅샷과 Entity의 현재 상태를 비교하여 달라진 Entity를 찾습니다.

     

    이후 변경된 필드들을 이용하여 쓰기지연 SQL 저장소에 Update 쿼리를 생성하여 쌓아 둡니다.

     

    모든 작업이 끝나고 트랜잭션을 커밋을 하면 이때 쓰기지연SQL 저장소에 있는 쿼리들을 DB에 전달하여 Update를 진행 합니다.

     

    ※Dirty Checking의 경우 영속성 컨텍스트의 스냅샷을 이용하여 Flush 시점에 Update 쿼리들이 생성되어 진다고 하였습니다. 그러므로 영속성 컨텍스트가 관리하는 영속 상태의 엔티티만 Dirty Checking이 가능 합니다. 만약 비영속이거나 준영속 상태인 Entity의 경우 Dirty Checking이 되지않아 Entity를 변경하여도 DB에는 적용되지 않습니다.

     

    3. Dirty Checking Test

    코드를 이용하여 Dirty Checking Test를 진행 해보도록 하겠습니다.

    @Getter
    @Entity
    @Builder
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    @AllArgsConstructor(access = AccessLevel.PROTECTED)
    public class Member {
        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE)
        private long id;
    
        private String name;
    
        private String nickName;
    
        public void changeNickName(String nickName){
            this.nickName = nickName;
        }
    }

    Entity Class

     

    @Service
    @RequiredArgsConstructor
    public class MemberService {
        private final MemberRepository memberRepository;
    
        @Transactional
        public void changeNickName(long id, String nickName){
            Member member = memberRepository.getById(id);
            member.changeNickName(nickName);
        }
    }

    Service Class

     

    @SpringBootTest
    public class JPADirtyCheckTest {
    
        @Autowired
        MemberService memberService;
    
        @Autowired
        MemberRepository memberRepository;
    
        @Test
        @Transactional
        public void Spring_Data_JPA_Dirty_Checking(){
            Member member = Member.builder()
                    .name("tom")
                    .nickName("tomato")
                    .build();
    
            member = memberRepository.save(member);
    
            memberService.changeNickName(member.getId(), "tomato_ju");
    
            Member findMember = memberRepository.getById(member.getId());
    
            assertThat(findMember.getNickName(), is("tomato_ju"));
        }
    }

    Test Class

     

    먼저 Member Entity를 Builder를 이용하여 생성한 뒤 Repository의 Save 메소드를 통해 Member를 DB에 저장 합니다.

     

    이후 changeNickName 메소드에 변경하고자 하는 Entity의 Id와 NickName을 전달하여 해당 Entity를 Id로 조회한 뒤 Entity의 nickName 값을 "tomato_ju"로 변경합니다.

     

    이후 Transaction이 종료 되므로 Commit과 동시에 쓰기지연 SQL 저장소에 있던 Update 쿼리를 DB에 실행하여 NickName을 변경 합니다.

     

    다시 Id 값으로 조회를 하면 NickName 값은 "tomato"에서 "tomato_ju"로 변경된 것을 확인할 수 있습니다.

     

    실제 실행된 SQL 쿼리를 확인 해 보겠습니다.

    시퀀스에서 다음 값을 들고와 신규 Member를 Insert 합니다.

     

    changeNickName 메소드에서 Id로 조회 후 조회된 Entity의 nickName 필드 값을 "tomato_ju"로 변경 후 메소드 종료(트랜잭션 종료) 되어 쓰기지연 SQL 저장소에 있는 Update 쿼리를 실행 합니다.

     

    Test의 마지막인 저장된 Id를 통해 nickName이 변경된 Entity를 다시 조회 합니다.

     

    이후 assert를 통해 결과 값을 비교 하여 정상적으로 Test가 통과되는 것을 확인할 수 있습니다.

     

    Test 진행으로 확인할 수 있는것 처럼 JPA의 Dirty Checking덕분에 데이터의 변경이 필요한 경우 변경이 필요한 Entity의 필드값만 변경하면 자동으로 DB에 반영되는 것을 확인할 수 있습니다.

     

    4. 변경된 필드만 변경하기

    위 Test의 Update 쿼리를 보면 이상한 부분이 있습니다. 분명 변경한 필드은 NickName 하나인대 Update 쿼리는 Name, NickName 2개의 값을 모두 Update 하는 쿼리를 만들어 실행 하였습니다.

     

    JPA에서 변경된 필드만 Update하지 않고 모든 필드를 변경하는 쿼리를 생성하는 이유는 모든 필드를 Update 쿼리로 만들면 수정 쿼리가 항상 동일하게 만들어지므로 미리 Update 쿼리를 생성하여 재사용이 가능 합니다.

     

    또한 데이터베이스는 동일한 쿼리를 전달하면 한번 파싱된 쿼리를 재사용할 수 있습니다.

     

    이러한 이점 때문에 JPA는 모든 필드를 Update 쿼리로 만들어 실행 합니다.

     

    하지만 변경된 필드만 Update 쿼리로 적용하고 싶다면 DynamicUpdate 어노테이션을 사용하면 됩니다.

     

    @Getter
    @Entity
    @Builder
    @NoArgsConstructor(access = AccessLevel.PROTECTED)
    @AllArgsConstructor(access = AccessLevel.PROTECTED)
    @DynamicUpdate
    public class DynamicUpdateMember {
        @Id
        @GeneratedValue(strategy = GenerationType.SEQUENCE)
        private long id;
    
        private String name;
    
        private String nickName;
    
        public void changeNickName(String nickName){
            this.nickName = nickName;
        }
    }

    DynamicUpdateMember Class

     

    DynamicUpdate 어노테이션을 적용한 Entity의 Update 쿼리를 보면 변경한 nickName 필드만 Update 쿼리로 생성된 것을 볼 수 있습니다.

     

    대략 30개 이상이 되면 기본 방법인 정적 수정 쿼리보다 DynamicUpdate 어노테이션을 사용한 동적 수정 쿼리가 더 빠르다고 합니다.
    하지만 한 테이블에 컬럼이30개 이상이 된다는 것은 테이블 설계상 책임이 적절하게 분리되지 않았을 가능성이 클 수 있습니다.

    출처 - 자바 ORM 표준 JPA 프로그래밍

     

    사용된 예제 코드는 Git에 있습니다.

    반응형

    '프로그래밍 > JPA' 카테고리의 다른 글

    [JPA] 즉시로딩과 지연로딩 Eager And Lazy  (0) 2022.01.13
    [JPA] Attribute Converter  (0) 2022.01.12
    [JPA] 예외 처리  (0) 2021.11.05
    [JPA] 연관관계의 주인  (0) 2021.10.21
    [JPA] 연관관계  (0) 2021.10.20

    댓글

Designed by Tistory.