109

Is there a way in JPA to map a collection of Enums within the Entity class? Or the only solution is to wrap Enum with another domain class and use it to map the collection?

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

I am using Hibernate JPA implementation, but of course would prefer implementation agnostic solution.

Greg Mattes
  • 33,090
  • 15
  • 73
  • 105
Gennady Shumakher
  • 5,637
  • 12
  • 40
  • 45

6 Answers6

131

using Hibernate you can do

@ElementCollection(targetElement = InterestsEnum.class)
@JoinTable(name = "tblInterests", joinColumns = @JoinColumn(name = "personID"))
@Column(name = "interest", nullable = false)
@Enumerated(EnumType.STRING)
Collection<InterestsEnum> interests;
jricher
  • 2,663
  • 1
  • 18
  • 17
  • 160
    In case anybody reads this now ... @CollectionOfElements is now deprecated, instead use: @ElementCollection –  Apr 21 '10 at 16:59
  • 2
    You can find a sample in the answser of this question : http://stackoverflow.com/q/3152787/363573 – Stephan Aug 13 '11 at 22:24
  • 1
    Since you mentioned Hibernate, I thought this answer might have been vendor-specific but I don't think it is unless using JoinTable causes trouble in other implementations. From what I've seen, I believe CollectionTable should be used instead. That's what I used in my answer and it works for me (although yes, I am also using Hibernate right now.) – spaaarky21 Mar 05 '12 at 20:51
  • I know this is an old thread, but we're implementing the same kind of thing using javax.persistence. When we add: @ElementCollection(targetClass=Roles.class) @CollectionTable( name="USER_ROLES", joinColumns=@JoinColumn(name="USER_ID") ) @Column( name="ROLE", nullable=false ) @Enumerated(EnumType.STRING) private Set roles; to our User table, things flake out in the entire model package. In our User object, even an error on the Primary key's @Id...@GeneratedValue generator and the first @OneToMany throw goofy errors on build. – LinuxLars Feb 25 '15 at 14:32
  • For what it's worth - the errors I'm seeing are a bug - https://issues.jboss.org/browse/JBIDE-16016 – LinuxLars Feb 25 '15 at 14:59
77

The link in Andy's answer is a great starting point for mapping collections of "non-Entity" objects in JPA 2, but isn't quite complete when it comes to mapping enums. Here is what I came up with instead.

@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;
}
spaaarky21
  • 6,524
  • 7
  • 52
  • 65
  • 5
    Sadly some "admin" decided to delete that answer without giving a reason (about par for the course here). For the reference it is http://www.datanucleus.org/products/accessplatform_3_0/jpa/orm/one_to_many_collection.html#join_nonpc – DataNucleus Apr 24 '13 at 09:52
  • 5
    All you actually need out of this is `@ElementCollection` and `Collection interests;` The rest is potentially useful but unnecessary. For example `@Enumerated(EnumType.STRING)` puts human readable strings in your database. – CorayThan May 08 '13 at 17:08
  • 2
    You are correct - in this example, you could rely on the `@Column`'s `name` being implied. I just wanted to clarify what is implied when @Column is omitted. And @Enumerated is always recommended since ordinal is an awful thing to default to. :) – spaaarky21 May 08 '13 at 19:34
  • I think its worth to mention that you actually need person_interest table – lukaszrys Oct 24 '14 at 14:36
  • 1
    I had to add the joinColumn parameter to make it work `@CollectionTable(name="person_interest", joinColumns = {@JoinColumn(name="person_id")})` – Tiago Aug 31 '15 at 19:00
11

tl;dr A short solution would be the following:

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

The long answer is that with this annotations JPA will create one table that will hold the list of InterestsEnum pointing to the main class identifier (Person.class in this case).

@ElementCollections specify where JPA can find information about the Enum

@CollectionTable create the table that hold relationship from Person to InterestsEnum

@Enumerated(EnumType.STRING) tell JPA to persist the Enum as String, could be EnumType.ORDINAL

Bruno Morais
  • 1,029
  • 11
  • 12
9

I was able to accomplish this in this simple way:

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

Eager loading is required in order to avoid lazy loading inizializing error as explained here.

Community
  • 1
  • 1
megalucio
  • 5,051
  • 2
  • 22
  • 26
  • [`FetchType.EAGER` is never the right answer for lazy-loading problems](https://thorben-janssen.com/lazyinitializationexception/#Don8217t_use_FetchTypeEAGER) because the JPA provider rarely uses a JOIN. As explained in https://stackoverflow.com/a/39129988/759042, `JOIN FETCH` is supported. – aha Sep 16 '21 at 12:12
  • @megalucio does it creates a new table too? – Amrit Raj Jul 19 '22 at 11:14
6

I'm using a slight modification of java.util.RegularEnumSet to have a persistent 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
  // ...
}

This class is now the base for all of my enum sets:

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

And that set I can use in my entity:

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

Advantages:

  • Working with a type safe and performant enum set in your code (see java.util.EnumSet for a description)
  • The set is just one numeric column in the database
  • everything is plain JPA (no provider specific custom types)
  • easy (and short) declaration of new fields of the same type, compared with the other solutions

Drawbacks:

  • Code duplication (RegularEnumSet and PersistentEnumSet are nearly the same)
    • You could wrap the result of EnumSet.noneOf(enumType) in your PersistenEnumSet, declare AccessType.PROPERTY and provide two access methods which use reflection to read and write the elements field
  • An additional set class is needed for every enum class that should be stored in a persistent set
    • If your persistence provider supports embeddables without a public constructor, you could add @Embeddable to PersistentEnumSet and drop the extra class (... interests = new PersistentEnumSet<>(InterestsEnum.class);)
  • You must use an @AttributeOverride, as given in my example, if you've got more than one PersistentEnumSet in your entity (otherwise both would be stored to the same column "elements")
  • The access of values() with reflection in the constructor is not optimal (especially when looking at the performance), but the two other options have their drawbacks as well:
    • An implementation like EnumSet.getUniverse() makes use of a sun.misc class
    • Providing the values array as parameter has the risk that the given values are not the correct ones
  • Only enums with up to 64 values are supported (is that really a drawback?)
    • You could use BigInteger instead
  • It's not easy to use the elements field in a criteria query or JPQL
    • You could use binary operators or a bitmask column with the appropriate functions, if your database supports that
Tobias Liefke
  • 8,637
  • 2
  • 41
  • 58
0

Collections in JPA refer to one-to-many or many-to-many relationships and they can only contain other entities. Sorry, but you'd need to wrap those enums in an entity. If you think about it, you'd need some sort of ID field and foreign key to store this information anyway. That is unless you do something crazy like store a comma-separated list in a String (don't do this!).

cletus
  • 616,129
  • 168
  • 910
  • 942
  • 8
    This is only valid for JPA 1.0. In JPA 2.0 you can use @ElementCollection annotation as shown above. – rustyx Dec 27 '10 at 12:17