코딩짜는 일상

[현대이지웰 Java 풀스택 개발자 아카데미 6월] TIL 21차 - DTO와 Map을 이용한 데이터베이스 조회와 API 결과물 차이 비교 본문

TIL/TIL 챌린지

[현대이지웰 Java 풀스택 개발자 아카데미 6월] TIL 21차 - DTO와 Map을 이용한 데이터베이스 조회와 API 결과물 차이 비교

Remily 2025. 12. 9. 23:54
반응형

📚 서론

이번주는... 제가 최근에 어마어마한 업보 청산을 당해서...

...그것에 대해 기록 해볼까 합니다.🥲

이것 때문에 반나절 날린거만 생각하면... 후...(속상)

 

 

시작은 2개 이상의 테이블을 JOIN하여 데이터를 가져올 때

2개 테이블들의 필드가 전부 있는 별도의 DTO를 만들어야 하는 번거로움에서 시작되었습니다.

 

이때 강사님께 Map<String, Object>를 사용하여 데이터를 받는 방법도 있다고 조언 받았고

 단일 테이블을 조회하는 게 아니면 대부분 Map을 이용하여 서비스를 구현하게 되었습니다.🤪

 

프로젝트 초반에는 별 문제가 없었는데

중반 쯤 지나 구현할 리엑트 컴포넌트가 늘어나면서

작업을 좀 줄여보려고 컴포넌트를 분리하고 재사용하면서 문제가 생겼습니다.😓

 

 

1️⃣ 필드명 스타일 통일의 필요성

