[Spring Boot] 수정 기능 구현 시, 멀쩡한 데이터가 NULL 로 덮어씌워지는 문제

2025. 12. 6. 02:06·ServerDev/SpringBoot

[Spring Boot] 수정 기능 구현 시, 멀쩡한 데이터가 NULL로 덮어씌워지는 문제

과거 멘토링 매칭 플랫폼 프로젝트 ( SSR로 구현 )를 진행하던 당시 겪었던 이슈를 기록으로 남기고자 합니다. 당시 회원 정보 수정 기능을 구현하고 테스트를 진행했는데, 분명 제목(Title)만 수정해서 저장했음에도 불구하고 카테고리나 인원수 같은 다른 필드들이 전부 NULL로 변해버리거나 데이터가 날아가는 현상이 있었습니다. 처음에는 단순한 쿼리 오류라고 생각했지만, 디버깅을 해보니 Spring MVC의 데이터 바인딩 원리와 JPA의 수정 방식을 제대로 이해하지 못해 발생한 구조적인 문제였습니다. 이번 글에서는 이 문제가 왜 발생했는지, 그리고 실무적으로 어떻게 해결하는 것이 가장 안전한지 정리해 보려고 합니다.

 

 


 

1. 문제 상황: 수정 버튼을 눌렀는데 데이터가 사라졌다

당시 상황은 이러했습니다. 멘토링 모집 글을 수정하는 화면이 있었고, 사용자는 기존에 입력된 데이터 중 일부(예: 제목)만 수정하고 싶어 했습니다.

기존 데이터 상태

  • 제목: "멘토링 모집합니다"
  • 카테고리: "개발"
  • 최소 인원: 2명
  • 최대 인원: 5명

사용자 행동

  • 제목을 "멘토링 모집 (수정)"으로 변경
  • 나머지 항목은 건드리지 않음 (혹은 수정 폼에 아예 노출되지 않음)

결과
DB를 확인해 보니 제목은 정상적으로 변경되었으나, 카테고리, 최소 인원, 최대 인원 컬럼이 모두 NULL로 업데이트 되어 있었습니다.

2.  DTO와 Entity의 매핑 과정

이 문제가 발생한 근본적인 원인은 "웹의 동작 방식"과 "매핑" 때문이었습니다.

SSR(서버 사이드 렌더링) 환경에서의 특수성?

당시 프로젝트는 타임리프(Thymeleaf)를 활용한 SSR 방식이었습니다. 하지만 이 문제는 SSR에서만 발생하는 것은 아닙니다. 다만 발생 원인이 조금 다릅니다.

  • SSR (HTML Form 태그): HTML Form 전송 시, 화면에 <input> 태그가 존재하지 않거나 disabled 처리된 필드는 아예 서버로 전송되지 않습니다.
  • REST API (JSON): 클라이언트가 수정할 필드만 JSON에 담아 보내면, 나머지 필드는 전송되지 않습니다.

결국 SSR이든 API든, "클라이언트가 보내지 않은 데이터"에 대해 Spring Boot는 해당 DTO 필드를 null로 초기화합니다. 문제는 그다음입니다. 제가 작성했던 서비스 코드는 아래와 같았습니다.

// 당시의 잘못된 코드
public void update(MentorDTO dto) {
    Mentor entity = mentorRepository.findById(dto.getId()).get();

    // DTO의 null 값까지 그대로 엔티티에 덮어씌움
    entity.setTitle(dto.getTitle());       // "수정된 제목"
    entity.setCategory(dto.getCategory()); // null
    entity.setMinMent(dto.getMinMent());   // null
}

 

DTO에 category 값이 null로 들어왔는데, 이를 그대로 엔티티에 set 해버리니 기존에 있던 "개발"이라는 데이터가 null로 덮어씌워진 것입니다.

3. 시행착오

이 문제를 해결하기 위해 처음에는 몇 가지 임시방편을 떠올렸으나, 이는 좋은 방법이 아니었습니다.

 

시도 1. HTML 폼에 hidden input 심기
수정 화면에 모든 필드에 대해 <input type="hidden">을 만들어 기존 값을 넣어두는 방식입니다.

  • 문제점: 보안에 취약하며, 필드가 늘어날 때마다 HTML을 수정해야 해서 유지보수가 매우 어렵습니다.

 

시도 2. 컨트롤러에서 null 체크
컨트롤러 레벨에서 if (dto.getCategory() == null) 체크를 하는 방식입니다.

  • 문제점: 컨트롤러 코드가 비대해지고, 비즈니스 로직이 프레젠테이션 계층에 섞이게 됩니다.

 

4. 해결책: Service 계층에서의 책임 분리 (Partial Update)

 

가장 정석적이고 안전한 해결 방법은 "수정은 기존 데이터를 조회한 뒤, 변경된 부분만 병합(Merge)한다"는 원칙을 지키는 것입니다.

이를 위해 서비스 계층에서 다음과 같은 흐름으로 로직을 변경했습니다.

  1. 조회: 수정할 대상 엔티티를 DB에서 먼저 가져옵니다 (findById).
  2. 병합: DTO에 값이 존재하는(Null이 아닌) 필드만 엔티티에 옮겨 담습니다.
  3. 저장: 트랜잭션 종료 시 변경 감지(Dirty Checking)를 통해 Update 쿼리가 실행됩니다.

