본문으로 바로가기

RestAPI 컨트롤러 요청과 응답

category Spring boot 2024. 11. 6. 14:50

💡학습 목표
1. REST API에서 DTO를 사용 방법을 연습 한다.
2. Service 레이어에서 Entity 대신 DTO를 반환하는 이유에 대해 이해 한다.
3. Lazy Loading 문제를 해결하고 요청과 응답의 일관성 유지하는 방법에 대해 알아 본다.

application-dev.yml - open-in-view 설정 변경

  jpa:
    hibernate:
      ddl-auto: create          
    show-sql: true              
    properties:
      hibernate:
        format_sql: true          
        default_batch_fetch_size: 10  # 한번에 들고오는 개수 설정
    defer-datasource-initialization: true  
    open-in-view: false   # OSIV 개념 확인 

REST API - 회원가입 만들기


 💡진행 순서

1. 응답 결과 확인하기
2. 공통 DTO 사용해서 데이터 내려주기 설계
3. 서비스 레이어 코드 수정 (레포지토리 코드 확인)
4. 컨트롤러 레이어 코드 수정

 

JSON 응답 결과 값 샘플 먼저 보여 주기

{
    "status": 200,
    "msg": "성공",
    "body": {
        "id": 5,
        "username": "tenco2",
        "email": "a@naver.com"
    }
}
package com.tenco.blog_v3.user;

import lombok.Getter;
import lombok.Setter;

public class UserResponse {

    @Getter
    @Setter
    public static class DTO {

        private int id;
        private String username;
        private String email;

        //ex) User 엔티티 반환 --> 서비스 계층 DTO 객체로 변환 처리
        public DTO(User user) {
            this.id = user.getId();
            this.username = user.getUsername();
            this.email = user.getEmail();
        }
        // 이메일 정보 포함을 안하고 싶다면
        public DTO(User user, boolean includeEmail) {
            this.id = user.getId();
            this.username = user.getUsername();
            this.email = includeEmail ? user.getEmail() : null;
        }
    }
}

UserService

/**
 * 회원 가입 서비스
 */
@Transactional
public UserResponse.DTO signUp(UserRequest.JoinDTO reqDto ) {
    // 1. username <-- 유니크 확인
    Optional<User> userOp = userJPARepository.findByUsername(reqDto.getUsername());

    if(userOp.isPresent()) {
        throw new Exception400("중복된 유저네임입니다");
    }

    // 회원 가입 처리
    User savedUser =  userJPARepository.save(reqDto.toEntity());
    System.out.println("savedUser : " + savedUser);
    // c -> DTO 반환 처리
    return new UserResponse.DTO(savedUser);
}

UserController

package com.tenco.blog_v3.user;

// ... 생략 

@RequiredArgsConstructor
@Slf4j
@RestController  // 코드 수정 
public class UserController {
      
  // @ResponseBody // 데이터 반환
  @PostMapping("/join")
  public ResponseEntity<ApiUtil<UserResponse.DTO>> join(UserRequest.JoinDTO reqDTO) {
      // 회원가입 서비스는 --> 서비스 객체에게 위임한다.
      UserResponse.DTO resDTO = userService.signUp(reqDTO);
      return ResponseEntity.ok(new ApiUtil<>(resDTO));
  }

  // .. 생략 

}

 

 

포스트 맨 확인


 

open-in-view: true란?

  • open-in-view: true 설정은 Lazy Loading을 지원하기 위해 기본적으로 활성화되어 있는 설정입니다.
  • 즉시로딩과 지연로딩을 관리하기 위해 트랜잭션이 끝난 후에도 영속성 컨텍스트를 유지하여 뷰 레이어까지 사용할 수 있도록 합니다. 이는 데이터베이스 연결을 길게 유지하기 때문에 간편하지만, 대규모 트래픽 상황에서는 성능 저하자원 낭비를 초래할 수 있습니다.
  • REST API 설계에서는 권장되지 않습니다. API의 응답은 뷰와 무관하게 서비스 계층에서 필요한 데이터를 명시적으로 로딩하고 반환해야 하므로 open-in-view 설정을 false로 변경하여 트랜잭션을 명확히 관리하는 것이 좋습니다.

 

 

DTO(Data Transfer Object)를 사용하는 이유

문제점

  • 커넥션 시간이 길어진다: 서비스에서 직접 Entity를 다룰 경우, 불필요한 필드까지 응답하게 되면서 데이터 전송 시간이 늘어나게 됩니다.
  • 불필요한 필드 응답: Entity에는 민감한 정보나 사용되지 않는 필드가 포함될 수 있어, 응답에서 필요 없는 데이터까지 포함하게 됩니다.
  • Lazy Loading 문제: MessageConverter가 JSON을 만들 때 Lazy Loading된 필드를 기다리지 않고 바로 JSON을 생성하려다 오류가 발생할 수 있습니다 (레이지 로딩된 부분을 호출하면 된다)

해결법

  • open-in-view: false 설정: Lazy Loading을 사용하지 않고 필요 시점에 데이터 로딩을 처리합니다.
  • Service 레이어 종료 전에 Lazy Loading 수행: 서비스에서 필요한 데이터를 명시적으로 모두 로딩하여 이후 단계에서 문제가 없도록 합니다.
  • DTO 사용: Service에서 Entity 대신 DTO를 생성하여 클라이언트에 필요한 데이터만 응답합니다.

'Spring boot' 카테고리의 다른 글

JwtUtil 만들어 보기  (3) 2024.11.06
JWT란 뭘까?  (0) 2024.11.06
공통 응답 DTO 및 예외 처리 구조 만들기  (1) 2024.11.06
RestAPI 주소 변경 및 인터셉터 수정  (0) 2024.11.06
뷰 연결 컨트롤러 정리  (0) 2024.11.06