고정 값을 가진 JPA의 enum을 매핑 하시겠습니까?


192

JPA를 사용하여 열거 형을 매핑하는 다른 방법을 찾고 있습니다. 특히 각 열거 형 항목의 정수 값을 설정하고 정수 값만 저장하려고합니다.

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {

  public enum Right {
      READ(100), WRITE(200), EDITOR (300);

      private int value;

      Right(int value) { this.value = value; }

      public int getValue() { return value; }
  };

  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "AUTHORITY_ID")
  private Long id;

  // the enum to map : 
  private Right right;
}

간단한 해결책은 EnumType.ORDINAL과 함께 Enumerated 주석을 사용하는 것입니다.

@Column(name = "RIGHT")
@Enumerated(EnumType.ORDINAL)
private Right right;

그러나이 경우 JPA는 원하는 값 (100,200,300)이 아닌 열거 형 인덱스 (0,1,2)를 매핑합니다.

내가 찾은 두 가지 해결책은 간단하지 않습니다 ...

첫 번째 해결책

here 제안 된 솔루션 은 @PrePersist 및 @PostLoad를 사용하여 열거 형을 다른 필드로 변환하고 열거 형 필드를 일시적으로 표시합니다.

@Basic
private int intValueForAnEnum;

@PrePersist
void populateDBFields() {
  intValueForAnEnum = right.getValue();
}

@PostLoad
void populateTransientFields() {
  right = Right.valueOf(intValueForAnEnum);
}

두 번째 해결책

여기에 제안 된 두 번째 솔루션 은 일반 변환 객체를 제안했지만 여전히 무겁고 최대 절전 모드로 보입니다 (@Type은 Java EE에 존재하지 않는 것 같습니다).

@Type(
    type = "org.appfuse.tutorial.commons.hibernate.GenericEnumUserType",
    parameters = {
            @Parameter(
                name  = "enumClass",                      
                value = "Authority$Right"),
            @Parameter(
                name  = "identifierMethod",
                value = "toInt"),
            @Parameter(
                name  = "valueOfMethod",
                value = "fromInt")
            }
)

다른 해결책이 있습니까?

몇 가지 아이디어를 염두에두고 있지만 JPA에 존재하는지 모르겠습니다.

  • Authority 오브젝트를로드 및 저장할 때 Authority 클래스의 올바른 멤버의 setter 및 getter 메소드를 사용하십시오.
  • 동등한 아이디어는 JPA에게 enum을 int로, int를 enum으로 변환하는 Right enum의 방법이 무엇인지 알려주는 것입니다.
  • Spring을 사용하고 있기 때문에 JPA에게 특정 변환기 (RightEditor)를 사용하도록 지시하는 방법이 있습니까?

7
누군가가 때때로 열거에서 항목의 위치를 ​​변경하고 데이터베이스가 재앙이 될 수 있습니다.
Natasha KP

2
이름 사용에 동일하게 적용되지 않습니다-누군가 enum 이름을 변경할 수 있으며 다시 데이터베이스와 동기화되지 않습니다 ...
topchef

2
@NatashaKP에 동의합니다. 서수를 사용하지 마십시오. 이름을 바꾸는 데는 그런 것이 없습니다. 실제로 이전 열거 형을 삭제하고 새로운 이름으로 새 열거 형을 추가하므로 저장된 데이터가 동기화되지 않습니다 (의미론, 아마도 : P).
스 벤드 한센

네, 내가 아는 5 가지 솔루션이 있습니다. 자세한 답변이있는 아래 답변을 참조하십시오.
Chris Ritchie

답변:


168