개선된 코드 예시

@Transactional
public void update(Long id, MentorDTO dto) {
    // 1. 기존 영속성 객체 조회
    Mentor mentor = mentorRepository.findById(id)
        .orElseThrow(() -> new NotFoundException("해당 게시글이 존재하지 않습니다."));

    // 2. 값이 있는 경우에만 업데이트 수행
    updateIfPresent(mentor, dto);
}

private void updateIfPresent(Mentor mentor, MentorDTO dto) {
    if (dto.getTitle() != null) {
        mentor.setTitle(dto.getTitle());
    }
    if (dto.getCategory() != null) {
        mentor.setCategory(dto.getCategory());
    }
    if (dto.getMinMent() != null) {
        mentor.setMinMent(dto.getMinMent());
    }
    // 필요한 필드 반복...
}

 

이렇게 코드를 변경하면, 사용자가 제목만 수정해서 보냈을 때 dto.getCategory()는 null이므로 if문을 타지 않게 됩니다. 즉, 엔티티의 기존 category 값("개발")이 그대로 유지됩니다.

 

5. 마무리하며

 

 

개발 초기에는 "그냥 받은 데이터를 다 저장하면 되는 거 아닌가?"라고 단순하게 생각하기 쉽습니다. 하지만 수정(Edit) 기능은 생성(Create) 기능보다 고려해야 할 점이 많습니다.

  • null은 "값 없음"을 의미할 수도 있지만, 수정 로직에서는 "변경하지 않음(Keep)"을 의미해야 할 때가 많습니다.
  • Validation을 적용할 때도 주의해야 합니다. 생성 시에는 @NotNull이어야 하지만, 수정 시에는 null을 허용해야 할 수 있기 때문에 Validation Group을 나누는 전략도 함께 고려해야 합니다.

결론적으로, SSR 환경이든 API 환경이든 "넘어오지 않은 데이터는 null이 된다"는 사실을 인지하고, 서비스 계층에서 꼼꼼하게 병합(Merge) 로직을 구현하는 것이 데이터 소실을 막는 가장 확실한 방법입니다.

'ServerDev > SpringBoot' 카테고리의 다른 글

[JPA] 동시성 제어 : LockModeType.PESSIMISTIC_WRITE  (0) 2025.12.07
[SpringBoot&JPA] QR 코드 기반 게스트 계정 자동 생성 및 로그인 구현  (0) 2025.12.07
[Spring Boot] 권한 검증 - 커스텀 AOP와 Redis로 해결하기 (@IsCouncil)  (0) 2025.12.06
[SpringBoot - SSR] 파일 업로드 에러: "Failed to convert String to MultipartFile" 원인과 해결  (0) 2025.12.06
[ Web Stack] 스프링 부트 Web Stack 종류 - Server MVC & WebFlux  (0) 2025.12.06
'ServerDev/SpringBoot' 카테고리의 다른 글
  • [SpringBoot&JPA] QR 코드 기반 게스트 계정 자동 생성 및 로그인 구현
  • [Spring Boot] 권한 검증 - 커스텀 AOP와 Redis로 해결하기 (@IsCouncil)
  • [SpringBoot - SSR] 파일 업로드 에러: "Failed to convert String to MultipartFile" 원인과 해결
  • [ Web Stack] 스프링 부트 Web Stack 종류 - Server MVC & WebFlux
yeseul-kim01
yeseul-kim01
  • yeseul-kim01
    슬 개발일지
    yeseul-kim01
  • 전체
    오늘
    어제
    • 분류 전체보기 (79)
      • 자격증 (1)
        • 정보보안기사 (0)
      • DevOps (17)
        • Docker (6)
        • Kubernetes (1)
        • GitHub Actions (0)
        • AWS (4)
        • Monitoring (1)
        • Nginx (1)
        • GCP (3)
      • ServerDev (34)
        • SpringBoot (13)
        • DJango (5)
        • FastAPI (14)
        • Next (0)
        • Flask (0)
        • Database (2)
      • Algorithm (2)
        • BFS (0)
        • DFS (1)
        • 다익스트라 (0)
      • CS (8)
      • Data Engineering (1)
      • AI&MLOps (2)
      • Architecture (6)
      • Software Engineering (0)
        • Library Packaging (0)
      • Project (5)
        • docx-generator (0)
        • speak-note (2)
        • ms-serving (1)
        • keyshield (2)
      • ProgrammingLanguages (3)
        • Python (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    STT
    grpc
    실시간시스템
    아키텍처설계
    Kubernetes
    FastAPI - CORS 마스터
    Django
    multipartfile
    FastAPI
    트러블슈팅
    프로젝트기록-KeyShield
    동시성제어
    docker
    NLP부트캠프
    프로젝트기록-speaknote
    rag
    KServe
    SpringBoot
    실무일기-인프라편
    백엔드
    depends
    하이브리드아키텍처
    멀티모듈
    SpeakNote
    아키텍처
    실무일기-백엔드편
    MLops
    KeyShield
    di
    비동기처리
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
yeseul-kim01
[Spring Boot] 수정 기능 구현 시, 멀쩡한 데이터가 NULL 로 덮어씌워지는 문제
상단으로

티스토리툴바