[항해 취업코스] 개발자 취준 기록 1일차 - 더티체킹

Godtaek·2024년 3월 5일
0

Spring

목록 보기
5/9

1. 더티체킹이란?

영속 상태의 데이터 변경을 감지하여 JPA에서 자동으로 변경된 객체를 DB에 반영해주는 기술

영속 상태의 데이터가 스냅샷과 다르다면 Update를 자동으로 해준다는 의미. 즉, JpaRepository.save()나, UPDATE 쿼리를 작성할 필요 없이, 자동으로 업데이트한다.

영속 상태의 데이터가 중요하다. 비영속 상태, 준영속 상태의 데이터는 더티 체킹하지 않는다.

2. 코드로 이해해보자

https://github.com/do0134/java_spring_study

Spring-data-Jpa를 활용하여 간단한 Entity와 비즈니스 로직을 짰다.
createBook에서 생성하고, updateBook에서 update할 예정이다.
save나 update 쿼리 하나 없는데, book의 내용이 바뀔지 확인하는 것이 포인트
updateBook API에서는 updateBook을 하고 나서 controller에서 findById를 통해 확인하고 데이터를 보낼 예정이다.

코드

// BookService

@Transactional
public Book createBook(String name, String content) {
    Book book = new Book();
    book.setName(name);
    book.setContent(content);
    return bookRepository.save(book);
}

@Transactional
public Book updateBook(Long id, String name, String content) {
    Book book = bookRepository.findById(id).orElseThrow(RuntimeException::new);
    book.setName(name);
    book.setContent(content);
    return book;
}
// BookController

@PostMapping("/create")
public ResponseEntity<Book> createBook(@RequestBody BookRequestDto bookRequestDto) {
    return new ResponseEntity<>(bookService.createBook(
    bookRequestDto.getName(),bookRequestDto.getContent()), 
    HttpStatus.OK);
}

@PutMapping("/{bookId}")
public ResponseEntity<Book> updateBook(@RequestBody BookRequestDto bookRequestDto, @PathVariable("bookId") Long bookId) {
    bookService.updateBook(bookId,bookRequestDto.getName(),bookRequestDto.getContent());
    return new ResponseEntity<>(
    bookRepository.findById(bookId).orElseThrow(RuntimeException::new),
    HttpStatus.OK);
}

결과

  1. 책 생성
    책생성

  2. 책 수정

  3. 쿼리

    어? 왜 controller select 쿼리는 안 나갔나요? 그러게요? @Transactional 밖인데 경계라서 1차 캐시 효과를 본건가

그런데?

Update 쿼리를 한 번 확인해보자

update book set content=?,created_at=?,name=?,updated_at=? where id=?

JPA의 update문은 기본적으로 엔티티 자체를 update한다. 만약 Entity 전체가 아닌 특정 컬럼만 변경하고 싶다면 Entity에 @DynamicUpdate를 붙여주자

  1. 요청

  2. DynamicUpdate

  3. 일반

Dynamic Update에서는 content를 지정하지 않으니, set content가 없는 걸 확인할 수 있다.

그런데 DynamicUpdate에 앞서서, 테이블 설계와 구조가 적절한지, 너무 많은 컬럼을 담고 있지 않는지 확인해보는 것이 좋다. << 김영한 선생님의 말씀

동작하지 않는 코드

public void fake(Long id, String name, String content) {
    Book book = bookRepository.findById(id).orElseThrow(RuntimeException::new);
    fakeUpdate(book, name, content);
}

@Transactional
public void fakeUpdate(Book book, String content, String name) {
    if (!name.isEmpty()) {
        book.setName(name);
    }
    if (!content.isEmpty()) {
       book.setContent(content);
    }
}

Book 엔티티 객체 자체를 인자로 받는 로직을 짜봤다.

  1. 쿼리

    당연히 update 쿼리가 안나갔다.
    그런데...

  2. 응답

    응답은 또 정상적이다. 영속성 컨텍스트에서 엔티티만 바꿔 응답한 것은 아닐까? get을 해봤다.

  3. get 요청
    get요청 시 바뀌지 않은 것을 확인할 수 있다.

작동하지 않는 이유는 단순하게 @Transactional안에서 Entity를 찾아오지 않았기 때문이다. 즉, 비영속 상태의 엔티티란 말이다. fake에 @Transactional을 옮겨주는 것으로 해결할 수 있다.

마치며

  1. Dirty checking에 대해 알아봤다. JPA에서 간단하게 사용할 수 있으며, 수정할 때, 데이터 일관성을 보장하는 방법이다.

  2. 영속 상태의 데이터만 작동하기 때문에 Dirty Checking에 문제가 생긴다면, @Transactional이나 영속 상태를 추적해보는 것이 효율적이다.

  3. 만약 준영속 상태의 데이터를 DB에 반영해야 한다면? Merge, 병합이란 방법이 있다. Dirty checking에 비해서 복잡하고 섬세하게 다뤄야 한다고 한다.

  4. @Transactional에서 Dirty Checking하고 싶지 않다면? @Transactional(readOnly = true)로 해결할 수 있다. readOnly = true가 된다면 영속성 컨텍스트 작동이 MANUAL이 되기 때문


항해 개발자 취업 리부트 코스를 수강하고 작성한 콘텐츠 입니다.

profile
성장하는 개발자가 되겠습니다

0개의 댓글