오라클은 큰따옴표(")를 쓰지 않는 한 대소문자 구분을 하지 않습니다.

 

때문에 테이블을 만들때도 테이블명, 필드명에 큰따옴표를 쓰지 않으면

모든 알파벳이 대문자로 통일됩니다.

 

 

이 상태에서 Map으로 데이터를 받게 되면 대문자인 테이블 필드명을 그대로 따라

서비스로 전달되는 데이터도 필드가 전부 대문자로 통일됩니다.

 

그런데 저는 가독성을 위해 DTO를 만들 땐 필드명을 카멜타입으로 작성했기에

리엑트에서 보면 어떤 건 only 대문자, 어떤 건 카멜타입으로 데이터가 들어오기에

컴포넌트가 많아질 수록 햇갈릴 수 밖에 없었습니다.

팀 프로젝트 페이지. 한 페이지에 각 컴포넌트마다 받는 필드명 스타일이 다름...

 

그래서 통일하기 위해 DTO의 모든 필드명을 대문자로 바꾸면서 새로운 문제가 발생합니다.

 

 

2️⃣ 필드명 스타일 섞임

필드명을 전부 대문자로 통일한 DTO

 

필드명 스타일이 섞인 API 데이터

 

DTO의 모든 필드명 스타일을 대문자로 통일했지만

결과는 위 사진처럼 필드명이 전부 소문자 스타일로 나오고

날짜만 대문자 스타일로 한 번 더 출력됩니다.

 

DTO를 보면 ODREGDATE에만 @JsonFormat 어노테이션이 붙어있는데

날짜 포맷을 처리하는 과정에서 DTO에 명시된 필드명 스타일을 인지하고

대문자로 한 번 더 출력한 것이 아닌가 싶습니다.

 

자, 그럼 다른 필드는 왜 전부 소문자로 스타일이 통일된 것일까요?

 

 

 

 

 

🎷 원인 : Lomboc과 Jackson의 환장의 콜라보

1️⃣ 롬복의 Getter/Setter 네이밍 규칙

롬복(Lomboc)은 주로 DTO, VO, Entity 같은 클래스에서 Getter/Setter 또는 생성자 또는 toString 같은

반복적인 코드를 자동으로 생성해주는 자바 라이브러리 입니다.

 

저는 주로 Getter와 Setter를 만드는 데 사용했는데 필드명이 전부 대문자 스타일이면

Getter는 getODREGDATE(), Setter는 setODREGDATE()가 됩니다.😉

 

 

2️⃣ JavaBeans 규칙

잭슨(Jackson)자바 객체를 JSON문자열로 전환해주는 라이브러리 입니다.

 

JavaBeans 규칙을 적용해서 자동으로 프로퍼티명을 만들고 api 응답에 회신하기 위해

결과 객체를 JSON으로 자동 변환해줍니다.

 

 

JavaBeans규칙을 차근차근 살펴보면 우선 get/set/is 같은 접두사를 제거한 뒤

첫 글자를 소문자로 변경해 주는데, "앞의 두 글자가 모두 대문자면 그대로 둔다"는 규칙이 있어서

getODREGDATE() 를 통해 자동으로 생성되는 프로퍼티명은 계획대로 ODREGDATE이 됩니다.

 

 

그런데 여기서 로우 카멜 케이스(LowerCamelCase)를 기본으로 하는 Jackson의 자체 규칙이 추가로 적용되고

ODREGDATE가 결과적으로 odregdate가 되면서 API의 모든 필드명은 소문자 스타일로 통일됩니다.😨

 

 

 

 

 

🎺 해결책들

여러가지 해결책이 있습니다.

전부 AI들이 추천해준 방식이죠...

 

 

먼저 Jackson의 네이밍 규칙을 어퍼 카멜 케이스(UpperCamelCase)로 변경하는 방법입니다.

하지만 이 방법은 모든 API에 통합 적용되는 것이며

팀 프로젝트 중반인 지금와서 변경하면 모든 API를 다시 검수해야 하므로 부적절합니다.😅

 

 

그 다음으로는 @JsonProperty 어노테이션을 붙여서 명시적으로 JSON 필드명을 지정하는 것입니다.

하지만 어노테이션을 따라 만든 대문자JSON 필드명 하나, Jackson이 자동으로 만든 소문자JSON 필드명 하나,

이렇게 총 2개가 각 필드마다 생겨나므로 주고 받는 데이터 패킷이 2배가 되어 부적절 합니다.😓

 

 

다음은 DTO를 소문자 스타일로 바꾸고 Map<String, Object>으로 받는 데이터도 소문자 스타일로 변경하는 것인데요.

 

이 방법은 resultMap에서 오라클 필드명과 매핑해줄 소문자 스타일의 필드명을 정의해주거나

약어(AS) 문법으로 각 필드마다 소문자 스타일의 필드명을 정의해주면 됩니다.

SELECT PRDNAME prdname, PRDPRICE prdprice, PRDSTOCK prdstock, ...
FROM PRODUCT

하지만 모든 필드명을 일일이 언급해주면 필드를 찾는 연산에서 쿼리 실행 시간이 증가할 수 있고

차라리 DTO를 만드는 편이 가독성이 더 좋을 것이므로 Map을 쓰는 의미가 없어집니다.😫

 

아니 왜 이런 방법만 알려주는데!! 전부 안되잖아!!!

 

 

 

 

 

📯 최종 해결책 : DTO를 카멜 스타일로 통일하고 Map 방식을 배제

그리하여 최종 선택한 해결책은 다시 처음으로 돌아와

Map<String, Object>로 받는 방식을 전부 DTO로 변경하고

DTO의 필드명 스타일을 카멜 스타일로 통일하는 방식을 채택했습니다.

일과가 끝날 쯤 되어서야 카멜 스타일로 만든 DTO는 카멜 스타일 그대로 받았다는 걸 떠올린 덕분이었어요...🥲

 

소문자나 대문자로 통일하면 필드명 가독성이 떨어지니까 카멜 스타일로 통일했고

Map을 쓰는 의미가 없어졌으니 DTO로 데이터를 받도록 변경한 것입니다.

 

돌고 돌아 겨우 찾은 해답...🫠

 

 

 

 

 

🔥 결론

위 에피소드를 겪으면서... 차라리 처음부터 DTO를 쓸껄 하고 얼마나 후회 했는지 모릅니다.😣

 

그냥 얌전히 DTO를 썼으면 @JsonFormat을 이용해 편하게 날짜형식을 바꿀 수 있었을텐데

괜히 Map으로 받아온다고 for문으로 KST포맷터를 만들어 일일이 적용하는 방식도 추가했었거든요.

// KST 포맷터
private static final DateTimeFormatter KST_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
.withZone(ZoneId.of("Asia/Seoul"));
// 날짜 필드 목록 (필요 시 추가 가능)
private static final List<String> DATE_FIELDS = Arrays.asList(
    "ODREGDATE"
    , "USERREGDATE"
);

// 불러온 데이터에 for로 포매터 사용...
List<Map<String, Object>> resultList = dao.메소드(params);
for (Map<String, Object> row : resultList) {
    for (String field : DATE_FIELDS) {
        Object value = row.get(field);
        if (value instanceof Timestamp ts) {
            row.put(field, KST_FORMATTER.format(ts.toInstant()));
        }
    }
}

어짜피 롬복 쓸 거... getter/setter 만드는 귀찮음도 없는데 진짜 왜 그랬지...

 

 

저는 DTO가 필요한 이유를 단순히 계층간에 데이터 전달을 위해서라고만 생각했는데

주고 받는 필드에 무엇이 있는지 바로 확인할 수 있어 테이블을 외우고 있지 않은 사람도 api를 쓰기 쉬워졌고

 

2개의 DTO를 필드로 갖고 있는 별도의 DTO를 만들어줌으로써

모든 필드명을 언급하지 않아도 다른 DTO의 모든 필드를 사용할 수 있는 등

 

 

사용하는 방법이 생각보다 유연해서 답답하지 않았습니다.

역시 대선배님들이 만들어 쓰시는 이유가 다 있다!!

 

프로젝트 하면서 별의 별 에피소드를 다 겪는 것 같은데

내일은 또 무슨 에피소드가 기다리고 있을지 기다...려...지네요...😂

반응형