[Lazy로딩] Postman에서 INTERNAL_SERVER_ERROR만 보이고, 콘솔에는 아무 에러 로그도 안 찍힐 때

2025. 10. 17. 13:57·[AND] 사용자 정의 지표 기반 실시감 감지 및 자동매매 시스템/기술

1. 문제 상황 

Postman에서 GET /api/alerts/today 를 호출했다. 분명 response에는 서버 내부 오류가 발생했다고 하는데 IntelliJ에는 아무런 예외 로그도 표시되지 않았다. 서버도 정상적으로 기동되고 쿼리도 정상 수행되는 상황이었다. 

 
 

 

2.  원인 분석

이 경우 대부분은 GlobalExceptionHandler가 모든 예외를 한꺼번에 잡고, 내부 로그를 찍지 않아서 발생하는 현상이다.

프로젝트에서 사용하는 ApiResponse, ResponseCode, GlobalExceptionHandler가 다음과 같이 되어 있었다.

@ExceptionHandler(RuntimeException.class)
protected ResponseEntity<ApiResponse<?>> handleRuntimeException(RuntimeException e) {
    return ResponseEntity
            .status(HttpStatus.BAD_REQUEST)
            .body(ApiResponse.error(ResponseCode.INTERNAL_SERVER_ERROR));
}

 

즉, 실제 예외는 존재하지만 log.error(e)가 없어서 콘솔에 찍히지 않은 것이다.
따라서 Postman에는 단순히 "서버 내부 오류"만 내려왔다.

 

 

그래서 log.error 와 sout으로 어느 부분에서 오류가 나는지 확인해보았다. 

@RestController
@RequestMapping("/api/alerts")
@RequiredArgsConstructor
public class AlertHistoryController {

    private final AlertHistoryService alertHistoryService;

    @GetMapping("/today")
    public ResponseEntity<?> getTodayAlertHistories(@AuthUser Long userId) {
        System.out.println(">>> [DEBUG] /api/alerts/today userId=" + userId);
        var histories = alertHistoryService.getTodayHistoryByUser(userId);
        System.out.println(">>> [DEBUG] result size=" + histories.size());
        return ResponseEntity.ok(histories);
    }

}
@ExceptionHandler(RuntimeException.class)
protected ResponseEntity<ApiResponse<?>> handleRuntimeException(RuntimeException e) {
    log.error("[INTERNAL_SERVER_ERROR]", e);
    return ResponseEntity
            .status(HttpStatus.BAD_REQUEST)
            .body(ApiResponse.error(ResponseCode.INTERNAL_SERVER_ERROR));
}

 

원인을 파악한 결과, Hibernate는 정상적으로 데이터를 조회한 후 컨트롤러에서 엔티티를 그대로 리턴하면서 문제가 발생했다.

 

AlertHistory 엔티티는 내부에 Lazy 로딩 관계를 갖고 있었다.

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "alert_id", nullable = false)
private Alert alert;

이 Lazy 필드를 JSON으로 직렬화하려다가
Alert → AlertHistory → Alert ... 순환 참조 구조에 빠져 직렬화 예외(StackOverflowError) 가 발생한 것이다.

 

하지만 이 예외가 handler로 인해 잡혀 숨겨졌고 Postman에서만 INTERNAL_SERVER_ERROR가 노출된 것이었다.

 

2-1. 왜 Lazy 로딩이 문제일까

AlertHistory를 조회할 때 실제 DB에서 alert 테이블은 바로 조회되지 않는다. Hibernate는 내부적으로 “프록시(대리 객체)”를 만들어 둔다.

Lazy 로딩 시, Hibernate는 실제 Alert 엔티티 대신 org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor 가 붙은 프록시 객체를 반환하는 것이다. 

 

즉, 이런 형태인 것이다. 

alertHistory.getAlert();  // 실제로는 AlertProxy (ByteBuddy Proxy)

이 프록시는 DB를 아직 조회하지 않은 상태이고 필요할 때 세션을 통해 실제 데이터를 가져오게 된다.Spring Boot의 기본 설정은 spring.jpa.open-in-view=true라서 트랜잭션이 끝나도 Lazy 필드를 직렬화 시 접근할 수 있기 때문이다.

 

근데 JSON 직렬화(Jackson) 시점에서 Controller에서

