👥🚨 수정은 됐는데 화면에는 “실패”라고 뜬다?

2026. 2. 7. 22:32·프로젝트/스프링 부트 3 백엔드 개발자 되기 Blog + 선착순 강의 프로젝트

1️⃣ 문제 상황

게시글 수정 기능을 구현한 뒤, 이상한 현상을 발견했다.

❗ 증상

  • 수정 버튼 클릭 → “수정에 실패했습니다” 알림
  • 하지만 새로고침하면?
    • ✅ DB에는 수정 내용이 반영되어 있음

👉 수정은 성공했는데, 화면에는 실패로 보이는 상황


2️⃣ 에러 로그 확인

서버 로그에서 가장 중요한 메시지는 이 문장이었다.

InvalidDefinitionException:
No serializer found for class 
org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor

처음 보면 굉장히 intimidating 하지만,
의미만 풀면 아주 단순한 문제다.


3️⃣ 에러 원인 분석

한 줄 요약

지연 로딩(LAZY) 상태의 엔티티를 그대로 JSON으로 반환하려다 Jackson이 폭발했다.

무슨 일이 벌어졌을까? (순서대로)

  1. 사용자가 수정 버튼 클릭
  2. 서버에서 update 로직 실행
    • ✅ DB 업데이트 성공
  3. 컨트롤러에서 수정된 Article 엔티티를 그대로 반환
  4. Jackson이 JSON 변환 시도
  5. Article.author가 지연 로딩 프록시 객체
  6. Jackson: “이 가짜 객체(ByteBuddyProxy)를 JSON으로 어떻게 바꾸라는 거야?”
  7. 💥 500 Internal Server Error 발생

왜 author가 문제일까?

@ManyToOne(fetch = FetchType.LAZY)
private User author;
  • author는 실제 User가 아니라 프록시 객체
  • Jackson은 이 프록시를 직렬화할 방법을 모름
  • 그 결과 InvalidDefinitionException 발생

4️⃣ 그래서 왜 DB는 바뀌었을까?

이 부분이 가장 헷갈리는 포인트다.

실제 작업 순서

  1. Hibernate가 DB에 update 쿼리 실행
  2. 트랜잭션 내부에서 변경 사항 반영
  3. 응답을 만들다가 JSON 변환 단계에서 실패
  4. 서버는 500 에러 반환
  5. 프론트엔드는 실패로 인식

👉 즉,

“일은 다 끝냈는데, 결과 보고서 작성하다가 넘어진 상황”

그래서 화면에는 실패가 뜨지만, DB에는 이미 반영되어 있었던 것.


5️⃣ 문제의 근본 원인

컨트롤러에서 엔티티를 직접 반환하고 있었다.

❌ 나쁜 예 ❌

@PutMapping("/api/articles/{id}")
public ResponseEntity<Article> updateArticle(
        @PathVariable long id,
        @RequestBody UpdateArticleRequest request) {

    Article updatedArticle = blogService.update(id, request);

    // 🚨 엔티티 직접 반환
    return ResponseEntity.ok(updatedArticle);
}

6️⃣ 해결 방법

✅ 정석적인 해결책: DTO를 반환하자

엔티티는 DB용 객체
API 응답은 DTO(Data Transfer Object)

✔ 수정된 컨트롤러 코드

@PutMapping("/api/articles/{id}")
public ResponseEntity<ArticleResponse> updateArticle(
        @PathVariable long id,
        @RequestBody UpdateArticleRequest request) {

    Article updatedArticle = blogService.update(id, request);

    return ResponseEntity.ok(new ArticleResponse(updatedArticle));
}

// 또는 수정 성공 여부만 전달
// return ResponseEntity.ok().build();

7️⃣ DTO 예시

ArticleResponse

@Getter
public class ArticleResponse {

    private final String title;
    private final String content;
    private final String author;

    public ArticleResponse(Article article) {
        this.title = article.getTitle();
        this.content = article.getContent();
        this.author = article.getAuthor().getNickname();
    }
}

ArticleListViewResponse

@Getter
public class ArticleListViewResponse {

    private final long id;
    private final String title;
    private final String content;
    private final String author;

    public ArticleListViewResponse(Article article) {
        this.id = article.getId();
        this.title = article.getTitle();
        this.content = article.getContent();
        this.author = article.getAuthor().getNickname();
    }
}

8️⃣ 왜 DTO를 써야 할까?

① Jackson 직렬화 안정성

  • DTO는 순수 데이터(String, 숫자) 만 포함
  • 프록시, 지연 로딩, Hibernate 내부 구현 ❌

② 보안

  • 엔티티를 그대로 반환하면:
    • 비밀번호
    • 내부 필드
    • 연관 객체
      가 의도치 않게 노출될 위험
  • DTO는 보여주고 싶은 것만 선택

③ 순환 참조 방지

  • 엔티티 간 양방향 연관관계
  • 그대로 반환 시 무한 루프
  • DTO는 이런 문제를 구조적으로 차단

9️⃣ 한 줄 요약

“엔티티를 API 응답으로 바로 던지는 순간, 언젠가는 반드시 터진다.”
→ 항상 DTO에 담아서 보내자.

'프로젝트 > 스프링 부트 3 백엔드 개발자 되기 Blog + 선착순 강의 프로젝트' 카테고리의 다른 글

👥💡 Querydsl 페이징: fetchResults()가 비권장(Deprecated)된 이유와 실무 정석 패턴  (0) 2026.02.14
👥🚨 수정시간 및 수정하기 작성자 권한 변경  (0) 2026.02.14
👥💡 N+1 리팩토링 과정  (1) 2026.02.07
👥🚨 로컬로 실행 후 배포한 서버에도 DB가 적용되는 이유  (0) 2026.02.07
👥🚨 배포 후 로컬 실행 시 OAuth2 승인 오류  (0) 2026.02.07
'프로젝트/스프링 부트 3 백엔드 개발자 되기 Blog + 선착순 강의 프로젝트' 카테고리의 다른 글
  • 👥💡 Querydsl 페이징: fetchResults()가 비권장(Deprecated)된 이유와 실무 정석 패턴
  • 👥🚨 수정시간 및 수정하기 작성자 권한 변경
  • 👥💡 N+1 리팩토링 과정
  • 👥🚨 로컬로 실행 후 배포한 서버에도 DB가 적용되는 이유
hak0622
hak0622
개발하면서 “이게 뭐지?”라는 순간마다 궁금한 점을 바탕으로 정리한 개발 블로그입니다.
  • hak0622
    궁금한 개발 이야기 Why?
    hak0622
  • 전체
    오늘
    어제
    • 분류 전체보기 (68)
      • 공부 (36)
        • 1. 자바 ORM 표준 JPA 프로그래밍 - 기본.. (35)
        • 시험 (1)
      • 프로젝트 (32)
        • 스프링 부트 3 백엔드 개발자 되기 Blog + .. (32)
  • 인기 글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
hak0622
👥🚨 수정은 됐는데 화면에는 “실패”라고 뜬다?
상단으로

티스토리툴바