คอลเลกชันแผนที่ JPA ของ Enums


93

มีวิธีใดใน JPA ในการแมปคอลเล็กชัน Enums ภายในคลาสเอนทิตีหรือไม่ หรือวิธีแก้ปัญหาเดียวคือการรวม Enum กับคลาสโดเมนอื่นและใช้เพื่อแมปคอลเล็กชัน?

@Entity
public class Person {
    public enum InterestsEnum {Books, Sport, etc...  }
    //@???
    Collection<InterestsEnum> interests;
}

ฉันใช้การใช้งาน Hibernate JPA แต่แน่นอนว่าต้องการใช้โซลูชันที่ไม่เชื่อเรื่องพระเจ้ามากกว่า

คำตอบ:


112

โดยใช้ไฮเบอร์เนตคุณสามารถทำได้

@CollectionOfElements(targetElement = InterestsEnum.class)
@JoinTable(name = "tblInterests", joinColumns = @JoinColumn(name = "personID"))
@Column(name = "interest", nullable = false)
@Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;

141
เผื่อใครอ่านตอนนี้ ... @CollectionOfElements เลิกใช้แล้วให้ใช้: @ElementCollection

2
คุณสามารถค้นหาตัวอย่างได้จากคำตอบของคำถามนี้: stackoverflow.com/q/3152787/363573
Stephan

1
เนื่องจากคุณพูดถึง Hibernate ฉันคิดว่าคำตอบนี้อาจเป็นคำตอบเฉพาะผู้ขาย แต่ฉันไม่คิดว่าจะเป็นเช่นนั้นเว้นแต่การใช้ JoinTable จะทำให้เกิดปัญหาในการใช้งานอื่น ๆ จากที่ฉันเห็นฉันเชื่อว่าควรใช้ CollectionTable แทน นั่นคือสิ่งที่ฉันใช้ในคำตอบของฉันและมันได้ผลสำหรับฉัน (แม้ว่าใช่ฉันกำลังใช้ Hibernate อยู่ในขณะนี้)
spaaarky21

ฉันรู้ว่านี่เป็นเธรดเก่า แต่เรากำลังใช้สิ่งเดียวกันโดยใช้ javax.persistence เมื่อเราเพิ่ม: @ElementCollection (targetClass = Roles.class) @CollectionTable (name = "USER_ROLES", joinColumns = @ JoinColumn (name = "USER_ID")) @Column (name = "ROLE", nullable = false) @Enumerated ( EnumType.STRING) ชุดบทบาท <Roles> ส่วนตัว; ในตารางผู้ใช้ของเราสิ่งต่างๆจะหลุดออกมาในแพ็คเกจโมเดลทั้งหมด ในวัตถุผู้ใช้ของเราแม้กระทั่งข้อผิดพลาดบนตัวสร้าง @Id ... @ GeneratedValue ของคีย์หลักและ @OneToMany ตัวแรกก็โยนข้อผิดพลาดโง่ ๆ ในการสร้าง
LinuxLars

สิ่งที่คุ้มค่า - ข้อผิดพลาดที่ฉันเห็นเป็นข้อผิดพลาด - issue.jboss.org/browse/JBIDE-16016
LinuxLars

65

ลิงก์ในคำตอบของ Andy เป็นจุดเริ่มต้นที่ดีสำหรับการทำแผนที่คอลเลกชันของอ็อบเจ็กต์ "non-Entity" ใน JPA 2 แต่ยังไม่สมบูรณ์เมื่อพูดถึงการแมป enums นี่คือสิ่งที่ฉันคิดขึ้นมาแทน

@Entity
public class Person {
    @ElementCollection(targetClass=InterestsEnum.class)
    @Enumerated(EnumType.STRING) // Possibly optional (I'm not sure) but defaults to ORDINAL.
    @CollectionTable(name="person_interest")
    @Column(name="interest") // Column name in person_interest
    Collection<InterestsEnum> interests;
}

4
น่าเศร้าที่ "ผู้ดูแลระบบ" บางคนตัดสินใจลบคำตอบนั้นโดยไม่ให้เหตุผล (ประมาณเท่าทุนสำหรับหลักสูตรที่นี่) สำหรับการอ้างอิงคือdatanucleus.org/products/accessplatform_3_0/jpa/orm/…
DataNucleus

