Java record란?
Java record는 Java 14에서 도입된 기능이다. 주로 데이터 전달 객체(DTO)와 같은 불변 객체를 정의할 때 사용된다. record는 클래스를 정의할 때 필드, 생성자, 접근자, toString(), equals() 및 hashCode() 메서드를 자동으로 생성하여 코드가 간결해진다.
주요 특징은 다음과 같다:
1. 불변성 (Immutability):
record는 기본적으로 불변 객체이다. 생성 후 상태를 변경할 수 없다.
2. 자동 생성되는 메서드들:
record는 필드를 기반으로 toString(), equals(), hashCode(), getter 메서드를 자동으로 생성한다.
3. 간결한 문법:
record는 클래스를 정의하는 것보다 간단한 문법을 제공한다.
기존 class를 이용해서 DTO 작성 방식
기존 class를 이용하는 방식으로는 세 가지 DTO 클래스(HouseInfoDto, RoomDto, RoommateDto)를 lombok으로 관리한다고 해도 다음과 같은 불편함이 존재한다.
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class HouseInfoDto {
private Long houseId;
private String name;
private String mainImgUrl;
private String monthlyRent;
private String deposit;
private String location;
private String occupancyTypes;
private String occupancyStatus;
private String genderPolicy;
private int contractTerm;
private List<String> moodTags;
private String roomMood;
private List<String> groundRule;
private int maintenanceCost;
private boolean isPinned;
private List<String> safetyLivingFacility;
private List<String> kitchenFacility;
}
@Data
@Builder
public class RoomDto {
private Long roomId;
private String name;
private boolean status;
private int occupancyType;
private String gender;
private int deposit;
private int prepaidUtilities;
private int monthlyRent;
private String contractPeriod;
private String managementFee;
}
@Data
@Builder
public class RoommateDto {
private String name;
private int age;
private String job;
private String mbti;
private String sleepTime;
private String activityTime;
}
- 중복된 Boilerplate(별 수정 없이 반복적으로 사용되는 코드) 코드:
- DTO 클래스마다 @Data, @Builder, @NoArgsConstructor, @AllArgsConstructor를 반복적으로 선언해야 했음.
- 이는 코드가 불필요하게 길어지고 유지보수의 부담을 가중시킴.
- 일관된 데이터 구조 제공 부족:
- 특정 엔드포인트에서 HouseInfoDto, RoomDto, RoommateDto를 조합하여 응답을 반환할 때, 이 세 클래스가 독립적으로 관리되다 보니 직관적인 구조를 제공하기 어려웠음.
- 이로 인해 API 응답 객체를 파악하기 힘들고, 가독성이 떨어지는 JSON 응답 구조가 만들어질 수 있었음.
- 코드 유지보수 및 확장성 저하:
- 연관된 클래스들이 분리되어 있어 변경사항이 발생하면 각각의 파일을 수정해야 했고, 이로 인해 유지보수성이 저하됨.
- 특히 연관성이 높은 데이터 구조를 한곳에서 확인하고 관리할 수 있는 장점이 사라졌음.
Record를 이용해서 DTO 작성
이러한 문제를 해결하기 위해 다음과 같은 방식을 적용하였다.
public record HouseDetailsResponseDto (
HouseInfoDto houseInfo,
List<RoomDto> rooms,
List<RoommateDto> roommates
) {
@Builder
public HouseDetailsResponseDto{
}
public record HouseInfoDto (
Long houseId,
String name,
String mainImgUrl,
String monthlyRent,
String deposit,
String location,
String occupancyTypes,
String occupancyStatus,
String genderPolicy,
int contractTerm,
List<String> moodTags,
String roomMood,
List<String> groundRule,
int maintenanceCost,
boolean isPinned,
List<String> safetyLivingFacility,
List<String> kitchenFacility
) {
@Builder
public HouseInfoDto{
}
}
public record RoomDto (
Long roomId,
String name,
boolean status,
int occupancyType,
String gender,
int deposit,
int prepaidUtilities,
int monthlyRent,
String contractPeriod,
String managementFee
) {
@Builder
public RoomDto {
}
}
public record RoommateDto (
String name,
int age,
String job,
String mbti,
String sleepTime,
String activityTime
) {
@Builder
public RoommateDto {
}
}
}
- Record로 전환:
- Java의 record는 간결한 문법을 제공하여 데이터 모델을 작성할 때 반복적인 boilerplate 코드를 제거해줌.
- 기존 DTO에서 사용되던 @Data, @Builder, @NoArgsConstructor, @AllArgsConstructor를 제거하고, record 문법으로 대체함으로써 클래스 정의를 단순화함.
- 단일 응답 객체로 통합:
- HouseDetailsResponseDto를 생성하여 HouseInfoDto, RoomDto, RoommateDto를 하나의 응답 객체로 묶음.
- 응답 데이터의 구조를 명확히 하고 관련 데이터 간의 관계를 직관적으로 보여줄 수 있도록 설계함.
ApiResponseDto record 활용(BaseResponse)
아래와 같이 base response record 작성하고 contoller layer에서 활용한다.
package server.producer.common.dto;
import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_NULL;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.annotation.Nullable;
import lombok.Builder;
import lombok.NonNull;
import org.springframework.http.HttpStatus;
import server.producer.common.dto.enums.ErrorCode;
import server.producer.common.dto.enums.SuccessCode;
@Builder
public record ApiResponseDto<T>(
int code,
@NonNull String message,
@JsonInclude(value = NON_NULL) T data
) {
public static <T> ApiResponseDto<T> success(final SuccessCode successCode, @Nullable final T data) {
return ApiResponseDto.<T>builder()
.code(successCode.getCode())
.message(successCode.getMessage())
.data(data)
.build();
}
public static <T> ApiResponseDto<T> success(final SuccessCode successCode) {
return ApiResponseDto.<T>builder()
.code(successCode.getCode())
.message(successCode.getMessage())
.data(null)
.build();
}
public static <T> ApiResponseDto<T> fail(final ErrorCode errorCode) {
return ApiResponseDto.<T>builder()
.code(errorCode.getCode())
.message(errorCode.getMessage())
.data(null)
.build();
}
}
Contoller Layer에서 적용
@GetMapping("/{houseId}/details")
public ApiResponseDto<HouseDetailsResponseDto> getHouseDetails(@PathVariable Long houseId) {
try{
HouseDetailsResponseDto responseDto = houseService.getHouseDetails(houseId, userId);
return ApiResponseDto.success(SuccessCode.HOUSE_DETAIL_GET_SUCCESS, responseDto);
} catch (EntityNotFoundException e) {
return ApiResponseDto.fail(ErrorCode.HOUSE_NOT_FOUND);
} catch (InvalidParameterException e) {
return ApiResponseDto.fail(ErrorCode.INVALID_PARAMETER);
} catch (Exception e){
return ApiResponseDto.fail(ErrorCode.INTERNAL_SERVER_ERROR);
}
}
반응형
'Server > 🌱 Spring Boot (java)' 카테고리의 다른 글
[Spring Boot jpa] 다중 LEFT JOIN 시 MultipleBagFetchException 발생 문제 (0) | 2025.01.25 |
---|---|
[Spring boot jpa] 연관된 엔티티 불러올때 LazyInitializationException 발생하는 문제 해결 (0) | 2025.01.25 |
[Spring boot jpa] 찜/좋아요 삭제시 연관된 엔티티도 같이 삭제되는 문제 해결 (0) | 2025.01.25 |
[Spring boot] SQL 예약어와 테이블 이름 충돌 문제 해결 (0) | 2025.01.25 |
[Spring boot] DataJpaTest를 이용한 Repository 테스트와 여러 문제 해결 (0) | 2025.01.25 |
댓글