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
, EclipseLinkConverter
등) 을 사용해야합니다 . (두 번째 해결책). ~ 또는 ~ - @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과 관련하여 두 가지 주요 단점이 있다고 생각합니다.
- name () 및 ordinal () 사용의 제한 사항. @Entity를 사용하는 방식 인 @Id로 getter를 표시하지 않는 이유는 무엇입니까?
- 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 주석으로 주석을 달아야합니다.