2
ทั้งหมดที่คุณต้องการจริงจากนี้อยู่@ElementCollectionและCollection<InterestsEnum> interests; ส่วนที่เหลืออาจเป็นประโยชน์ แต่ไม่จำเป็น ตัวอย่างเช่น@Enumerated(EnumType.STRING)ใส่สตริงที่มนุษย์อ่านได้ในฐานข้อมูลของคุณ
CorayThan

2
คุณถูกต้อง - ในตัวอย่างนี้คุณสามารถพึ่งพาสิ่ง@Columnที่nameเป็นนัยได้ ฉันแค่อยากจะชี้แจงว่าอะไรเป็นนัยเมื่อละเว้น @Column และขอแนะนำให้ใช้ @Enumerated เสมอเนื่องจากลำดับเป็นสิ่งที่แย่มากในการเริ่มต้น :)
spaaarky21

ฉันคิดว่ามันคุ้มค่าที่จะพูดถึงว่าคุณต้องการตาราง person_interest จริง ๆ
lukaszrys

ฉันต้องเพิ่มพารามิเตอร์ joinColumn เพื่อให้มันใช้งานได้@CollectionTable(name="person_interest", joinColumns = {@JoinColumn(name="person_id")})
Tiago

8

ฉันสามารถทำสิ่งนี้ให้สำเร็จได้ด้วยวิธีง่ายๆนี้:

@ElementCollection(fetch = FetchType.EAGER)
Collection<InterestsEnum> interests;

โหลดกระตือรือร้นเป็นสิ่งจำเป็นเพื่อที่จะหลีกเลี่ยงการโหลดขี้เกียจ inizializing ข้อผิดพลาดตามที่อธิบายไว้ที่นี่


5

ฉันใช้ java.util.RegularEnumSet แก้ไขเล็กน้อยเพื่อให้มี EnumSet ต่อเนื่อง:

@MappedSuperclass
@Access(AccessType.FIELD)
public class PersistentEnumSet<E extends Enum<E>> 
    extends AbstractSet<E> {
  private long elements;

  @Transient
  private final Class<E> elementType;

  @Transient
  private final E[] universe;

  public PersistentEnumSet(final Class<E> elementType) {
    this.elementType = elementType;
    try {
      this.universe = (E[]) elementType.getMethod("values").invoke(null);
    } catch (final ReflectiveOperationException e) {
      throw new IllegalArgumentException("Not an enum type: " + elementType, e);
    }
    if (this.universe.length > 64) {
      throw new IllegalArgumentException("More than 64 enum elements are not allowed");
    }
  }

  // Copy everything else from java.util.RegularEnumSet
  // ...
}

ตอนนี้คลาสนี้เป็นฐานสำหรับชุด enum ทั้งหมดของฉัน:

@Embeddable
public class InterestsSet extends PersistentEnumSet<InterestsEnum> {
  public InterestsSet() {
    super(InterestsEnum.class);
  }
}

และชุดนั้นฉันสามารถใช้ในเอนทิตีของฉัน:

@Entity
public class MyEntity {
  // ...
  @Embedded
  @AttributeOverride(name="elements", column=@Column(name="interests"))
  private InterestsSet interests = new InterestsSet();
}

ข้อดี:

  • การทำงานกับ enum ประเภทปลอดภัยและประสิทธิภาพที่กำหนดไว้ในรหัสของคุณ (ดูjava.util.EnumSetคำอธิบาย)
  • ชุดนี้เป็นเพียงคอลัมน์ตัวเลขหนึ่งคอลัมน์ในฐานข้อมูล
  • ทุกอย่างเป็น JPA ธรรมดา (ไม่มีประเภทกำหนดเองเฉพาะของผู้ให้บริการ)
  • การประกาศเขตข้อมูลใหม่ประเภทเดียวกันที่ง่าย (และสั้น) เปรียบเทียบกับโซลูชันอื่น ๆ

