이거 제목을 뭐라해야 할 지 모르겠어서 그냥 길게 적어버렸다.
우선 상황을 설명하자면, ClosetItemEntity 코드 내 업데이트에 관한 생성자 구조에서 param 값이 하나라도 빠질 경우 작동하지 않았다.
하지만, 옷장 아이템에 대해서 데이터 구조를 설정했을 때 필수 or 선택 항목이 있었고, 선택 항목은 작성하지 않아도 기본 값으로 적용되어 작성되어야 한다.
그러다 보니 값을 받을 때, 하나하나 작성해서 생성하기에는 코드가 너무 길어졌다.
그래서 빌더 패턴으로 생성, 업데이트 둘 다 가능한 메서드로 작성했는데,
매번 같은 순서나 타입으로 받는게 아니라서 에러가 발생하다 보니 조금 복잡하게 수정을 했다.
변경 전 코드는 아래와 같다.
public ClosetItemEntity updateEntity(Map<String, String> itemParams,
ClosetEntity closetId) {
this.modifiedAt = LocalDateTime.now(ZoneId.of("Asia/Seoul"));
this.brand = itemParams.get("brand");
this.category = itemParams.get("category");
this.itemImageUrl = itemParams.get("itemImageUrl");
this.itemName = itemParams.get("itemName");
this.price = Long.valueOf(itemParams.get("price"));
this.description = itemParams.get("description");
this.closetId = closetId;
return this;
}
수정된 코드 전문은 아래와 같다.
@Builder
public ClosetItemEntity updateEntity(Map<String, String> itemParams,
ClosetEntity closetId) {
this.modifiedAt = LocalDateTime.now(ZoneId.of("Asia/Seoul"));
// 필수 및 선택 항목 변경
this.brand = getValueOrDefault(itemParams, "brand", this.brand);
this.category = getValueOrDefault(itemParams, "category", this.category);
this.itemImageUrl = getValueOrDefault(itemParams, "itemImageUrl", this.itemImageUrl);
this.itemName = getValueOrDefault(itemParams, "itemName", this.itemName);
this.price = getValueOrDefault(itemParams, "price", this.price);
this.description = getValueOrDefault(itemParams, "description", this.description);
this.closetId = closetId;
return this;
}
@SuppressWarnings("unchecked")
private static <T> T getValueOrDefault(Map<String, String> itemParams,
String key,
T defaultValue) {
if (itemParams.containsKey(key)) {
Object value = itemParams.get(key);
if (defaultValue instanceof Long) return (T) Long.valueOf(value.toString());
else return (T) value;
} else {
return defaultValue;
}
}
우선 Setter, Getter 생성자 구조에서 빌더 패턴으로 변경되었다.
기존의 값을 변경하고 싶지 않다면(혹은 선택사항이라서) 작성하지 않고 값을 넘길텐데, 이 경우에 에러가 발생한 것.
그래서 처음에는 그 부분을 삼항연산자로 처리하려고 위의 updateEntity가 작성되었는데 이게 또 타입 구분을 못해서
아래 getValueOrDefault가 작성되었다.
당시 주석이랑 같이 보면 아래와 같다.
// T 타입 체크에서 경고 발생
// -> Front에서 타입을 제대로 받는다면 문제 없을 것으로 예상
// 그래서 경고 무시 작성
@SuppressWarnings("unchecked")
private static <T> T getValueOrDefault(Map<String, String> itemParams,
String key,
T defaultValue) {
// itemParams에 key가 존재하는지?
if (itemParams.containsKey(key)) {
// Object 타입 -> defaultValue(기존에 있던 값) 타입으로 변환
Object value = itemParams.get(key);
// defaultValue is Long type? -> Long 타입 반환
// -> form-data를 String으로 받아오기 때문에 이런 과정이 필요한 것
if (defaultValue instanceof Long) return (T) Long.valueOf(value.toString());
else return (T) value;
} else {
// form-data parameter 없으면 기존의 값으로 대체
return defaultValue;
}
}
우선 @SuppressWarnings의 경우 unchecked exception이 발생했다.
내용을 확인해보니까 메서드 내 제네릭 타입을 사용하면서 형 변환을 하는데, 그래서 안전성 보장 경고를 하는 것이었다.
근데 프론트엔드에서 타입을 제대로 받으면 상관 없을 것 같아서 경고를 무시했다.(결과도 문제 없었음)
메서드 자체 동작 순서는 아래와 같다.
1. 먼저, itemParams 맵에 해당 키(key)가 존재하는지 확인
2. 키가 존재하면 해당 키에 대한 값을 가져오고, 이 값을 Object 타입으로 받음
3. 그 다음, 기본값(defaultValue)의 타입이 Long인지 확인
만약 defaultValue가 Long 타입이라면, 가져온 값을 문자열로 변환한 후 Long.valueOf()를 사용하여 Long 타입으로 변환
4. 키가 존재하지 않으면 기본값 반환
예시를 들자면 form-data로 받아온 Key-Value가 'brand - 브랜드1' 라고 하자.
updateEntity에서 param 값으로 (itemParams, "brand", this.brand)를 받는다.
그러면 getValueOrDefault에선 ('brand - 브랜드1', "brand", String(기존 brand의 타입))를 받고 시작한다.
itemParams의 Key에 해당하는 brand가 존재하니까, 값인 '브랜드1'을 Object 타입으로 받는다.
그리고 나서 기존에 있던 타입이 Long인지 String인지 파악한 후 Long이면 Long 타입으로,
아니면 원래 defaultValue(brand)의 타입인 String으로 반환한다.
여기선 String 타입이니까 브랜드1을 반환한다.
만약 이 모든 경우가 아닌(변경 사항이 없을 경우) 상황에선 원래 가지고 있던 값을 그대로 반환한다.
뭐 이런 과정을 거친다고 생각하면 된다.
수정을 거치면서 이런 코드가 되었는데 제네릭 타입이라던지 사용 안해본 어노테이션이라던지 사용할 수 있는 기회여서 좋았던 것 같다.
+ 코드블럭으로 작성했는데, 내가 보는 창 크기로는 한 눈에 못봐서 코드 블럭도 수정하고 전문도 캡쳐해왔다.