return ResponseEntity.ok(alertHistory);

이런 식으로 리턴하면
Spring MVC는 내부적으로 Jackson(ObjectMapper) 를 써서 객체를 JSON으로 바꾼다.

문제는 이때 발생하는데 Hibernate Proxy는 순수 자바 객체가 아니라서 ByteBuddyInterceptor 필드를 가지고 있고 Jackson이 이걸 직렬화하려다 아래 오류가 난다

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

“이 프록시 안에 뭘 직렬화해야 하는지 모르겠음!”

 

 

3. 해결 방법

DTO(record)로 변환하여 반환하는 것이다. 엔티티를 그대로 반환하지 말고, DTO를 만들어서 필요한 데이터만 선택적으로 리턴한다.

@GetMapping("/today")
public ResponseEntity<?> getTodayAlertHistories(@AuthUser Long userId) {
    var histories = alertHistoryService.getTodayHistoryByUser(userId)
            .stream()
            .map(AlertHistoryDto::from)
            .toList();
    return ResponseEntity.ok(histories);
}
public record AlertHistoryDto(
        Long id,
        LocalDateTime createdAt,
        String indicatorSnapshot,
        String stockCode
) {
    public static AlertHistoryDto from(AlertHistory entity) {
        return new AlertHistoryDto(
                entity.getId(),
                entity.getCreatedAt(),
                entity.getIndicatorSnapshot(),
                entity.getAlert() != null ? entity.getAlert().getStockCode() : null
        );
    }
}

→ Lazy 로딩된 Alert 전체를 건드리지 않고 alertId까지만 접근하므로 순환 참조 및 직렬화 오류가 사라진다.

'[AND] 사용자 정의 지표 기반 실시감 감지 및 자동매매 시스템 > 기술' 카테고리의 다른 글

[Redis] private subnet에 있는 redis를 redis insight를 확인하고 싶을 때  (6) 2025.10.25
[FCM] Spring boot와 FCM(firebase) 연결하기  (0) 2025.10.17
[JavaBean] boolean 값이 계속해서 false가 되는 이유  (0) 2025.10.17
[JWT] Spring Boot security 와 JWT로 인증/인가 구현하기(2) - 설정 파일, 예외 처리  (0) 2025.10.06
[JWT] Spring Boot security 와 JWT로 인증/인가 구현하기(1) - Access Token, Refresh Token과 멀티 디바이스  (7) 2025.10.05
'[AND] 사용자 정의 지표 기반 실시감 감지 및 자동매매 시스템/기술' 카테고리의 다른 글
  • [Redis] private subnet에 있는 redis를 redis insight를 확인하고 싶을 때
  • [FCM] Spring boot와 FCM(firebase) 연결하기
  • [JavaBean] boolean 값이 계속해서 false가 되는 이유
  • [JWT] Spring Boot security 와 JWT로 인증/인가 구현하기(2) - 설정 파일, 예외 처리
지-토리
지-토리
  • 지-토리
    지토리의 개발이야기
    지-토리
  • 전체
    오늘
    어제
    • 분류 전체보기 (46)
      • [AND] 사용자 정의 지표 기반 실시감 감지 및.. (13)
        • 기획 (4)
        • 기술 (8)
      • [Linkompany] 뉴스와 공시를 통합 기업 .. (4)
      • [Candly] 차트 공부 및 예측 웹 프로젝트 (2)
      • JAVA (4)
        • 코딩테스트 (1)
      • 대규모 시스템 설계 (1)
      • Infra (12)
      • ELK (3)
      • AI (2)
      • FrontEnd (1)
      • TIP (2)
      • 자격증 (1)
  • 블로그 메뉴

    • 홈
    • Github
  • 링크

  • 공지사항

  • 인기 글

  • 태그

    kpt
    프로디지털아카데미
    쿠버네티스
    신한투자증권
    And
    알파코캠퍼스
    EC2
    Firebase
    인프런
    K8S
    부트캠프
    신투
    프로젝트
    인증/인가
    K디지털트레이닝
    SpringSecurity
    Issue
    알파코
    프디아
    kdt교육
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
지-토리
[Lazy로딩] Postman에서 INTERNAL_SERVER_ERROR만 보이고, 콘솔에는 아무 에러 로그도 안 찍힐 때
상단으로

티스토리툴바