실무를 하다보면, 비슷한 데이터를 가진 두 객체를 병합해야 할 경우가 있다.
DB에서 값을 가져와서 화면출력을 해야하는데, DB A에서 조회되는 컬럼과, DB B에서 조회되는 컬럼이 달라서 비지니스 로직 병합하여 보야줘야 하는 경우..
Jackson 라이브러리를 사용할까 라고 생각했다가 필드값을 다른 객체값으로 덮는게 아니라 특정 조건에 부합되는 경우가 필요했다.
A에서 조회된 컬럼값이 없어서 Null로 부여된다면, B의 값이 대체한다. 즉, A값이 있다면 B의값으로 대체하지 않는다.
해당 문제를 해결하기 위해 적용했던 2가지 방식에 대해 알아보자.
원하는 시나리오는 다음과 같다
1. objectA에는 필드 A,B,C가 존재한다. 하지만 C의 값은 Null이다.
public static void main(String[] args) {
ObjectA objectA = ObjectA.builder().FieldA("A").FieldB("B").build();
ObjectB objectB = ObjectB.builder().FieldA("A").FieldB("B-1").FieldC("C").FieldD("D").build();
System.out.println(objectA);
System.out.println(objectB);
//FunctionalInterfaceTest.ObjectA(FieldA=A, FieldB=B, FieldC=null)
//FunctionalInterfaceTest.ObjectB(FieldA=A, FieldB=B-1, FieldC=C, FieldD=D)
// Interface mergeObjectsInterface(List.of(objectA), ObjectA::getFieldA, List.of(objectB), ObjectB::getFieldA);
System.out.println(objectA);
mergeObjectsFunction(List.of(objectA), ObjectA::getFieldA, List.of(objectB), ObjectB::getFieldA);
System.out.println(objectA);
//FunctionalInterfaceTest.ObjectA(FieldA=A, FieldB=B, FieldC=C)
//FunctionalInterfaceTest.ObjectA(FieldA=A, FieldB=B, FieldC=C)}
@Getter @Setter @Builder @ToString
public static class ObjectA {
private String FieldA;
private String FieldB;
private String FieldC;
}
@Getter @Setter @Builder @ToString
public static class ObjectB {
private String FieldA;
private String FieldB;
private String FieldC;
private String FieldD;
}
ObjectA는 FieldA, FieldB에 값이 할당되고 FieldC는 NullObjectB는 FieldA, FieldB, FieldC에 값이 할당ObjectA와 ObjectB는 FieldA의 키값을 가지며 매칭이 진행ObjectA의 FieldC는 값이 Null이고 ObjectB의 FieldC는 Null이 아니므로 ObjectA-FieldC의 값은 UpdateObjectB의 FieldB이 Null이 아니지만 ObjectA의 FieldB가 Null이 아니므로 처리 대상이 아님ObjectB의 FieldD는 ObjectA에 있는 필드가 아니므로 대상이 아님private static <T, U> void mergeObjects(T objectA, U objectB) {
// A 클래스의 모든 필드에 대해 반복
for (Field fieldA : objectA.getClass().getDeclaredFields()) {
try {
// 필드의 이름 가져오기
String fieldName = fieldA.getName();
// B 클래스에서 동일한 이름의 필드 찾기
Field fieldB = null;
for (Field declaredField : objectB.getClass().getDeclaredFields()) {
if (declaredField.getName().equals(fieldName)) {
fieldB = declaredField;
break;
}
}
// 필드가 존재하는 경우에만 처리
if (fieldB != null) {
// 필드의 값을 가져오고 설정
fieldA.setAccessible(true);
fieldB.setAccessible(true);
// A 객체의 값이 null이면 B 객체의 값을 사용
if (fieldA.get(objectA) == null) {
fieldA.set(objectA, fieldB.get(objectB));
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
objectA의 클래스에서 모든 선언된 필드에 대해 반복objectA 각 필드의 이름을 가져와서 objectB의 클래스에서 동일한 이름의 필드를 조회break, 루프를 종료setAccessible(true)), fieldA의 값이 null인 경우에만 fieldB의 값을 fieldA에 설정fieldA의 값이 이미 존재한다면 변경하지 않고 Pass 처리List<A> - 완성 되는 객체 리스트Method reference A - 완성 되는 함수 참조List<B> - 추가 되는 객체 리스트Method reference B - 추가 되는 함수 참조interface FieldExtractor<T> {
String extract(T object);
}
private static <T, U> void mergeObjectsInterface(
List<T> objectAList, FieldExtractor<T> extractorA,
List<U> objectBList, FieldExtractor<U> extractorB
) {
for (T objectA : objectAList) {
String valueA = extractorA.extract(objectA);
U objectB = objectBList.stream()
.filter(x -> extractorB.extract(x).equals(valueA))
.findAny().orElse(null);
if (Objects.nonNull(objectB)) {
// Assuming mergeObjects method exists
mergeObjects(objectA, objectB);
}
}
}
FieldExtractor<T>로 대상 필드로 하는 FieldExtractor 구현체 정의를 파라미터로 전달objectAList의 각 객체에 대해 반복FieldExtractor.extract()를 사용하여 각 객체에서 특정 필드 값을 추출objectB를 objectBList에서 조회mergeObjects 메서드를 호출하여 두 객체를 병합Case 1. 의 방법도 인터페이스고, 해당 방법도 Functional Interface니 구조는 결국 같다.
private static <T, U> void mergeObjectsFunction(
List<T> objectAList, Function<T, String> extractorA,
List<U> objectBList, Function<U, String> extractorB
) {
for (T objectA : objectAList) {
String valueA = extractorA.apply(objectA);
U objectB = objectBList.stream()
.filter(x -> extractorB.apply(x).equals(valueA))
.findAny().orElse(null);
if (Objects.nonNull(objectB)) {
// Assuming mergeObjects method exists
mergeObjects(objectA, objectB);
}
}
}
Function<T, String>로 대상 필드로 하는 Functional Interface 구현체 정의를 파라미터로 전달objectAList의 각 객체에 대해 반복Function.apply()를 사용하여 각 객체에서 특정 필드 값을 추출objectB를 objectBList에서 조회mergeObjects 메서드를 호출하여 두 객체를 병합