본문 바로가기
데이터 접근 기술/JPA

@Converter, Enum

by oncerun 2023. 7. 7.
반응형

 

Enum은 열거형이라고 불리는 데이터 유형입니다. 고정된 상수 집합을 하나의 클래스처럼 다룰 수 있기에 빈번하게 사용됩니다. 

 

JPA는 enum을 지원하며 데이터베이스에 열거형 상수를 저장하고 조회할 수 있도록 지원합니다.

 

일반적으로 JPA에서 enum을 매핑하기 위해 @Enumerated 애노테이션을 사용하여 매핑합니다. 혹은 @Converter를 활용합니다.

 

Java

@Entity
public class Book {
    // ...
    @Enumerated(EnumType.STRING)
    private BookType type;
    // ...
}

 

Kotlin

@Entity
class Book constructor(
   ...
   @Convert(converter = BookTypeConvertor::class)
   val type: BookType,

   ...
)

 

enum을 매핑하는 방법이 여러 가지가 존재하지만 오늘은 이 두 가지에 대해 정리하려고 합니다.

 

 

 

@Enumerated

 

@Enumerated 애노테이션은 JPA에서 enum을 매핑하는 가장 기본적인 방법입니다.

 

이를 사용하면 enum 상수를 데이터베이스에 어떻게 저장할지 지정할 수 있습니다. 

 

이는 EnumType.ORDINAL 혹은 EnumTYPE.STRING을 매개변수로 사용합니다. 

 

순서를 저장할지 상수의 이름을 문자열로 저장할지 선택합니다.

 

물론 대부분 문자열로 저장합니다. 순서는 변경에 매우 취약하기 때문입니다.

 

이 매핑 방식은 ENUM의 상수의 변경에 취약합니다. 실제 상수의 값이 변경되면 다양한 문제가 발생할 수 있습니다.

예를 들어 테스트 코드, 비즈니스 로직, 데이터베이스 일관성 등 생각보다 많은 영향을 주게 됩니다. 

 

설계단계에서 절대 변하지 않을 것 같지만 가능성은 존재하고 개발자는 이에 대비해야 합니다.

 

다음과 같은 Enum이 정의되어 있고, 값이 변경되었습니다.

enum class BookType {
    SCIENCE, 
    SOCIETY,
    ECONOMY,
    //COMPUTER,  COMPUTER_SCIENCE 로 변경됩니다.
    COMPUTER_SCIENCE,
    UNCATEGORIZED,
    LANGUAGE;
}

 

매핑 방법을 변경하지 않고 영향을 최소화할 수도 있을 것 같습니다.

enum class BookType constructor(
    val value: String
) {
    SCIENCE("SCIENCE"),
    SOCIETY("SOCIETY"),
    ECONOMY("ECONOMY"),
    //COMPUTER("COMPUTER"),
    COMPUTER_SCIENCE("COMPUTER"),
    UNCATEGORIZED("UNCATEGORIZED"),
    LANGUAGE("LANGUAGE");
}

 

만약 enum의 속성을 사용하도록 비즈니스 로직이 작성되어 있다면 수정할 부분이 줄어들 것입니다.

이 값을 활용해 쿼리를 작성할 수도 있을 것입니다. 

 

 

 

이제 문제는 과거 데이터베이스에 저장된 속성 값을 어떻게 가져오느냐에 초점을 두려고 합니다. 

 

과거에는 "COMPUTER"라는 문자열이 저장되어 있고 이는 현재 "COMPUTER_SCIENCE"라는 문자열로 변경되어야 하기 때문입니다. 

 

이때 우리는 다른 매핑 방법을 활용할 수 있습니다.

 

@Converter

 

AttributeConverter를 상속받아 특정 속성이 데이터베이스에 저장될 때 혹은 데이터베이스에서 조회될 때 작동하는 컨버터를 사용자 커스텀 할 수 있습니다.

 

@Converter
class BookTypeConvertor : AttributeConverter<BookType, String>{

    override fun convertToDatabaseColumn(bookType: BookType?): String? {
        if(bookType == null) return null;
        return bookType.value
    }

    override fun convertToEntityAttribute(dbData: String?): BookType? {
        if(dbData == null) return null
        return BookType.fromValue(dbData)
    }

}

 

다만 주의사항이 몇 가지 존재합니다.

 

@Id, @Version, @Enumerated, @Temporal로 명시적으로 애노테이션이 적용된 속성은 컨버터에 적용을 받지 않습니다. 

 

@Target({TYPE}) @Retention(RUNTIME)
public @interface Converter {
     boolean autoApply() default false;
}

autoApply가 true인 경우는 @Convert을 사용하여 특정 속성에서 자동으로 적용되는 변환을 재정의하거나 비활성화할 수 있습니다. 

 

false라면 명시적으로  @Convert 애노테이션으로 지정된 속성만 변환됩니다.

 

이는 명시적으로 지정하는 것이 좋다고 생각합니다. 코드를 통해 한눈에 파악할 수 있다는 장점 때문입니다.

 

 

convertToDatabaseColumn : 이름 그대로 데이터베이스에 저장될 때 실행됩니다.


convertToEntityAttribute : 데이터베이스에서 조회한 이후 엔티티로 변환될 때 실행됩니다.

 

이를 활용하면 과거에 저장된 상수와 현재는 제거된 상수를 매핑할 수도 있을 것입니다.

 

override fun convertToEntityAttribute(dbData: String?): BookType? {
    if(dbData == null) return null

    return BookType.fromValue(dbData)
}

companion object {
    fun fromValue(value: String): BookType {

        if(value == "COMPUTER"){
            return COMPUTER_SCIENCE
        }

        return values().firstOrNull {
            it.value == value
        } ?: throw IllegalArgumentException("Format $value is illegal")
    }
}

하드코딩이 마음에 들지 않는다면 과거의 데이터를 표현하는 상수를 정의하고 사용하면 될 것 같습니다.

 

 

 

정리

 

@Enumerated는 JPA의 기본적인 Enum 매핑 방법 중 하나입니다. 하지만 변경에 유연하지 않습니다.

 

만약 미래를 생각하여 Enum 속성을 데이터베이스에 저장할 때 @Converter를 사용하여 Enum의 속성을 저장

 

하도록 하고, 엔티티로 변환 시 그 속성 값을 기준으로 Enum을 반환하도록 한다면 매우 유연해지므로 변경에

 

도 일관된 데이터 및 변경의 영향을 최소화할 수 있습니다.

 

JPA에서 ENUM을 활용할 때 이러한 미래의 영향까지 생각해 매핑 방법을 결정하는 것은 미래에 큰 도움이 될 것 같습니다.

반응형

'데이터 접근 기술 > JPA' 카테고리의 다른 글

Hibernate 5 Bootstrapping API  (0) 2023.02.01
DTO와 Entity  (0) 2022.12.11
상속관계 매핑  (0) 2022.12.04
값 타입  (0) 2022.11.20
OSIV  (0) 2022.10.31

댓글