JPA 2.1 이전 버전의 경우 JPA는 두 가지 방법으로 열거 형을 처리 name할 수 ordinal있습니다. 표준 JPA는 사용자 정의 유형을 지원하지 않습니다. 그래서:

  • 사용자 정의 유형 변환을 수행하려면 공급자 확장 (Hibernate UserType, EclipseLink Converter등) 을 사용해야합니다 . (두 번째 해결책). ~ 또는 ~
  • @PrePersist 및 @PostLoad 트릭 (첫 번째 솔루션)을 사용해야합니다. ~ 또는 ~
  • getter 및 setter에 주석을 달고 int값을 가져 오거나 ~ 또는 ~
  • 엔티티 레벨에서 정수 속성을 사용하고 getter 및 setter에서 변환을 수행하십시오.

최신 옵션을 설명하겠습니다 (기본 구현이므로 필요에 따라 조정하십시오).

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {

    public enum Right {
        READ(100), WRITE(200), EDITOR (300);

        private int value;

        Right(int value) { this.value = value; }    

        public int getValue() { return value; }

        public static Right parse(int id) {
            Right right = null; // Default
            for (Right item : Right.values()) {
                if (item.getValue()==id) {
                    right = item;
                    break;
                }
            }
            return right;
        }

    };

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "AUTHORITY_ID")
    private Long id;

    @Column(name = "RIGHT_ID")
    private int rightId;

    public Right getRight () {
        return Right.parse(this.rightId);
    }

    public void setRight(Right right) {
        this.rightId = right.getValue();
    }

}

50
슬픈 JPA는 이에 대한 기본 지원을하지 않습니다
Jaime Hablutzel

20
트윗 담아 가기 개발자가 int 값이나 이름 대신 필드 / 속성 중 하나의 값으로 열거 형을 유지하려고한다고 생각하는 것이 미친 일입니까? 둘 다 극도로 "깨지기 쉬우 며"리팩토링에 좋지 않습니다. 또한 이름을 사용하면 Java와 데이터베이스 모두에서 동일한 이름 지정 규칙을 사용한다고 가정합니다. 예를 들어 성별을 보자. 데이터베이스에서 단순히 'M'또는 'F'로 정의 될 수 있지만 Gender.MLE 또는 Gender.F 대신 Java에서 Gender.MALE 및 Gender.FEMALE을 사용하지 못하게해서는 안됩니다.
spaaarky21

2
다른 값이나 필드가 아닌 이름과 서 수가 모두 고유하다는 것이 이유 일 수 있습니다. 순서가 변경 될 수 있고 (서수를 사용하지 않음) 이름도 변경 될 수 있지만 (열 이름을 변경하지 마십시오 : P) 다른 값도있을 수 있습니다 ... 나는 확실하지 않습니다. 다른 가치를 저장하는 능력을 추가하여 큰 가치.
스 벤드 한센

2
사실,이 값을 참조 할 ... 나는 위 내 댓글의 일부를 삭제할 것 I 수 아직 편집이 경우 : P : D
스벤 한센

13
JPA2.1은 변환기를 기본적으로 지원합니다. 참조하시기 바랍니다 somethoughtsonjava.blogspot.fi/2013/10/...을
drodil

69

이제 JPA 2.1에서 가능합니다.

@Column(name = "RIGHT")
@Enumerated(EnumType.STRING)
private Right right;

자세한 내용은 :


2
이제 무엇이 가능합니까? 물론 우리는 사용할 수 @Converter있지만 enum더 우아하게 처리해야합니다!
YoYo April

4
"답변"은 AttributeConverterBUT 사용에 대해 이야기하는 2 개의 링크를 참조 하며, 아무 것도하지 않고 OP에 응답하지 않는 코드를 인용합니다.

@ DN1은 자유롭게 개선 할 수 있습니다
Tvaroh

1
"답변"이므로 "개선"해야합니다. 이미에 대한 답이있다AttributeConverter

1
다음을 추가 한 후 완벽하게 작업 :@Convert(converter = Converter.class) @Enumerated(EnumType.STRING)
Kaushal28

23

JPA 2.1부터는 AttributeConverter 를 사용할 수 있습니다 .

다음과 같이 열거 클래스를 작성하십시오.

public enum NodeType {

