JPA Entity 클래스에 Enum 타입 사용기 @Enumerated, AttributeConverter 활용(기본)

예전에 프로젝트를 했을 때, JPA에 String 클래스 타입으로 enum의 값을 넣었던것 같다.

 

코드로 예를 들면,

 

// JPA Entity 클래스
...
@Entity
public class Study {
	...
    @Column(name = "study_type")
    private String studyType;
}

// StudyType Enum
public enum StudyType {
	ONLINE("ONLINE", "온라인"),
    OFFLINE("OFFLINE", "오프라인");
    
    private String type;
    private String name;
    
    // + type getter
    // + name getter
}

// Entity를 영속화하는 서비스 로직
public class StudyService {
	// Entity save
	void save() {
		studyRepository.save(Study.of(... , StudyType.ONLINE.getType()));
	}
}

 

이렇게 하면 직접적으로 StudyType Enum 클래스의 "ONLINE" 이라는 문자열이 Study 라는 디비 테이블의 study_type 컬럼으로 들어가게 되겠지만... 뭔가 껄그럽지 못했다고 생각했다.

 

나중에 나중에 하면서 미뤄왔던 기술적 부채를 지금 해결해보려 한다.

 

Entity 클래스에 Enum 타입을 본격적으로 활용해보자!!

 


가장 기본은 @Enumerated 어노테이션을 활용하는 것이다.

@Entity
public class Study {
	...
    @Enumerated
    @Column(name = "study_type")
    private String studyType;
}

@Enumerated 어노테이션은 두가지 기능을 지원한다.

 

첫째 ORDINAL, Enum의 선언된 순서를 Integer 값으로 변환하여 DB 컬럼에 꽂아준다. 즉, Enum 내부에 선언된 상수들의 순서가 매우 중요하다. DB 컬럼은 numeric 타입이다. 이것은 내가 원하는 기능이 아니다.

 

둘째 STRING, Enum의 선언된 상수의 이름을 String 클래스 타입으로 변환하여 DB에 꽂아준다. 즉, DB 클래스 타입은 String이다. 

 


 

어라?? 두번째 기능이 내가 원하는 기능일 것 같아보였다. 하지만 위에서 보다싶이 나는 type 이라는 변수에 저장된 값을 원했다. 해당 값에 들어있는 속성이어야 했다..!!

 

여기서 타협점이 하나 생각났다. ( 아니.. 그럼 Enum에 선언한 상수명이랑 type 으로 넣을 값이랑 무조건 동일하게 하면 되잖아...? )

 

하지만 명확히 다른 소스에서 가져온 글자이고 해당 글자를 누군가 수정할 수 있다는 점도 고려했을 때, 들고가기 어려운 타협점이었다. 또 누가 정말 수정했다거나 하면 어플리케이션에서 로그를 보지 않으면 알 수 없는 꽤나 불편한 런타임 에러를 발생할 수 있는 여지를 남겨놓을 수도 없었다.

 

그러던 도중 찾게된 내용이 AttributeConverter 였다.

 


AttributeConverter

public interface AttributeConverter<X,Y> {
    // Enum -> db 저장 타입
    public Y convertToDatabaseColumn (X attribute);

    // db 저장 타입 -> Enum
    public X convertToEntityAttribute (Y dbData);
}

AttributeConverter는 이러한 기능으로 이루어진 인터페이스인데 이를 구현하고 Entity에서 아래와 같이 사용하게 되면 스프링에서 entity를 저장할때 해당 컨버터를 입혀서 저장하게 된다.

 

@Entity
public class Study {
	...
    @Convert(converter = XXXConverter.class)
    @Column(name = "study_type")
    private String studyType;
}

 

그럼 직접 StudyTypeConverter를 구현해보자!!

@Converter
public class StudyTypeConverter implements AttributeConverter<StudyType, String> {

    @Override
    public String convertToDatabaseColumn(StudyType attribute) {
        if (Objects.isNull(attribute)) {
            return null;
        }
        return attribute.getType();
    }

    @Override
    public StudyType convertToEntityAttribute(String dbData) {
        if (StringUtils.isBlank(dbData)) {
            return null;
        }
        return StudyType.find(dbData); // find는 미리 정의된 함수. 없으면 Exception.
   }
}

 

이제 해당 컨버터를 구현했으니 직접 JPA Entity에 사용을 해보면...?

@Entity
public class Study {
	...
    @Convert(converter = StudyTypeConverter.class)
    @Column(name = "study_type")
    private String studyType;
}

 

@Converter 어노테이션에는 autoApply 라는 속성이 default false; 로 설정되어있는데 해당 속성을 true로 하게 되면 엔티티에 @Convert 어노테이션으로 명시해주지 않아도 알아서 처리해준다. 하지만 개인적으로 명시해주는 부분이 제 3자 혹은 타인이 코드를 보았을 때 한눈에 파악할 수 있으므로 기본 속성인 false 인 채로 개발하려 한다.

 


위 내용의 의미와 한계점

Mybatis의 typehandler를 이용하였을 때와 비슷한 처리라고 보면 될것 같다. 결국 엔티티 클래스의 멤버로 활용되는 Class 던 Enum 이던 활용도가 높다고 생각이 든다.

 

하지만 어플리케이션 하나를 만드는데 무수한 비즈니스 로직이 있고 그 안에 활용되는 수많은 변수중에서 상당수 많은 부분을 Enum화하여 관리할 것 이라고 생각이 든다. Entity의 멤버로 들어가는 것도 있고 서비스 로직 내에서 활용되는 것도 있겠지만 어쨌든 Entity의 멤버로 사용되는 Enum의 경우는 결국 그 Enum의 갯수만큼 Converter 클래스를 따로 정의해주어야 된다는 생각이 한계로 다가왔다.

 

예를 들면, 위의 study_type 말고 study_category, study_topic 과 같은 enum화 하여 관리될 법한 내용이 하나 둘 씩 생기게 된다면 그 때마다 StudyCategoryConverter, StudyTopicConverter를 정의해주어야 한다.

 

이렇게까지되니까... Entity 클래스에 Enum 하나 써보겠다고 관리포인트가 하나 더 늘어나는 것만 같아서 혼동이 왔다. 결국은 이 부분도 해결될 여지가 있어보이지만 아직은 찾지 못했으니...

 

이 부분에 대해서 어떻게 해결할 수 있을까를 좀더 고민해보아야 겠다.

 

 

댓글

Designed by JB FACTORY