JPA 엔티티에서 Enum을 더 유연하게 사용하는 방법 (@Converter)

Kim DongKyun·2023년 12월 1일
3

개요

JPA Entity를 만들 때 특정 컬럼의 값이 명확히 정해져있다면 (ex : 학생 엔티티의 학점 필드가 가지는 값은 A~F 중 하나일 것) Enum을 사용하는 것이 괜찮은 것 같다. 그리고 JPA에서 필드에 Enum을 사용할 때는 두 가지 옵션 중 하나를 골라야 한다!


@Enumerated(value = EnumType.String)

@Enumerated 로 Enum을 필드로 사용하게 될 때, 디폴트로 설정되어 있는 이넘의 타입은 EnumType.ORDINAL 이다. 이녀석은 Enum의 실제 값이 아닌 "순서" 를 DB에 저장하는 녀석이다. 예를 들어

enum Color {
	RED,
    YELLOW,
    ORANGE
}

이 Enum 에서 RED 는 DB에 1 로 저장되고, YELLOW 는 2, ORNAGE는 3 으로 저장되는 식.

따라서 해당 방식에는 큰 문제가 있는데

Enum 의 구현 세부 내용이 바뀐다면?

예를 들어 클래스가 아래와 같이 바뀐다면

enum Color {
	RED,
    BLUE, // 추가됨!
    YELLOW,
    ORANGE
}

원래 2였던 YELLOW 는 3이되고, BLUE 가 새롭게 2가 된다.

그러나 DB에 이미 저장되어 있던 YELLOW의 값(value==2)들은 변하지 않는다 -> 혼동이 발생한다.


@Convert || @Converter

이번엔 Enum이 아래와 같은 모양이라고 하자. (이게 좀 더 자연스럽다고 생각된다)

public enum Color {
    RED("빨간색"),
    YELLOW("노란색"),
    ORANGE("오렌지색");

    private final String name;

    Color(String name) {
        this.name = name;
    }
}

이 Enum 클래스의 인스턴스 (RED, YELLOW...) 말고, 이 녀석들의 이름("빨간색") 을 DB에 넣고싶다. 이 경우 어떻게 해야할까? -> Converter를 사용하면 된다.

AttributeConverter<K, V>

JPA 패키지(jakarta.persistence) 에는 AttributeConverter 라는 인터페이스가 존재한다. 해당 클래스의 설명은 다음과 같다

A class that implements this interface can be used to convert entity attribute state into database column representation and back again. Note that the X and Y types may be the same Java type.

업로드중..

  • Entity의 Attribute(필드) 를 DB Column 으로 변환하고, DB의 Column 을 엔티티의 Attribute 로 다시 변환시켜주는 녀석이다

  • DB에 들어갈땐 DB값으로 변환, DB에서 가져올 땐 원하는 자바 타입으로 변환시켜준다고 이해하면 편하다.

  • <자바 타입, DB 타입> 으로 Generic 을 사용한다.

한번 사용해보자!

사용한 부분

// 1. Converter
public class ColorConverter implements AttributeConverter<Color, String> {

    @Override
    public String convertToDatabaseColumn(Color attribute) {
        if (attribute == null){
            return null;
        }
        return attribute.getName();
    }

    @Override
    public Color convertToEntityAttribute(String dbData) {
        return Color.getInstance(dbData);
    }
}

// 2. Enum class
public enum Color {
    RED("빨간색"),
    YELLOW("노란색"),
    ORANGE("오렌지색");
    @Getter
    private final String name;

    Color(String name) {
        this.name = name;
    }

    public static Color getInstance(String name){
        return Arrays.stream(values())
                .filter(color -> color.getName().equals(name))
                .findFirst()
                .orElseThrow();
    }
}

// 3. Entity
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class FooEntity {
    @Id
    private Long id;
    @Convert(converter = ColorConverter.class)
    private Color color;
}
  • 이 경우 양쪽 컨버트가 잘 된다(서버 - DB 간 데이터 타입의)

  • 그런데, Enum 마다 Converter 를 다 선언해줘야하는 게 너무 불편하다!


좀 더 스마트하게 Enum 을 Convert 하는 방법

출처 블로그에서 설명과 함께!

public interface CodedEnum<T> {
    T getCode();
}
public abstract class AbstractCodedEnumConverter<T extends Enum<T> & CodedEnum<E>, E> implements AttributeConverter<T, E> {
    private final Class<T> clazz;

    public AbstractCodedEnumConverter(Class<T> clazz) {
        this.clazz = clazz;
    }

    @Override
    public E convertToDatabaseColumn(T attribute) { 
        return attribute.getCode();
    }

    @Override
    public T convertToEntityAttribute(E dbData) {
        if (Objects.isNull(dbData)) {
            return null;
        }
        return Arrays.stream(clazz.getEnumConstants())
            .filter(e -> e.getCode().equals(dbData))
            .findFirst()
            .orElseThrow(() -> new IllegalArgumentException("Unknown code: " + dbData));
    }
}
public enum ProductType implements CodedEnum<Integer> {
    FOOD(100),
    DRINK(200),
    CLOTHES(300),
    ELECTRONICS(400),
    OTHER(500);

    private final int code;

    ProductType(int code) {
        this.code = code;
    }

    public Integer getCode() {
        return code;
    }
    
    @jakarta.persistence.Converter(autoApply = true)
    static class Converter extends AbstractCodedEnumConverter<ProductType, Integer> {
        public Converter() {
            super(ProductType.class);
        }
    }
}

위 코드가 너무 생소해요!

  • 곧 Type Parameter 와 Enum을 파헤치는 글을 써볼게요!

  • 이상한점, 별로인점 언제든 댓글주세요

4개의 댓글

comment-user-thumbnail
2024년 1월 25일

EnumType 을 사용할때 String 으로 명시해야 잘못된 동작을 방지할수 있다 정도로만 알고있었는데 내부값을 바로 저장할수가 있었네요 조금더 명확하게 데이터가 저장되서 유용할거같습니다 :)

1개의 답글
comment-user-thumbnail
2024년 1월 30일

유용한 정보 감사합니다.

1개의 답글