1. 소개

JPA 버전 2.0 이하에서는 Enum 값을 데이터베이스 열에 매핑하는 편리한 방법이 없습니다 . 각 옵션에는 한계와 단점이 있습니다. 이러한 문제는 JPA 2.1을 사용하여 피할 수 있습니다. 특징.

이 사용방법(예제)에서는 JPA를 사용하여 데이터베이스에서 열거형을 유지해야 하는 다양한 가능성을 살펴보겠습니다. 또한 장점과 단점을 설명하고 간단한 코드 예제를 제공합니다.

2. @Enumerated 어노테이션 사용 하기

enum 값을 2.1 이전 JPA의 데이터베이스 표현과 매핑하는 가장 일반적인 옵션입니다. @Enumerated 어노테이션 을 사용하는 입니다. 이런 식으로 JPA 공급자에게 열거형을 서수 또는 문자열으로 변환하도록 지시할 수 있습니다 .

이 섹션에서는 두 가지 옵션을 모두 살펴보겠습니다.

하지만 먼저 이 예제 전체에서 사용할 간단한 @Entity만들어 보겠습니다 .

@Entity
public class Article {
    @Id
    private int id;

    private String title;

    // standard constructors, getters and setters
}

2.1. 서수 값 매핑

우리가 넣으면 @Enumerated (EnumType.ORDINAL) 열거 필드에 어노테이션을, JPA는 사용 Enum.ordinal () 데이터베이스에 지정된 엔티티를 지속 할 때 값입니다.

첫 번째 열거형을 소개하겠습니다.

public enum Status {
    OPEN, REVIEW, APPROVED, REJECTED;
}

다음으로 Article 클래스에 추가하고 @Enumerated(EnumType.ORDINAL) 어노테이션을 추가해 보겠습니다 .

@Entity
public class Article {
    @Id
    private int id;

    private String title;

    @Enumerated(EnumType.ORDINAL)
    private Status status;
}

이제 Article 엔터티를 유지할 때 :

Article article = new Article();
article.setId(1);
article.setTitle("ordinal title");
article.setStatus(Status.OPEN);

JPA는 다음 SQL 문을 트리거합니다.

insert 
into
    Article
    (status, title, id) 
values
    (?, ?, ?)
binding parameter [1] as [INTEGER] - [0]
binding parameter [2] as [VARCHAR] - [ordinal title]
binding parameter [3] as [INTEGER] - [1]

이러한 종류의 매핑 문제는 열거형을 수정해야 할 때 발생합니다. 중간에 새 값을 추가하거나 열거형의 순서를 재정렬하면 기존 데이터 모델이 중단 됩니다.

이러한 문제는 파악하기 어려울 뿐만 아니라 모든 데이터베이스 레코드를 업데이트해야 하므로 해결하기 어려울 수 있습니다.

2.2. 문자열 값 매핑

유사하게, JPA는 enum 필드에 @Enumerated(EnumType.STRING) 어노테이션을 추가하면 엔티티를 저장할 때 Enum.name() 값을 사용합니다 .

두 번째 열거형을 만들어 보겠습니다.

public enum Type {
    INTERNAL, EXTERNAL;
}

이를 Article 클래스에 추가하고 @Enumerated(EnumType.STRING) 어노테이션을 추가해 보겠습니다 .

@Entity
public class Article {
    @Id
    private int id;

    private String title;

    @Enumerated(EnumType.ORDINAL)
    private Status status;

    @Enumerated(EnumType.STRING)
    private Type type;
}

이제 Article 엔터티를 유지할 때 :

Article article = new Article();
article.setId(2);
article.setTitle("string title");
article.setType(Type.EXTERNAL);

JPA는 다음 SQL 문을 실행합니다.

insert 
into
    Article
    (status, title, type, id) 
values
    (?, ?, ?, ?)
binding parameter [1] as [INTEGER] - [null]
binding parameter [2] as [VARCHAR] - [string title]
binding parameter [3] as [VARCHAR] - [EXTERNAL]
binding parameter [4] as [INTEGER] - [2]

@Enumerated(EnumType.STRING) 을 사용하면 새 열거형 값을 안전하게 추가하거나 열거형 순서를 변경할 수 있습니다. 그러나 열거형 값의 이름을 바꾸면 여전히 데이터베이스 데이터가 손상됩니다.

