Enum과 AttributeConverter

태히·2022년 7월 18일
1

SpringBoot/JPA

목록 보기
2/3
post-thumbnail

이 글의 배경

JPA를 사용하면서 서버정보를 담고있는 Server Entity의 필드중 하나인 type을 enum타입으로 사용하려고 했다.

위는 ServerType이라는 enum클래스이다. codedesc 두 개의 필드를 가지고있고, 현재 DB의 컬럼에는 code가 저장되어 있다. 따라서 DB에 INSERT 할 때는 code를 사용하고, SELECT 할 때는 해당 code를 가지고있는 enum객체를 담아주길 원했다.

  • INSERT 할 때 : Server테이블 - type컬럼 = P
  • SELECT 할 때 : Server.type == ServerType.PRODUCT;

이대로 Server Entity에 enum ServerType을 컬럼으로 정의하고 실행해 보았다.


발생 문제

특정 조건 없이 findOne을 했을 때 DB에서 가져오는 ServerType의 값이 Entity와 매핑이 되지 않았다.

org.springframework.dao.InvalidDataAccessApiUsageException: No enum constant com.spectra.store.jpo.enums.ServerType.P;
nested exception is java.lang.IllegalArgumentException: No enum constant com.spectra.store.jpo.enums.ServerType.P
  • DB에서 값을 가져와 Entity로 변환 할 때 기본적으로 enum클래스의 필드 이름인 ServerType.P로 매핑하려 하는데 해당 enum은 ServerType.PRODUCT 이기 때문에 발생하는 에러인 것 같다.

AttributeConverter<X,Y>

원하는 값을 매핑하기 위해서는 ServerType.PRODUCTcode에 매핑을 시켜줘야 한다.
이를 간단하게 해결해 주는 AttributeConverter가 존재한다!!

AttributeConverter는 인터페이스로 2개의 메서드가 선언되어있다. 이를 구현하는 자식 클래스를 만들어 재정의 하면 된다.

  • Y convertToDatabaseColum(X var1) : X타입을 받아 Y(DB컬럼)타입으로 리턴
  • X convertToEntityAttribute(Y var1) : Y타입을 받아 X(Entity)타입으로 리턴

AttributeConverter를 상속하는 ServerTypeConverter를 만들었고 메서드들을 재정의했다. DB로 저장하기 위해 해당 ServerType.getCode()를 하여 code를 DB에 저장하고, Entity로 변환하기 위해 ServerType(enum)에 ofCode(String code) static 메서드를 만들었다.

그리고 Entity의 ServerType필드에는 @Convert,
ServerTypeConverter에는 @Converter Annotaion을 붙여주면 끝이다!!


해치웠나...? 아직 개선 필요!!

이대로 간단하게 끝나면 정말 좋겠지만... 생각을 조금 더 해보면 Enum클래스 마다 ofCode() 메서드와 Converter를 만들어 줘야한다. 필요한 enum클래스가 100개라면 Converter도 100개가 만들어져야한다!!

"불필요하고 반복되는 작업은 개발자를 힘들게해요!!"

추상화가 필요해

현재 반복되는 작업인 AttributeConverter 클래스의 메소드 재정의 부분과 enum의 ofCode() 메서드를 추상화 하여 자식클래스에서 간편하게 사용하도록 만들 것이다.

interface EnumFild 와 ServerType enum 클래스

우선 EnumField 인터페이스를 만들어 getter를 강제하고 enum클래스들이 이를 상속하도록 한다. 이는 밑의 추상화 Converter 클래스에서 다형성을 활용하기 위함이다.

추상화 클래스 AbstractEnumCodeAttributeConverter

AbstractEnumCodeAttributeConverter 클래스는 이전에 각각의 enum에서 재정의하던 기능들을 추상화 시킨 클래스이다.
반환타입을 보면 <T extends Enum & EnumField>인데 이 의미는 Enum과 EnumField를 동시에 상속받고 구현하는 타입이라는 뜻이다. 결국 ServerType이 반환타입이 되겠다.
이렇게 불특정한 타입이지만 어느정도의 경계는 정해주고 추론 할 수 있도록 해주는 것이 "경계가 있는 타입 파라미터(Bounded Type Parameter)"라고 한다.

ServerTypeConverter 클래스

ServerTypeConverter클래스는 부모클래스인 AbstractEnumCodeAttributeConverter클래스에 자기 자신을 넘겨 타입을 정의한다.

EnumValueUtils 클래스

마지막으로 AbstractEnumCodeAttributeConverter클래스에서 호출하는 EnumBalueUtils 클래스이다. 이는 Util클래스로 final클래스로 정의하고 메서드를 static으로 선언한다.
메서들의 기능을 하나씩 보겠다.

  • toDBCode(T db) : 파라미터로 ServerType이 들어오면 해당 필드의 code를 반환해주고, 파라미터가 null로 들어오면 빈문자열""을 반환한다.
  • toEntityCode(Class enumClass, String dbCode) : 파라미터로 ServerType과 code가 넘어오면 ServerType을 EnumSet으로 만들어 stream으로 순회하면서 파라미터 code와 맞는 필드를 찾아 반환한다. 역시 파라미터로 들어온 code가 비어있으면 null을 반환한다.

흐름 정리

  1. Repository에서 INSERT 혹은 SELECT를 하기위해 쿼리 실행.
  2. Entity에 @Convert 어노테이션이 있는 필드가 있으면 해당 Converter 호출. -> ServerTypeConverter클래스
  3. AttributeConverter클래스를 구현하는 AbstractEnumCodeAttributeConverter를 호출.
  4. Entity -> DB컬럼 인 경우 : convertToDatabaseColumn() 실행
    DB컬럼 -> Entity 인 경우 : convertToEntityAttribute() 실행
  5. EnumValueUtils클래스의 Bounded Type Parameter로 되어있는 static 메서드 실행.

마치며....

처음에 무지성으로 찾아보며 개발하다가 이렇게 글로 정리하려니 꽤나 힘든 일이다. 하지만 이렇게 정리하니 Converter가 어떤 동작을 하는지 조금은 이해가 가고, 사실 Converter도 중요하지만 Bounded Type Parameter라는 엄청난 수확이 있었던 것 같다.

여기까지 이 글을 읽어주신 분들께 정말 감사하고 피드백 환영입니다!! 글 쓰는 연습좀 더 해야겠습니다...


참조

https://techblog.woowahan.com/2600/ -> AttributeConverter
https://thecodinglog.github.io/java/2020/12/09/java-generic-class.html -> Bounded Type Parameter

profile
하고싶은게 많은 개발자가 되고싶은

0개의 댓글