    ROOT("root-node"),
    BRANCH("branch-node"),
    LEAF("leaf-node");

    private final String code;

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

    public String getCode() {
        return code;
    }
}

다음과 같이 변환기를 작성하십시오.

import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

@Converter(autoApply = true)
public class NodeTypeConverter implements AttributeConverter<NodeType, String> {

    @Override
    public String convertToDatabaseColumn(NodeType nodeType) {
        return nodeType.getCode();
    }

    @Override
    public NodeType convertToEntityAttribute(String dbData) {
        for (NodeType nodeType : NodeType.values()) {
            if (nodeType.getCode().equals(dbData)) {
                return nodeType;
            }
        }

        throw new IllegalArgumentException("Unknown database value:" + dbData);
    }
}

엔터티에서는 다음이 필요합니다.

@Column(name = "node_type_code")

운이 좋으면 @Converter(autoApply = true)컨테이너마다 다를 수 있지만 Wildfly 8.1.0에서 작동하도록 테스트되었습니다. 작동하지 않으면 @Convert(converter = NodeTypeConverter.class)엔터티 클래스 열을 추가 할 수 있습니다 .


"값 ()는" ") (NodeType.values"이어야
커티스 Yallop

17

가장 좋은 방법은 고유 ID를 각 열거 형 유형에 매핑하여 ORDINAL 및 STRING의 함정을 피하는 것입니다. 열거 형을 매핑 할 수있는 5 가지 방법을 간략하게 설명하는 이 게시물 을 참조하십시오 .

위의 링크에서 가져온 것 :

1 & 2. @Enumerated 사용

@Enumerated 어노테이션을 사용하여 JPA 엔티티 내에 열거를 맵핑 할 수있는 방법은 현재 두 가지가 있습니다. 불행히도 EnumType.STRING 및 EnumType.ORDINAL에는 제한이 있습니다.

EnumType.String을 사용하는 경우 열거 형 유형 중 하나의 이름을 바꾸면 열거 형 값이 데이터베이스에 저장된 값과 동기화되지 않습니다. EnumType.ORDINAL을 사용하는 경우 열거 형 내에서 형식을 삭제하거나 재정렬하면 데이터베이스에 저장된 값이 잘못된 열거 형 형식에 매핑됩니다.

이 두 옵션은 모두 깨지기 쉽습니다. 데이터베이스 마이그레이션을 수행하지 않고 열거 형을 수정하면 데이터의 무결성을 위태롭게 할 수 있습니다.

3. 라이프 사이클 콜백

가능한 해결책은 JPA 라이프 사이클 콜백 주석, @PrePersist 및 @PostLoad를 사용하는 것입니다. 엔터티에 두 개의 변수가 있기 때문에 이것은 아주 못생긴 느낌입니다. 하나는 데이터베이스에 저장된 값을 매핑하고 다른 하나는 실제 열거 형을 매핑합니다.

4. 고유 한 ID를 각 열거 형에 매핑

선호되는 솔루션은 열거 형에 정의 된 고정 값 또는 ID에 열거 형을 매핑하는 것입니다. 미리 정의 된 고정 값에 매핑하면 코드가 더욱 강력 해집니다. 열거 형의 순서를 변경하거나 이름을 리팩토링해도 부작용이 발생하지 않습니다.

5. Java EE7 @Convert 사용

JPA 2.1을 사용하는 경우 새로운 @Convert 주석을 사용하는 옵션이 있습니다. 이를 위해서는 @Converter로 주석이 달린 변환기 클래스를 만들어야합니다. 여기에서 각 열거 형 유형에 대해 데이터베이스에 저장할 값을 정의 할 수 있습니다. 엔티티 내에서 @Convert로 열거에 주석을 달 것입니다.

나의 선호도 : (번호 4)

변환기를 사용하는 대신 열거 형 내에서 ID를 정의하는 것을 선호하는 이유는 캡슐화가 좋기 때문입니다. 열거 형에만 ID를 알고 있어야하며 엔티티 만 열거 형을 데이터베이스에 매핑하는 방법을 알아야합니다.

코드 예제 는 원래 게시물 을 참조하십시오 .


1
javax.persistence.Converter는 단순하며 @Converter (autoApply = true)를 사용하면 도메인 클래스에 @Convert 주석이없는 상태를 유지할 수 있습니다.
Rostislav Matl

9

문제는 JPA가 이미 복잡한 기존 스키마를 가질 수 있다는 생각을 결코 받아들이지 않았다는 것입니다.

Enum과 관련하여 두 가지 주요 단점이 있다고 생각합니다.

  1. name () 및 ordinal () 사용의 제한 사항. @Entity를 사용하는 방식 인 @Id로 getter를 표시하지 않는 이유는 무엇입니까?
  2. Enum은 일반적으로 데이터베이스에 적절한 이름, 설명 이름, 현지화 등을 포함한 모든 종류의 메타 데이터와 연결할 수 있도록 표현되어 있습니다. 엔터티의 유연성과 결합 된 Enum을 쉽게 사용할 수 있어야합니다.

JPA_SPEC-47 에 대한 나의 원인과 투표를 도와주세요

@Converter를 사용하여 문제를 해결하는 것보다 더 우아하지 않습니까?

// Note: this code won't work!!
// it is just a sample of how I *would* want it to work!
@Enumerated
public enum Language {
  ENGLISH_US("en-US"),
  ENGLISH_BRITISH("en-BR"),
  FRENCH("fr"),
  FRENCH_CANADIAN("fr-CA");
  @ID
  private String code;
  @Column(name="DESCRIPTION")
  private String description;

  Language(String code) {
    this.code = code;
  }

  public String getCode() {
    return code;
  }

  public String getDescription() {
    return description;
  }
}

4

파스칼 관련 코드를 닫을 수 있습니다.

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {

    public enum Right {
        READ(100), WRITE(200), EDITOR(300);

        private Integer value;

        private Right(Integer value) {
            this.value = value;
        }

        // Reverse lookup Right for getting a Key from it's values
        private static final Map<Integer, Right> lookup = new HashMap<Integer, Right>();
        static {
            for (Right item : Right.values())
                lookup.put(item.getValue(), item);
        }

        public Integer getValue() {
            return value;
        }

        public static Right getKey(Integer value) {
            return lookup.get(value);
        }

    };

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = "AUTHORITY_ID")
    private Long id;

    @Column(name = "RIGHT_ID")
    private Integer rightId;

    public Right getRight() {
        return Right.getKey(this.rightId);
    }

    public void setRight(Right right) {
        this.rightId = right.getValue();
    }

}

3

나는 다음을 할 것입니다 :

자체 파일에 열거 형을 별도로 선언하십시오.

public enum RightEnum {
      READ(100), WRITE(200), EDITOR (300);

      private int value;

      private RightEnum (int value) { this.value = value; }


      @Override
      public static Etapa valueOf(Integer value){
           for( RightEnum r : RightEnum .values() ){
              if ( r.getValue().equals(value))
                 return r;
           }
           return null;//or throw exception
     }

      public int getValue() { return value; }


}

Right라는 새로운 JPA 엔터티 선언

@Entity
public class Right{
    @Id
    private Integer id;
    //FIElDS

    // constructor
    public Right(RightEnum rightEnum){
          this.id = rightEnum.getValue();
    }

    public Right getInstance(RightEnum rightEnum){
          return new Right(rightEnum);
    }


}

이 값을 받기위한 변환기도 필요합니다 (JPA 2.1 만 해당되며 변환기를 사용하여 직접 유지하기 위해 이러한 열거 형과 관련하여 여기서 논의하지 않을 문제가 있습니다)

import mypackage.RightEnum;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;

/**
 * 
 * 
 */
@Converter(autoApply = true)
public class RightEnumConverter implements AttributeConverter<RightEnum, Integer>{

    @Override //this method shoudn´t be used, but I implemented anyway, just in case
    public Integer convertToDatabaseColumn(RightEnum attribute) {
        return attribute.getValue();
    }

    @Override
    public RightEnum convertToEntityAttribute(Integer dbData) {
        return RightEnum.valueOf(dbData);
    }

}

기관 :

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {


  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "AUTHORITY_ID")
  private Long id;

  // the **Entity** to map : 
  private Right right;

  // the **Enum** to map (not to be persisted or updated) : 
  @Column(name="COLUMN1", insertable = false, updatable = false)
  @Convert(converter = RightEnumConverter.class)
  private RightEnum rightEnum;

}

이 방법으로 enum 필드에 직접 설정할 수 없습니다. 그러나 다음을 사용하여 권한에서 권한 필드를 설정할 수 있습니다

autorithy.setRight( Right.getInstance( RightEnum.READ ) );//for example

비교해야 할 경우 다음을 사용할 수 있습니다.

authority.getRight().equals( RightEnum.READ ); //for example

꽤 멋진 것 같아요. 변환기가 열거 형과 함께 사용하도록 의도되지 않았으므로 완전히 정확하지 않습니다. 실제로 설명서에서는이 목적으로 사용하지 말라고 대신 @Enumerated 주석을 사용해야합니다. 문제는 두 가지 열거 형 유형이 있습니다. ORDINAL 또는 STRING이지만 ORDINAL은 까다 롭고 안전하지 않습니다.


그러나 그것이 만족스럽지 않으면 조금 더 해킹되고 더 단순한 것을 할 수 있습니다.

보자

RightEnum :

public enum RightEnum {
      READ(100), WRITE(200), EDITOR (300);

      private int value;

      private RightEnum (int value) { 
            try {
                  this.value= value;
                  final Field field = this.getClass().getSuperclass().getDeclaredField("ordinal");
                  field.setAccessible(true);
                  field.set(this, value);
             } catch (Exception e) {//or use more multicatch if you use JDK 1.7+
                  throw new RuntimeException(e);
            }
      }


      @Override
      public static Etapa valueOf(Integer value){
           for( RightEnum r : RightEnum .values() ){
              if ( r.getValue().equals(value))
                 return r;
           }
           return null;//or throw exception
     }

      public int getValue() { return value; }


}

권한 기관

@Entity
@Table(name = "AUTHORITY_")
public class Authority implements Serializable {


  @Id
  @GeneratedValue(strategy = GenerationType.AUTO)
  @Column(name = "AUTHORITY_ID")
  private Long id;


  // the **Enum** to map (to be persisted or updated) : 
  @Column(name="COLUMN1")
  @Enumerated(EnumType.ORDINAL)
  private RightEnum rightEnum;

}

이 두 번째 아이디어에서 서수 속성을 해킹 한 이후 완벽한 상황은 아니지만 훨씬 작은 코딩입니다.

JPA 사양에는 EnumType.ID가 포함되어야하며 여기서 enum value 필드에는 일종의 @EnumId 주석으로 주석을 달아야합니다.


2

이러한 종류의 Enum JPA 매핑을 해결하는 내 솔루션은 다음과 같습니다.

1 단계 -db 열에 매핑하려는 모든 열거 형에 사용할 다음 인터페이스를 작성하십시오.

public interface IDbValue<T extends java.io.Serializable> {

    T getDbVal();

}

2 단계 -다음과 같이 사용자 정의 일반 JPA 변환기를 구현하십시오.

import javax.persistence.AttributeConverter;

