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

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)를 사용하도록 지시하는 방법이 있습니까?


답변

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();
    }

}


답변

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

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

자세한 내용은 :


답변

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)엔터티 클래스 열을 추가 할 수 있습니다 .


답변

가장 좋은 방법은 고유 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를 알고 있어야하며 엔티티 만 열거 형을 데이터베이스에 매핑하는 방법을 알아야합니다.

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


답변

문제는 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;
  }
}


답변

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

@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();
    }

}


답변

나는 다음을 할 것입니다 :

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

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 주석으로 주석을 달아야합니다.