프로그래밍 노트/JPA

[JPA] 제네릭(generic)한 컨버터(converter) 만들기. Generic Json Converter

깡냉쓰 2019. 10. 10. 00:19
728x90
반응형

Table에 추가적인 데이터를 Json 문자열로 저장하기 위해, JPA Converter를 이용하려고한다.
즉, 서비스단에서는 bean으로 사용을하고, DB에 저장될 때는 이 bean을 Json 문자열로 자동 파싱하여 저장하기 위해서이다.
일단 샘플로 Student 클래스를 만들자

@Entity
@Getter
@Setter
public class Student {
    @Id
    private String key;
    private String name;
    private int age;

    private AdditionalData additionalData;
}

Student는 추가데이터(additionalData)를 갖고 있으며, 이 데이터는 additionalData라는 컬럼에 Json 형태로 들어갈 것이다.

@Getter
@Setter
public class AdditionalData {
    private Double height;
    private Double weight;
    private String hobby;
}

AdditionalData는 위와같이 구성되어 있으며, 우리는 아무 생각없이 AdditionalData를 Student 객체에 넣어주기만하면 자동으로 Json형태로 변환되서 DB에 저장되길 원한다.

그러려면 컨버터가 필요하다. JPA Converter는 AttributeConverter라는 인터페이스를 구현함으로써 만들 수 있다.
AdditionalDataConveter를 만들어보자. (Json변환은 Jackson라이브러리를 사용한다.)
⇒ ObjectMapper는 thread-safe하다고 하여 static으로 선언해두고 사용해도됨

@Slf4j
public class AdditionalDataConverter implements AttributeConverter<AdditionalData, String> {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public String convertToDatabaseColumn(AdditionalData additionalData) {
        // AdditionalData -> Json문자열로 변환
        try {
            return objectMapper.writeValueAsString(additionalData);
        } catch (JsonProcessingException e) {
            log.error("fail to serialize as object into Json : {}", additionalData, e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public AdditionalData convertToEntityAttribute(String jsonStr) {
        // Json 문자열 -> AdditionalData로 변환
        try {
            return objectMapper.readValue(jsonStr, AdditionalData.class);
        } catch (IOException e) {
            log.error("fail to deserialize as Json into Object : {}", jsonStr, e);
            throw new RuntimeException(e);
        }
    }
}

Student에 컨버터 설정

@Entity
@Getter
@Setter
public class Student {
    @Id
    private String key;
    private String name;
    private int age;

    @Convert(converter = AdditionalDataConverter.class)
    private AdditionalData additionalData;
}

잘들어가는 것일까...? 테스트

@Component
public class StudentRunner implements ApplicationRunner {

    @PersistenceContext
    EntityManager em;

    @Override
    @Transactional
    public void run(ApplicationArguments args) throws Exception {
        AdditionalData additionalData = new AdditionalData();
        additionalData.setHeight(180d);
        additionalData.setWeight(70d);
        additionalData.setHobby("음악감상");

        Student student = new Student();
        student.setKey("1");
        student.setAge(20);
        student.setName("corn");
        student.setAdditionalData(additionalData);

        em.persist(student);
    }
}

Json으로 잘 들어가는 것 확인!!

성공

그런데 사용하다보니, 생각보다 많은 서비스들이 추가적인데이터를 Json문자열로 저장하는 경우가 많았다. 그때마다 우리는 각기다른 additionalData에 해당하는 Converter를 만들어줘야했다. 예를들면 BookAdditionalData, CustomerAdditionalData 에 해당하는 BookAdditionalDataConverter, CustomerAdditionalDataConverter를 말이다. 코드는 대부분 중복이기 때문에 비효율적으로 느껴져 제네릭한 컨버터가 필요했다.
제네릭한 컨버터를 만들어보자

@Slf4j
public class GenericJsonConverter<T> implements AttributeConverter<T, String> {

    private final ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public String convertToDatabaseColumn(T additionalData) {
        // AdditionalData -> Json문자열로 변환
        try {
            return objectMapper.writeValueAsString(additionalData);
        } catch (JsonProcessingException e) {
            log.error("fail to serialize as object into Json : {}", additionalData, e);
            throw new RuntimeException(e);
        }
    }

    @Override
    public T convertToEntityAttribute(String jsonStr) {
        // Json 문자열 -> AdditionalData로 변환
        try {
            return objectMapper.readValue(jsonStr, new TypeReference<T>(){});
        } catch (IOException e) {
            log.error("fail to deserialize as Json into Object : {}", jsonStr, e);
            throw new RuntimeException(e);
        }
    }
}

GenericJsonConverter를 상속받은 AdditionDataConverter2 생성

public class AdditionDataConverter2 extends GenericJsonConverter<AdditionalData> {
}

이미 부모 객체에서 모두 구현되어 있기 때문에 위처럼만 선언해도 바로 사용가능하다.
AdditionDataConverter2로 컨버터 변경

@Entity
@Getter
@Setter
public class Student {
    @Id
    private String key;
    private String name;
    private int age;

    @Convert(converter = AdditionDataConverter2.class)
    private AdditionalData additionalData;
}

테스트결과 잘 동작하는 것을 확인했다.
이 또한 Converter를 additionalData수 만큼 만들어야 하지만, 중복되는 안의 로직들을 복붙안해도 된다는 장점이 있다.
음.. 제 머리로는 이게 한계인데.. 만약 더 좋은 방법이 있다면 추천을 해주세요~!

728x90
반응형