본문 바로가기

카테고리 없음

📘 TIL: DTO ↔ Entity 매핑, 부분 업데이트(PATCH), JSON 직렬화까지 한 번에 잡기

✅ 1. DTO ↔ Entity 매핑: 수동 vs 자동

✨ 수동 매핑

단순한 프로젝트나 예외 로직이 많은 경우 권장.

public User toEntity(UserDto dto) {
    return User.builder()
            .id(dto.getId())
            .name(dto.getName())
            .email(dto.getEmail())
            .build();
}

 

✨ 자동 매핑 (MapStruct)

@Mapper(componentModel = "spring")
public interface UserMapper {
    User toEntity(UserDto dto);
    UserDto toDto(User entity);
}

 

✅ 2. @DynamicUpdate – 변경된 필드만 DB에 반영

@Entity
@DynamicUpdate
public class User {
    private String name;
    private String email;
}

 

  • 변경된 필드만 UPDATE SQL에 포함됨
  • 불필요한 DB 작업 방지 및 트리거 안정성 보장
더보기

💡
@DynamicUpdate는 모든 엔티티에 일괄 적용하지 말고,
컬럼 수가 많고 부분 수정이 빈번한 테이블에만 선택적으로 적용하세요.

✅ 1. @DynamicUpdate의 동작 방식 복습

  • JPA는 일반적으로 Entity가 수정되면 모든 필드를 UPDATE SQL에 포함시킵니다.
  • 하지만 @DynamicUpdate를 붙이면,
    실제로 변경된 필드만 UPDATE SQL에 포함됩니다.
-- 기본 JPA 동작
update user set name = ?, email = ?, phone = ? where id = ?

        -- @DynamicUpdate 사용 시 (예: name만 변경됨)
update user set name = ? where id = ?

 

❗ 실제론 오히려 성능이 떨어질 수도 있다?

▶ 이유 1. Hibernate는 변경 감지를 위해 매번 필드 비교를 수행

  • @DynamicUpdate가 적용되면, JPA는 SQL을 동적으로 생성하기 위해
    모든 필드의 이전 값(old value)과 현재 값(current value)를 비교해야 합니다.
  • 즉, 성능이 좋은 정적 쿼리 캐시를 사용할 수 없고, 매번 새로운 SQL을 만들게 됩니다.

📌 일반 UPDATE는 정적 쿼리를 캐싱하여 성능을 높일 수 있음
@DynamicUpdate 사용 시 → 매번 동적 쿼리 생성JPA 성능 캐시 미사용

▶ 이유 2. JPA 2차 캐시, Batch 처리와의 충돌 가능성

  • Hibernate의 batch update 전략을 사용하는 경우
    → 동적으로 SQL이 달라지면 batch가 깨지고 단건 처리로 전환
  • 또한, 2차 캐시를 사용할 경우에도 정적 쿼리에 비해 동적 쿼리는 비효율적

▶ 이유 3. 변경 감지(Dirty Checking) 비용 증가

  • 모든 필드에 대해 equals 비교가 필요하므로
    엔티티의 필드 수가 많을수록 부하 증가

✅ 그래서 언제 사용하는 게 좋을까?

상황사용 여부
필드 수가 적고 항상 전체 수정 ❌ 굳이 사용하지 않아도 됨
필드 수 많음 + PATCH 방식 사용 ✅ 선택적으로 사용 권장
엔티티가 자주 조회되고 적게 수정됨 ❌ 불필요한 동적 쿼리는 오히려 손해
DB 트리거가 있어서 수정된 필드만 반영돼야 하는 경우 ✅ 필수적으로 사용해야 함

✅ 3. @JsonInclude – JSON 직렬화 제어

@JsonInclude(JsonInclude.Include.NON_NULL)
public class UserDto {
    private String name;
    private String email;
}
  • NON_NULL: null 값은 JSON 응답에서 제외
  • NON_EMPTY: 빈 리스트나 문자열도 제외

💡
클라이언트에 꼭 필요한 정보만 전달하고 싶을 때 사용하세요.
특히 공통 응답 DTO에 적용하면 효과적입니다.

더보기

❗ 왜 입력(PATCH)에선 @JsonInclude가 작동하지 않나?

  • @JsonInclude는 직렬화(출력) 전용
  • 역직렬화(입력) 시에는 아무 효과 없음

→ 입력값에서 null을 무시하려면 @JsonInclude가 아닌 다른 전략 필요

✅ 4.PATCH 요청 처리 – MapStruct에서 null 무시 설정

@Mapper(componentModel = "spring")
public interface ShopMapper {

    @BeanMapping(nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
    void updateShopFromDto(ShopPatchRequestDto dto, @MappingTarget Shop entity);
}
  • null 값인 필드는 무시하고, 기존 값을 유지
  • PATCH에서 필수적인 부분 매핑 로직

💡
PATCH 요청은 "해당 필드만 변경"이 핵심!
@MappingTarget과 nullValuePropertyMappingStrategy.IGNORE를 함께 기억하세요.

 

✅ 전체 흐름 요약: 실전 활용 시나리오

단계처리 내용관련 기술
1 클라이언트에서 DTO로 PATCH 요청 JSON, DTO 클래스
2 DTO → Entity 변환 (MapStruct) @BeanMapping, @MappingTarget
3 Entity 저장 (JPA) @DynamicUpdate
4 응답 시 DTO 변환 @JsonInclude