또한 이 데이터 표현은 @Enumerated(EnumType.ORDINAL) 옵션에 비해 훨씬 읽기 쉽지만 필요한 것보다 훨씬 더 많은 공간을 소비합니다. 이것은 많은 양의 데이터를 처리해야 할 때 중요한 문제로 판명될 수 있습니다.

사용 3. @PostLoad@PrePersist 어노테이션

데이터베이스에서 지속되는 열거형을 처리해야 하는 또 다른 옵션은 표준 JPA 콜백 메서드를 사용하는 것입니다. 우리는 앞뒤로 우리의 열거를 매핑 할 수 있습니다  @PostLoad 및  @PrePersist 이벤트.

아이디어는 엔티티에 두 개의 속성을 갖는 것입니다. 첫 번째는 데이터베이스 값에 매핑되고 두 번째 는 실제 열거형 값을 보유하는 @Transient 필드입니다. 그러면 임시 속성이 비즈니스 논리 코드에서 사용됩니다.

개념을 더 잘 이해하기 위해 새 열거형을 만들고 매핑 논리에서 int 값을 사용하겠습니다 .

public enum Priority {
    LOW(100), MEDIUM(200), HIGH(300);

    private int priority;

    private Priority(int priority) {
        this.priority = priority;
    }

    public int getPriority() {
        return priority;
    }

    public static Priority of(int priority) {
        return Stream.of(Priority.values())
          .filter(p -> p.getPriority() == priority)
          .findFirst()
          .orElseThrow(IllegalArgumentException::new);
    }
}

또한 int 값을 기반으로 Priority 인스턴스를 쉽게 가져올 수 있도록 Priority.of() 메서드를 추가했습니다 .

이제 Article 클래스 에서 사용하려면 두 가지 속성을 추가하고 콜백 메서드를 구현해야 합니다.

@Entity
public class Article {

    @Id
    private int id;

    private String title;

    @Enumerated(EnumType.ORDINAL)
    private Status status;

    @Enumerated(EnumType.STRING)
    private Type type;

    @Basic
    private int priorityValue;

    @Transient
    private Priority priority;

    @PostLoad
    void fillTransient() {
        if (priorityValue > 0) {
            this.priority = Priority.of(priorityValue);
        }
    }

    @PrePersist
    void fillPersistent() {
        if (priority != null) {
            this.priorityValue = priority.getPriority();
        }
    }
}

이제 Article 엔터티를 유지할 때 :

Article article = new Article();
article.setId(3);
article.setTitle("callback title");
article.setPriority(Priority.HIGH);

JPA는 다음 SQL 쿼리를 트리거합니다.

insert 
into
    Article
    (priorityValue, status, title, type, id) 
values
    (?, ?, ?, ?, ?)
binding parameter [1] as [INTEGER] - [300]
binding parameter [2] as [INTEGER] - [null]
binding parameter [3] as [VARCHAR] - [callback title]
binding parameter [4] as [VARCHAR] - [null]
binding parameter [5] as [INTEGER] - [3]

이 옵션을 사용하면 이전에 설명한 솔루션에 비해 데이터베이스 값의 표현을 선택할 때 더 많은 유연성을 얻을 수 있지만 이상적이지는 않습니다. 엔터티에서 단일 열거형을 나타내는 두 개의 속성을 갖는 것은 옳지 않다고 생각합니다. 또한 이러한 유형의 매핑을 사용하면 JPQL 쿼리에서 열거형 값을 사용할 수 없습니다. 

4. JPA 2.1 @Converter 어노테이션 사용

위에 표시된 솔루션의 한계를 극복하기 위해 JPA 2.1 릴리스는 엔티티 속성을 데이터베이스 값으로 또는 그 반대로 변환하는 데 사용할 수 있는 새로운 표준화된 API를 도입했습니다. 우리가 해야 할 일은 javax.persistence.AttributeConverter 를 구현하는 새 클래스를 만들고 @Converter로 어노테이션을 추가하는 것입니다 .

실용적인 예를 살펴보겠습니다. 하지만 먼저 평소와 같이 새 열거형을 만듭니다.

public enum Category {
    SPORT("S"), MUSIC("M"), TECHNOLOGY("T");

    private String code;

    private Category(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }
}

또한 Article 클래스 에 추가해야 합니다 .

@Entity
public class Article {

    @Id
    private int id;

    private String title;

