Skip to content

캘린더 API 응답 시 JSON 직렬화 오류 #73

@Tae4an

Description

@Tae4an

문제 정의

캘린더 일정 조회 API 호출 시 다음과 같은 오류가 발생했습니다:

Type definition error: [simple type, class org.hibernate.proxy.pojo.bytebuddy.ByteBuddyInterceptor]

이는 JPA Entity를 JSON으로 직렬화하는 과정에서 발생한 문제로, Calendar 엔티티와 User 엔티티 간의 양방향 참조로 인한 순환 참조(Circular Reference) 이슈입니다.

문제 발생 원인

  1. Calendar 엔티티가 User 엔티티를 참조
  2. JPA의 지연 로딩(LAZY Loading)으로 인해 프록시 객체가 생성됨
  3. 이 프록시 객체를 JSON으로 직렬화하려 할 때 Jackson 라이브러리가 처리하지 못함

시도한 해결 방법들

1. @JsonIgnore 어노테이션 사용

@Entity
public class Calendar {
    @ManyToOne
    @JsonIgnore
    private User user;
}
  • 문제점: User 정보가 완전히 누락되어 클라이언트에서 필요한 사용자 정보를 얻을 수 없음

2. @JsonManagedReference, @JsonBackReference 사용

@Entity
public class Calendar {
    @ManyToOne
    @JsonManagedReference
    private User user;
}
  • 문제점: 복잡한 객체 그래프에서 예측하기 어려운 동작 발생

최종 해결 방법: DTO 패턴 적용

1. DTO 클래스 생성

@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CalendarDTO {
    private Long id;
    private String employeeId; 
    private String title;
    private LocalDateTime start;
    private LocalDateTime end;
    private LocalDateTime createdAt;
    private LocalDateTime updatedAt;

    // Entity -> DTO 변환 메서드
    public static CalendarDTO from(Calendar entity) {
        return CalendarDTO.builder()
                .id(entity.getId())
                .employeeId(entity.getUser().getEmployeeId())
                .title(entity.getTitle())
                .start(entity.getStart())
                .end(entity.getEnd())
                .createdAt(entity.getCreatedAt())
                .updatedAt(entity.getUpdatedAt())
                .build();
    }
    
    // DTO -> Entity 변환 메서드
    public Calendar toEntity(User user) {
        Calendar calendar = new Calendar();
        calendar.setTitle(this.title);
        calendar.setStart(this.start);
        calendar.setEnd(this.end);
        calendar.setUser(user);
        return calendar;
    }
}

2. Controller에서 DTO 사용

@RestController
@RequestMapping("/api/calendar")
@RequiredArgsConstructor
public class CalendarController {
    
    @GetMapping
    public ResponseEntity<List<CalendarDTO>> getUserEvents(@RequestHeader("X-User-Id") String employeeId) {
        return ResponseEntity.ok(service.getUserEvents(employeeId).stream()
                .map(CalendarDTO::from)
                .collect(Collectors.toList()));
    }
    
    @PostMapping
    public ResponseEntity<CalendarDTO> createEvent(
            @RequestHeader("X-User-Id") String employeeId,
            @RequestBody CalendarDTO calendarDTO) {
        Calendar savedCalendar = service.createEvent(employeeId, calendarDTO);
        return ResponseEntity.ok(CalendarDTO.from(savedCalendar));
    }
}

해결 결과

  1. 순환 참조 문제 해결: Entity 간의 순환 참조 문제가 DTO를 통해 깔끔하게 해결됨

  2. 데이터 전송 최적화:

    • 필요한 데이터만 클라이언트로 전송
    • User 엔티티의 민감한 정보(비밀번호 등) 노출 방지
  3. 명확한 API 계약:

    • API 응답 형식이 DTO를 통해 명확하게 정의됨
    • 프론트엔드와의 인터페이스가 더 안정적으로 변경
  4. 성능 향상:

    • 불필요한 데이터 직렬화/역직렬화 과정 제거
    • 네트워크 전송 데이터량 감소

추가 고려사항

  1. Validation: DTO 클래스에 검증 로직 추가 검토
  2. Mapping Library: 복잡한 엔티티-DTO 변환 시 MapStruct 등 매핑 라이브러리 도입 고려
  3. API 문서화: Swagger/OpenAPI 문서에 DTO 스키마 추가

이러한 해결 방법을 통해 안정적이고 효율적인 API 응답 처리가 가능해졌습니다.

Metadata

Metadata

Assignees

Labels

bugSomething isn't working

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions