1. 문제 발생
- 신규 서버 프로젝트의 구조 및 개발 방향 구상 중, DB model object(이하 DTO)와 Service model object(VO)을 따로 구현 및 관리하기로 결정이 되었음
- 기존 운영 프로젝트에선 DTO의 구조가 그대로 Client까지 전달이 되었지만, 신규 구성에선 그런 동작을 방지하기로 결정
- 하지만 클라이언트로 전달되지 말아야 하는 데이터 이외의 대부분의 값들은 Client에게 전달이 되어야 하는 상황
2. 방안 1
- DTO로 받은 각 값들을 일일이 소스 코드에서 변환 처리 해준다
- 개인적으로는 이게 취향
- 성능적으로도 솔직히 이것만큼 예측이 쉽고 문제 원인이 될 것도 없음
- 하지만 Human error의 위험 및 엄청난 귀차니즘, 장기간의 소스 코드 관리의 문제가 발생할 수 있음
- 나중에 DB table 구조에 수정 및 추가가 생기면 다 수정해야 하는 시간 비용도 두려움
3. 방안 2 (진행)
- DTO와 VO간의 자동 변환 기능을 만들자
- 솔직히 아마 검색해보면 분명히 있을거지만, 요즘 개인 개발이 좀 느슨해진김에 해볼까 함
- 개념상으로만 알고 있던 Reflection 코딩 적용하면, 약간의 규칙만 정해주면 가능할 것 같았음
- 결과가 괜찮으면 검토 건의 해보지 뭐 ㅋ
4. 구성 및 기초 설계
4-1. 규칙 1
DTO와 VO간의 동일한 타입 및 이름이어야 한다.
"DB에서 int로 쓰고있지만 클라이언트는 String으로 받아서 쓸 것이다"라는 규칙으로 개발을 해봤지만, 좋은 꼴을 본 적이 없다.
4-2. 규칙 2
규칙 1을 기반으로, 각 객체의 field는 Java에서 제안하는 getter/setter 규격을 따라야 한다. 이 네이밍 규칙을 기준으로 데이터 복사 작업을 진행할 것이다. 또한 최근 자바 프로젝트는 다 이 규격을 사용하는 lombok 라이브러리를 통해 관리하기 때문에 이렇게 판단했다.
5. 구현
package reflection;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
public class ConverterUtil{
public static <T_DTO, T_VO> void convertObject(T_DTO objectDTO, T_VO objectVO) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 1. 공통 method 조회(Object Class method 제외 처리에 사용)
HashSet<String> objectMethodSet = new HashSet<>();
{
Object testObj = new Object();
Method[] objectMethods = testObj.getClass().getMethods();
for(int i = 0 ; i < objectMethods.length ; i ++){
objectMethodSet.add(objectMethods[i].getName());
}
}
// 2. DTO getter method 정리
HashSet<String> DTO_getterSet = new HashSet<>();
{
Method[] methods = objectDTO.getClass().getMethods();
for(int i = 0 ; i < methods.length ; i ++){
if(!objectMethodSet.contains(methods[i].getName())){
if(methods[i].getName().substring(0,3).equals("get")){
DTO_getterSet.add(methods[i].getName());
}
}
}
}
{
// 3. VO setter method
HashMap<String, String> VO_getter_Map = new HashMap<>();
HashMap<String, String> VO_setter_Map = new HashMap<>();
Method[] methods = objectVO.getClass().getMethods();
for (int i = 0; i < methods.length; i++) {
if (!objectMethodSet.contains(methods[i].getName())) {
if (methods[i].getName().substring(0, 3).equals("get")) {
VO_getter_Map.put(methods[i].getName().substring(3, methods[i].getName().length()), methods[i].getName());
}
if (methods[i].getName().substring(0, 3).equals("set")) {
VO_setter_Map.put(methods[i].getName().substring(3, methods[i].getName().length()), methods[i].getName());
}
}
}
// 4. 복사 작업 진행
Iterator<String> getterKeyItr = DTO_getterSet.iterator();
while (getterKeyItr.hasNext()) {
String getKey = getterKeyItr.next();
String fieldName = getKey.substring(3, getKey.length());
if (VO_getter_Map.containsKey(fieldName)) {
Method testVOGetMethod = objectVO.getClass().getMethod(getKey);
Class dtoClass = objectDTO.getClass().getMethod(getKey).invoke(objectDTO).getClass();
Class objToPriClass = null;
if (dtoClass == Integer.class) {
objToPriClass = int.class;
} else if (dtoClass == Double.class) {
objToPriClass = double.class;
} else if (dtoClass == Float.class) {
objToPriClass = float.class;
} else if (dtoClass == Character.class) {
objToPriClass = char.class;
} else if (dtoClass == Long.class) {
objToPriClass = long.class;
} else if (dtoClass == Byte.class) {
objToPriClass = byte.class;
} else if (dtoClass == Boolean.class) {
objToPriClass = boolean.class;
} else if (dtoClass == Short.class) {
objToPriClass = short.class;
} else {
objToPriClass = objectDTO.getClass().getMethod(getKey).invoke(objectDTO).getClass();
}
Method testVOSetMethod = objectVO.getClass().getMethod(VO_setter_Map.get(fieldName), objToPriClass);
testVOSetMethod.setAccessible(true);
testVOSetMethod.invoke(
objectVO,
objectDTO.getClass().getMethod(getKey).invoke(objectDTO)
);
}
}
}
}
}
6. 테스트 진행
프로젝트의 testMain.java 실행하면 된다.
jdk 1.8 기준으로 작성했다.
그냥 객체 두 개 두고 복사되는지만 봤다. 안보는게 낫다
7. 결론 및 후기
어차피 공부 겸 재미용으로 만든 코드이기 때문에 테스트를 여러가지 케이스에서 막 굴리지는 않았다.
무조건 이름이 같은 경우에만 복사하고 아님 말고의 방식으로 개발되어, 규칙을 지켜주며 객체 생성을 하지 않을거라면 못쓰는 코드에 가깝다.
무엇보다 이거 괜찮은거 맞나? 싶다. 모든 서버 API 반환부에서 저 함수가 실행된다고 생각하면, 매번 method를 순회하고 타입 비교를 해가면서 변환 시켜준다는 것이 cpu 성능을 필요 이상으로 사용하는 건 아닌가 하는 걱정이 든다. 솔직히 개발 중간에 이럴거면 뭐하러 만드나 하는 생각까지 했지만, 그래 어차피 연습용이니까 하고 진행했다.
까놓고 옛날에 xml 설정으로 각 객체간 mapping 등록을 해서 복사해주는 방식도 본적이 있긴한데 걔나 얘나 다 별로같다.
* 귀찮아서 그냥 진행하긴 했는데 여기서 말하는 DTO가 Entity 객체고, VO가 DTO다. 시작할 때 네이밍을 잘못 정했다.
* 빡치게 진짜 있네
https://www.baeldung.com/entity-to-and-from-dto-for-a-java-spring-application
근데 이 라이브러리도 내부를 까본건 아니지만 parameter가 객체와 class type을 받아가는 것 보면, 결국 방향성은 내 코드와 비슷해보인다. 물론 예외처리나 최적화는 더 잘했겠지만!
'공부 - 언어, 프레임워크 > java' 카테고리의 다른 글
[JAVA] Finalize 정리 (0) | 2022.04.15 |
---|---|
[JAVA] List 객체 정리 (0) | 2022.04.15 |
[JAVA] HashMap Loop 실행 시간 비교해보기 feat. Iterator (0) | 2020.04.09 |
[JUnit] 단위 테스트 (0) | 2020.03.29 |
[JAVA] 가장 많이 사용하는 인터페이스 : Map(2) (0) | 2019.12.28 |