Java - 두 객체간 키값으로 병합하기

JunMyung Lee·2023년 11월 15일
0

자바

목록 보기
5/8
post-custom-banner

실무를 하다보면, 비슷한 데이터를 가진 두 객체를 병합해야 할 경우가 있다.

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;  
}
  • ObjectAFieldA, FieldB에 값이 할당되고 FieldCNull
  • ObjectBFieldA, FieldB, FieldC에 값이 할당
  • ObjectAObjectBFieldA의 키값을 가지며 매칭이 진행
    ObjectAFieldC는 값이 Null이고 ObjectBFieldCNull이 아니므로 ObjectA-FieldC의 값은 Update
    ObjectBFieldBNull이 아니지만 ObjectAFieldBNull이 아니므로 처리 대상이 아님
  • ObjectBFieldDObjectA에 있는 필드가 아니므로 대상이 아님

공통 적용 코드 - 객체간 필드 병합 처리

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 - 추가 되는 함수 참조

Case 1. Interface

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()를 사용하여 각 객체에서 특정 필드 값을 추출
  • 추출한 값과 일치하는 객체 objectBobjectBList에서 조회
  • mergeObjects 메서드를 호출하여 두 객체를 병합

Case 2. Function

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()를 사용하여 각 객체에서 특정 필드 값을 추출
  • 추출한 값과 일치하는 객체 objectBobjectBList에서 조회
  • mergeObjects 메서드를 호출하여 두 객체를 병합
profile
11년차 검색개발자 입니다. 여러 지식과 함께 실제 서비스를 운영 하면서 발생한 이슈에 대해서 정리하고 공유하고자 합니다.
post-custom-banner

0개의 댓글