    @Enumerated(EnumType.ORDINAL)
    private Status status;

    @Enumerated(EnumType.STRING)
    private Type type;

    @Basic
    private int priorityValue;

    @Transient
    private Priority priority;

    private Category category;
}

이제 새 CategoryConverter를 만들어 보겠습니다 .

@Converter(autoApply = true)
public class CategoryConverter implements AttributeConverter<Category, String> {
 
    @Override
    public String convertToDatabaseColumn(Category category) {
        if (category == null) {
            return null;
        }
        return category.getCode();
    }

    @Override
    public Category convertToEntityAttribute(String code) {
        if (code == null) {
            return null;
        }

        return Stream.of(Category.values())
          .filter(c -> c.getCode().equals(code))
          .findFirst()
          .orElseThrow(IllegalArgumentException::new);
    }
}

우리는 설정 한 @Converter 의의 값 autoApply을진정한 JPA는 자동으로 모든 매핑 된 속성에 전환 논리를 적용 할 정도로 분류 유형입니다. 그렇지 않으면 엔티티 필드에 @Converter 어노테이션을 직접 넣어야 합니다 .

이제 Article 엔터티를 유지해 보겠습니다 .

Article article = new Article();
article.setId(4);
article.setTitle("converted title");
article.setCategory(Category.MUSIC);

그런 다음 JPA는 다음 SQL 문을 실행합니다.

insert 
into
    Article
    (category, priorityValue, status, title, type, id) 
values
    (?, ?, ?, ?, ?, ?)
Converted value on binding : MUSIC -> M
binding parameter [1] as [VARCHAR] - [M]
binding parameter [2] as [INTEGER] - [0]
binding parameter [3] as [INTEGER] - [null]
binding parameter [4] as [VARCHAR] - [converted title]
binding parameter [5] as [VARCHAR] - [null]
binding parameter [6] as [INTEGER] - [4]

보시다시피 AttributeConverter 인터페이스를 사용하면 열거형을 해당 데이터베이스 값으로 변환하는 자체 규칙을 간단히 설정할 수 있습니다. 또한 이미 유지된 데이터를 손상시키지 않고 안전하게 새 열거형 값을 추가하거나 기존 열거형 값을 변경할 수 있습니다.

전체 솔루션은 구현이 간단하며 이전 섹션에 제공된 옵션의 모든 단점을 해결합니다.

5. JPQL에서 열거형 사용하기

이제 JPQL 쿼리에서 열거형을 사용하는 것이 얼마나 쉬운지 봅시다.

Category.SPORT 범주가 있는 모든 Article 엔터티 를 찾으려면 다음 문을 실행해야 합니다.

String jpql = "select a from Article a where a.category = com.baeldung.jpa.enums.Category.SPORT";

List<Article> articles = em.createQuery(jpql, Article.class).getResultList();

이 경우 정규화된 열거형 이름을 사용해야 한다는 점에 유의하는 것이 중요합니다.

물론 정적 쿼리에 국한되지 않습니다. 명명된 매개변수를 사용하는 것은 완전히 합법적입니다.

String jpql = "select a from Article a where a.category = :category";

TypedQuery<Article> query = em.createQuery(jpql, Article.class);
query.setParameter("category", Category.TECHNOLOGY);

List<Article> articles = query.getResultList();

위의 예는 동적 쿼리를 형성하는 매우 편리한 방법을 제공합니다.

또한 정규화된 이름을 사용할 필요가 없습니다.

6. 결론

이 사용방법(예제)에서는 데이터베이스에서 열거형 값을 유지하는 다양한 방법을 다루었습니다. 버전 2.0 이하에서 JPA를 사용할 때의 옵션과 JPA 2.1 이상에서 사용할 수 있는 새로운 API를 제시했습니다.

JPA에서 열거형을 처리할 수 있는 유일한 가능성은 이것이 아닙니다. PostgreSQL과 같은 일부 데이터베이스는 열거형 값을 저장하기 위한 전용 열 유형을 제공합니다. 그러나 이러한 솔루션은 이 문서의 범위를 벗어납니다.

경험상 JPA 2.1 이상을 사용하는 경우 항상 AttributeConverter 인터페이스와 @Converter 어노테이션을 사용해야 합니다 .

평소와 같이 모든 코드 예제는 GitHub 리포지토리 에서 사용할 수 있습니다 .

Persistence footer banner