ข้อเสีย:

  • การทำสำเนารหัส ( RegularEnumSetและPersistentEnumSetเกือบจะเหมือนกัน)
    • คุณสามารถสรุปผลลัพธ์ของEnumSet.noneOf(enumType)คุณPersistenEnumSetประกาศAccessType.PROPERTYและระบุวิธีการเข้าถึงสองวิธีซึ่งใช้การสะท้อนเพื่ออ่านและเขียนelementsฟิลด์
  • จำเป็นต้องมีคลาสเซ็ตเพิ่มเติมสำหรับคลาส enum ทุกคลาสที่ควรเก็บไว้ในเซ็ตถาวร
    • หากผู้ให้บริการการคงอยู่ของคุณสนับสนุน embeddables โดยไม่ต้องสร้างสาธารณะคุณสามารถเพิ่ม@EmbeddableการPersistentEnumSetและวางชั้นพิเศษ ( ... interests = new PersistentEnumSet<>(InterestsEnum.class);)
  • คุณต้องใช้@AttributeOverrideตามที่ให้ไว้ในตัวอย่างของฉันหากคุณมีมากกว่าหนึ่งรายการPersistentEnumSetในเอนทิตีของคุณ (มิฉะนั้นทั้งสองจะถูกเก็บไว้ในคอลัมน์ "องค์ประกอบ" เดียวกัน)
  • การเข้าถึงvalues()ด้วยการสะท้อนในตัวสร้างนั้นไม่เหมาะสมที่สุด (โดยเฉพาะเมื่อดูที่ประสิทธิภาพ) แต่อีกสองตัวเลือกก็มีข้อเสียเช่นกัน:
    • การใช้งานเช่นEnumSet.getUniverse()ใช้ประโยชน์จากsun.miscคลาส
    • การระบุอาร์เรย์ค่าเป็นพารามิเตอร์มีความเสี่ยงที่ค่าที่ระบุจะไม่ถูกต้อง
  • รองรับเฉพาะ enums ที่มีค่าสูงสุด 64 ค่าเท่านั้น (นั่นเป็นข้อเสียเปรียบจริงหรือ?)
    • คุณสามารถใช้ BigInteger แทนได้
  • ไม่ใช่เรื่องง่ายที่จะใช้ช่ององค์ประกอบในแบบสอบถามเกณฑ์หรือ JPQL
    • คุณสามารถใช้ตัวดำเนินการไบนารีหรือคอลัมน์บิตมาสก์กับฟังก์ชันที่เหมาะสมหากฐานข้อมูลของคุณรองรับ

4

tl; dr วิธีแก้ปัญหาสั้น ๆ จะเป็นดังต่อไปนี้:

@ElementCollection(targetClass = InterestsEnum.class)
@CollectionTable
@Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;

คำตอบแบบยาวคือด้วยคำอธิบายประกอบนี้ JPA จะสร้างตารางหนึ่งตารางที่จะเก็บรายการ InterestsEnum ที่ชี้ไปยังตัวระบุคลาสหลัก (Person.class ในกรณีนี้)

@ElementCollections ระบุว่า JPA สามารถค้นหาข้อมูลเกี่ยวกับ Enum ได้ที่ไหน

@CollectionTable สร้างตารางที่เก็บความสัมพันธ์จาก Person to InterestsEnum

@Enumerated (EnumType.STRING) บอกให้ JPA ยังคงมี Enum เป็น String อาจเป็น EnumType.ORDINAL


ในกรณีนี้ฉันไม่สามารถเปลี่ยนคอลเล็กชันนี้ได้เนื่องจากบันทึกเป็น PersistenceSet ซึ่งไม่เปลี่ยนรูป
Nicolazz92

ความผิดพลาดของฉัน. เราสามารถเปลี่ยนชุดนี้ได้ด้วยเซ็ตเตอร์
Nicolazz92

0

คอลเล็กชันใน JPA หมายถึงความสัมพันธ์แบบหนึ่งต่อกลุ่มหรือแบบกลุ่มต่อกลุ่มและสามารถมีได้เฉพาะเอนทิตีอื่น ๆ ขออภัยคุณต้องห่อ enums เหล่านั้นในเอนทิตี หากคุณคิดเกี่ยวกับเรื่องนี้คุณจะต้องมีฟิลด์ ID และคีย์ต่างประเทศเพื่อจัดเก็บข้อมูลนี้ นั่นคือนอกจากคุณจะทำอะไรบ้าๆเช่นเก็บรายการที่คั่นด้วยจุลภาคใน String (อย่าทำแบบนี้!)


8
ใช้ได้กับ JPA 1.0 เท่านั้น ใน JPA 2.0 คุณสามารถใช้คำอธิบายประกอบ @ElementCollection ดังที่แสดงด้านบน
rustyx
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.