이걸 심화라고 할 수 있을까 싶긴 하지만... 일단 내가 구현해본 코드...
JSON타입 지원 RDB사용 전제로 구현 시 3가지 조건
- 데이터 변경 이력 데이터는 변경전 & 변경후 값이 함께 쌓임
- asis데이터는 DB에서 조회, tobe데이터는 client에서 body에 넣어서 보내줌.
- 전달받은 tobe데이터와 asis 데이터를 비교하여 변경이 있는 필드값만 모아 HistoryDetail형태로 DB 저장
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);
}
}

Customer의 String(고객 이름)과 int(고객 나이) 필드를 비교하여 값이 다른 경우에만 HistoryDetail changes에 담게 되어 있는데,
2개의 TEST데이터가 (asis/tobe Customer 객체) 모두 변경되어 2가지 값이 모두 HistroyDetail에 담겼음.
이름: '일부엉' -> '이부엉' / 나이: 40-> 30
이후에 이력데이터를 보여줄 시 데이터 역직렬화가 필요한데, 이 경우 "type"에 넣어둔 "STRING" , "NUMBER" 등 을 이용해서 역직렬화가 가능하다.