최원종의 개발 블로그

V2(PersistentContext) 게시글 수정하기 - Persistence Context와 Dirty Checking 활용 본문

Spring boot 입문

V2(PersistentContext) 게시글 수정하기 - Persistence Context와 Dirty Checking 활용

chl6698 2026. 5. 11. 09:09

BoardRequest.UpdateDTO -코드 (내부 클래스 추가)

package com.tenco.blog.board;

import lombok.Builder;
import lombok.Data;

// 요청 데이터를 담는 DTO 클래스
// 컨트롤러.. 비즈니스 .. 데이터 계층 사이에서 데이터 전송 역할 객체
public class BoardRequest {

    @Data
    @Builder
    public static class SaveDTO {
        private String username;
        private String title;
        private String content;

        // 편의 기능 설계 가능
        // DTO 에서 Entity로 변환해주는 편의 메서드
        public Board toEntity() {
            return Board.builder()
                    .username(username)
                    .title(title)
                    .content(content)
                    .build();
        }
    }

    // 내부 정적 클래스 게시글 수정 DTO 설계
    @Data
    public static class UpdateDTO {
        private String username;
        private String title;
        private String content;
        
        // 게시글 수정시 유효성 검사 편의 메서드
        public void validate() {
            if(username == null ||username.trim().isEmpty()) {
                throw new IllegalArgumentException("작정자 이름은 필수입니다");
            }
            if(title == null || title.trim().isEmpty()) {
                throw new IllegalArgumentException("제목은 필수입니다");
            }
            if(content== null || content.length() < 3) {
                throw new IllegalArgumentException("내용은 3글자 이상 작성해야 합니다.");
            }
        }

    }

}

BoardController 코드 수정

    @PostMapping("/board/{id}/update")
    // 메세지 컨버터란 객체가 동작해서 자동으로 객체를 생성하고 값을 매핑해 준다.
    public String updateProc(@PathVariable(name = "id") Integer id,
                             BoardRequest.UpdateDTO updateDTO) {
        // 1. 유효성 검사
        // username, title, content 유효성 검사
        updateDTO.validate();
        boardPersistRepository.updateById(id, updateDTO);
        return "redirect:/board/" + id;
    }

 


BoardPersistRepository 코드 수정

@Transactional
    public void updateById(Integer id, BoardRequest.UpdateDTO updateDTO) {
        // 수정시 항상 조회 먼저 확인
        Board boardEntity = em.find(Board.class, id);
        // em.find() 호출 수 리턴 받은 board 는 영속 상태가 되어 졌다.

        if(boardEntity == null) {
            throw new IllegalArgumentException("수정할 게시글을 찾을 수 없습니다 : " + id);
        }
        boardEntity.update(updateDTO);
        // 변경 감지(Dirty Checking) 동작 됨.
        // 영속 컨텍스트에 관리 되어지는 객체(엔티티)안에 조회 했을 때 기준으로 1차 캐쉬에 저장되어 짐
        // 추후 1차 캐쉬에 들어가 있는 객체의(엔티티의) 변수값이 변경 되었다면 자동으로 감지 한다.
        // 그냥 새로은 보드 생성
        //em.persist(boardEntity);

        // 앞으로 수정 기능을 만들어 줄 때 더티 체킹 동작으로 사용하자.
    }

Board 코드

package com.tenco.blog.board;

import com.tenco.blog.util.MyDateUtil;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;

import java.sql.Timestamp;

@Data // get,set, toString ..
// @Entity - JPA가 이 클래스를 데이터베이스 테이블과 매핑하는 객체로 인식하게 설정
// 즉, 이 어노테이션이 있어야 JPA가 관리 함
@Entity
@Table(name = "board_tb")
@NoArgsConstructor
@AllArgsConstructor // 전체 멤벼 번수를 넣을 수 있는 생성자.
@Builder
public class Board {

    // @id : 이 필드가 기본키임을 설정 함
    @Id
    // IDENTITY 전략: 데이터베이스게 기본 AUTO_INCREMENT 기능 사용
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;
    private String username;
    private String title;
    private String content;

    // @CreationTimestamp : 하이버네이트가 제공하는 어노테이션
    // 특정 하나의 엔티티가 저장이 될 때 현재 시간을 자동으로 저장해 설정
    // now() 명시할 필요 없음
    // pc --> db (자동 날짜 주입)
    @CreationTimestamp
    private Timestamp createdAt;

    // createdAt -> 포멧 하는 메서드 만들어 보기
    public String getTime() {
        return MyDateUtil.timestampFormat(createdAt);
    }

    // 수정 편의 기능 만들기
    public void update(BoardRequest.UpdateDTO updateDTO) {
        this.username = updateDTO.getUsername();
        this.title = updateDTO.getTitle();
        this.content = updateDTO.getContent();

        // 더티체킹 - 변경 감지 동작 과정
        // 1. 최초 조회시 영속성 컨텍스트 1차 캐쉬에 데이터를 스냅샷으로 보관 함.
        // 2. 영속화된 엔티티가(board)의 멤버 변수값이 변경이 된다면
        //    1차에서 보관했던 값과 2차에서 수정된 필드값을 비교 함.
        // 3. 변화가 발생이 되었다면 트랜잭션 커밋 시점에 변경된 필드값 UPDATE 쿼리 자동 생성
        // 4. 물리적은 DB에 반영 됨.
    }

}

더티 체킹(Dirty Checking)

영속 상태의 엔티티 값만 변경하면, 트랜잭션이 끝날 때 JPA가 알아서 UPDATE SQL을 실행하는 기능

 


더티 체킹 작동 원리

조회: DB에서 엔티티를 조회하여 영속성 컨텍스트에 넣음. (이때 스냅샷 생성)

수정: 자바 코드에서 객체의 값(setName, setAge 등)을 바꿈.

트랜잭션 커밋: 사용자가 커밋을 하면 JPA는 내부적으로 flush()를 호출.

비교: 현재 엔티티의 상태와 처음 찍어둔 스냅샷을 하나하나 비교.

SQL 실행:  이름이 바뀌었다라고 판단되면 자동으로 UPDATE 쿼리를 만들어 DB로 날림.

 


더티 체킹의 조건

영속 상태여야 함: 영속성 컨텍스트가 관리하지 않는 객체(비영속, 준영속)는 값을 아무리 바꿔도 JPA가 알 방법이 없음.
장바구니에 담긴 물건이어야 주인이 바뀌었는지 알 수 있는 것과 같다.

트랜잭션 안에서 일어나야 함: 보통 서비스 레이어의 메서드에 @Transactional이 붙어 있어야 한다.
트랜잭션이 끝나는 시점에 비교 작업이 시작되기 때문이다