public abstract class EnumDbValueConverter<T extends java.io.Serializable, E extends Enum<E> & IDbValue<T>>
        implements AttributeConverter<E, T> {

    private final Class<E> clazz;

    public EnumDbValueConverter(Class<E> clazz){
        this.clazz = clazz;
    }

    @Override
    public T convertToDatabaseColumn(E attribute) {
        if (attribute == null) {
            return null;
        }
        return attribute.getDbVal();
    }

    @Override
    public E convertToEntityAttribute(T dbData) {
        if (dbData == null) {
            return null;
        }
        for (E e : clazz.getEnumConstants()) {
            if (dbData.equals(e.getDbVal())) {
                return e;
            }
        }
        // handle error as you prefer, for example, using slf4j:
        // log.error("Unable to convert {} to enum {}.", dbData, clazz.getCanonicalName());
        return null;
    }

}

이 클래스는 on enum 을 사용하여 enum 값 E을 유형의 데이터베이스 필드 T(예 :)로 변환 하거나 그 반대로 변환합니다.StringgetDbVal()E

3 단계 -원래 열거 형이 1 단계에서 정의한 인터페이스를 구현하도록합니다.

public enum Right implements IDbValue<Integer> {
    READ(100), WRITE(200), EDITOR (300);

    private final Integer dbVal;

    private Right(Integer dbVal) {
        this.dbVal = dbVal;
    }

    @Override
    public Integer getDbVal() {
        return dbVal;
    }
}

4Right 단계-3 단계 열거에 대해 2 단계 변환기를 확장하십시오 .

public class RightConverter extends EnumDbValueConverter<Integer, Right> {
    public RightConverter() {
        super(Right.class);
    }
}

5 단계 -마지막 단계는 다음과 같이 엔티티의 필드에 주석을 추가하는 것입니다.

@Column(name = "RIGHT")
@Convert(converter = RightConverter.class)
private Right right;

결론

IMHO 이것은 매핑 할 열거 형이 많고 열거 형 자체의 특정 필드를 매핑 값으로 사용하려는 경우 가장 깨끗하고 가장 우아한 솔루션입니다.

비슷한 매핑 논리가 필요한 프로젝트의 다른 모든 열거 형의 경우 3 단계에서 5 단계 만 반복하면됩니다.

  • IDbValue열거 형에 인터페이스 를 구현하십시오 .
  • EnumDbValueConverter코드를 3 줄로 확장하십시오 (분리 된 클래스를 작성하지 않도록 엔티티 내 에서이 작업을 수행 할 수도 있습니다).
  • 패키지 @Convert에서 enum 속성에 주석을 답니다 javax.persistence.

도움이 되었기를 바랍니다.


1
public enum Gender{ 
    MALE, FEMALE 
}



@Entity
@Table( name="clienti" )
public class Cliente implements Serializable {
...

// **1 case** - If database column type is number (integer) 
// (some time for better search performance)  -> we should use 
// EnumType.ORDINAL as @O.Badr noticed. e.g. inserted number will
// index of constant starting from 0... in our example for MALE - 0, FEMALE - 1.
// **Possible issue (advice)**: you have to add the new values at the end of
// your enum, in order to keep the ordinal correct for future values.

@Enumerated(EnumType.ORDINAL)
    private Gender gender;


// **2 case** - If database column type is character (varchar) 
// and you want to save it as String constant then ->

@Enumerated(EnumType.STRING)
    private Gender gender;

...
}

// in all case on code level you will interact with defined 
// type of Enum constant but in Database level

첫 번째 사례 ( EnumType.ORDINAL)

╔════╦══════════════╦════════╗
 ID     NAME       GENDER 
╠════╬══════════════╬════════╣
  1  Jeff Atwood      0   
  2  Geoff Dalgas     0   
  3 Jarrod Jesica     1   
  4  Joel Lucy        1   
╚════╩══════════════╩════════╝

두 번째 경우 ( EnumType.STRING)

╔════╦══════════════╦════════╗
 ID     NAME       GENDER 
╠════╬══════════════╬════════╣
  1  Jeff Atwood    MALE  
  2  Geoff Dalgas   MALE  
  3 Jarrod Jesica  FEMALE 
  4  Joel Lucy     FEMALE 
╚════╩══════════════╩════════╝
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.