✅ 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 |