@JsonTypeInfo (+@JsonSubTypes) 2편 (심화)

주야·2024년 4월 5일

이걸 심화라고 할 수 있을까 싶긴 하지만... 일단 내가 구현해본 코드...

JSON타입 지원 RDB사용 전제로 구현 시 3가지 조건

  1. 데이터 변경 이력 데이터는 변경전 & 변경후 값이 함께 쌓임
  2. asis데이터는 DB에서 조회, tobe데이터는 client에서 body에 넣어서 보내줌.
  3. 전달받은 tobe데이터와 asis 데이터를 비교하여 변경이 있는 필드값만 모아 HistoryDetail형태로 DB 저장


@JsonTypeInfo 활용한 코드 예제

1. SpringBoot에서 아래 2개의 어노테이션을 사용하기 위해서는 아래와 같은 의존성 주입이 필요하다.

//build.gradle에 아래 의존성 추가
implementation 'com.fasterxml.jackson.core:jackson-databind:2.14.1'
implementation 'com.fasterxml.jackson.core:jackson-core:2.14.1'

Jackson 의존성 관련 에러들은 아래 포스팅에 따로 모아뒀음.
Jackson 에러 케이스 보러가기!

2. 프로젝트 구조

customer package 
ㄴCustomer.class
ㄴHistoryDto.class
ㄴCaptured.class
ㄴStringCaptured.class
ㄴNumberCaptured.class
ㄴCustomerController.class

3. 구현 코드

// Customer 클래스 
@Data
public class Customer {

    private int id;
    private String name;
    private int age;

}
// HistoryDto 클래스
@Data
public class HistoryDto {
    private int index;
    private List<Captured> changes = new ArrayList<>();

    public void addIfChanged(Captured change) {

        if(change.changed()) {
            changes.add(change);
        }
    }
}
//Captured 인터페이스 클래스 
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME,
        property = "type"
)
@JsonSubTypes({
        @JsonSubTypes.Type(value = StringCaptured.class, name="STRING"),
        @JsonSubTypes.Type(value = NumberCaptured.class, name="NUMBER")
        })
public interface Captured {

    boolean changed(); 

}

@JsonTypeInfo & @JsonSubTypes
데이터를 직렬화 & 역직렬화 시 데이터 타입을 명시해주는 역할.

//StringCaptured
import org.apache.commons.lang3.StringUtils;

@Getter
@EqualsAndHashCode
public class StringCaptured implements Captured{
    private final String name;
    private final String oldValue;
    private final String newValue;

    @Builder
    @ConstructorProperties({"name", "oldValue", "newValue"})
    public StringCaptured(String name, String oldValue, String newValue) {
        this.name = name;
        this.oldValue = oldValue;
        this.newValue = newValue;
    }

    @Override
    public boolean changed() {
        return !StringUtils.equalsIgnoreCase(oldValue, newValue);
    }

}

String 타입의 데이터 변경 전,후 값을 비교하는 객체.

//NumberCaptured
@Getter
public class NumberCaptured implements Captured{
    private final String name;
    private final int oldValue;
    private final int newValue;

    @Builder
    @ConstructorProperties({"name", "oldValue", "newValue"})
    public NumberCaptured (String name, int oldValue, int newValue) {
        this.name = name;
        this.oldValue = oldValue;
        this.newValue = newValue;
    }

    @Override
    public boolean changed() {
        return oldValue != newValue;
    }

}

int 타입의 데이터 변경 전,후 값을 비교하는 객체.

//CustomerController
@RestController
@RequestMapping("/customer")
public class CustomerController {

    ObjectMapper om = new ObjectMapper();

    @PostMapping("update")
    public void updateCustomerInformation () throws JsonProcessingException {
        //oldValue 객체 1 (원래 DB조회로 데이터를 가져와야하지만 그냥 test객체 만들어 사용)
        Customer asIs = new Customer();
        asIs.setId(100001);
        asIs.setName("일부엉");
        asIs.setAge(40);
        //newValue 객체 2 (원래 client 쪽에서 parameter로 넘겨받아야하지만 그냥 test객체 만들어 사용)
        Customer toBe = new Customer();
        toBe.setId(asIs.getId());
        toBe.setName("이부엉");
        toBe.setAge(30);

        HistoryDto hd = new HistoryDto(); 

        StringCaptured stringCaptured = new StringCaptured("성명", asIs.getName(), toBe.getName());
        NumberCaptured numberCaptured = new NumberCaptured("나이", asIs.getAge(), toBe.getAge());


        hd.addIfChanged(stringCaptured);
        hd.addIfChanged(numberCaptured);

        hd.setIndex(asIs.getId());
        String result  = om.writeValueAsString(hd);
        System.out.println(result);
    }

}

DB에 저장되는 데이터 형태

Customer의 String(고객 이름)과 int(고객 나이) 필드를 비교하여 값이 다른 경우에만 HistoryDetail changes에 담게 되어 있는데,

2개의 TEST데이터가 (asis/tobe Customer 객체) 모두 변경되어 2가지 값이 모두 HistroyDetail에 담겼음.
이름: '일부엉' -> '이부엉' / 나이: 40-> 30



이후에 이력데이터를 보여줄 시 데이터 역직렬화가 필요한데, 이 경우 "type"에 넣어둔 "STRING" , "NUMBER" 등 을 이용해서 역직렬화가 가능하다.

profile
개발자

0개의 댓글