49

Is it possible to use an enum as a discriminator value when using SINGLE_TABLE inheritance strategy?

Pascal Thivent
  • 562,542
  • 136
  • 1,062
  • 1,124
Bauna
  • 649
  • 1
  • 5
  • 6

7 Answers7

62

If what you are trying to achieve is to not to duplicate the discriminator values, there is a simple workaround.

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="FREQUENCY",
    discriminatorType=DiscriminatorType.STRING
)    
public abstract class Event  {
}

@Entity
@DiscriminatorValue(value=Frequency.Values.WEEKLY)
public class WeeklyEvent extends Event {
    …
}

public enum Frequency {
    DAILY(Values.DAILY),
    WEEKLY(Values.WEEKLY),
    MONTHLY(Values.MONTHLY);
    
    private String value;

    …

    public static class Values {
        public static final String DAILY = "D";
        public static final String WEEKLY = "W";
        public static final String MONTHLY = "M";
    }   
}

Nothing to do with Hibernate/JPA really, but better than having to maintain the values in multiple places.

Asa
  • 1,624
  • 15
  • 19
29

I just wanted to improve the great answer of @asa about the workaround. Usually, we often like to use the discriminator column as an attribute of the abstract class, and mapped with an enum of course. We can still use the solution mentioned above and force some consistencies between enum names (used to map the column) and String values (used as discrimnator values). Here is my suggestion:

public enum ELanguage {
  JAVA(Values.JAVA), GROOVY(Values.GROOVY);

  private ELanguage (String val) {
     // force equality between name of enum instance, and value of constant
     if (!this.name().equals(val))
        throw new IllegalArgumentException("Incorrect use of ELanguage");
  }

  public static class Values {
     public static final String JAVA= "JAVA";
     public static final String GROOVY= "GROOVY";
  }
}

And for the entities, here is the code:

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="LANGUAGE_TYPE", discriminatorType=DiscriminatorType.STRING)    
public abstract class Snippet {
   // update/insert is managed by discriminator mechanics
   @Column(name = "LANGUAGE_TYPE", nullable = false, insertable = false, updatable = false) 
   @Enumerated(EnumType.STRING)
   public ELanguage languageType
}

@Entity
@DiscriminatorValue(value=ELanguage.Values.JAVA)
public class JavaSnippet extends Snippet {
    …
}

Still not perfect, but a little bit better, I think.

honk
  • 9,137
  • 11
  • 75
  • 83
Genu
  • 827
  • 6
  • 12
  • The languageType field is null when retrieved from the entity manager so this is not working for me. – dukethrash Sep 19 '21 at 19:47
  • @dukethrash it will be hard to help you without more details. Maybe a fiddle ? Did you checked the column in database ? – Genu Sep 20 '21 at 20:41
8

No, unfortunately you can't.

If you try to use an enum as discriminator value, you'll get a Type Mismatch exception ("cannot convert from MyEnum to String"), as the only discriminator types allowed are String, Char and Integer. Next, I tried using an enum's name and ordinal combined with DiscriminatorType.STRING and DiscriminatorType.INTEGER, respectively. But this didn't work either, as the @DiscriminatorValue annotation (as any other) requires a constant expression:

This doesn't work:

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="FREQUENCY",
    discriminatorType=DiscriminatorType.STRING
)    
public abstract class Event  {}

@Entity
@DiscriminatorValue(value=Frequency.WEEKLY.name())
public class WeeklyEvent extends Event {
    // Exception: The value for annotation attribute DiscriminatorValue.value must be a constant expression
}

Doesn't work either:

@Entity
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="FREQUENCY",
    discriminatorType=DiscriminatorType.INTEGER
) 
public abstract class Event  {}

@Entity
@DiscriminatorValue(value=Frequency.WEEKLY.ordinal())
public class WeeklyEvent extends Event {
    // Exception: The value for annotation attribute DiscriminatorValue.value must be a constant expression
}
marciowerner
  • 508
  • 2
  • 7
  • 7
6

I would suggest to invert the relationship: define the discriminator value as a constant in the entity, then wrap it in the enum:

@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(
    name = "FIELD_TYPE",
    discriminatorType = DiscriminatorType.STRING
)
public class Shape {}

@Entity
@DiscriminatorValue(Square.DISCRIMINATOR_VALUE)
public class Square extends Shape {
    public static final String DISCRIMINATOR_VALUE = "SQUARE";
}

@Entity
@DiscriminatorValue(Circle.DISCRIMINATOR_VALUE)
public class Circle extends Shape {
    public static final String DISCRIMINATOR_VALUE = "CIRCLE";
}

@AllArgsConstructor
public enum FieldType {
    SHAPE(Shape.DISCRIMINATOR_VALUE),
    CIRCLE(Circle.DISCRIMINATOR_VALUE);

    @Getter
    private final String discriminatorValue;
}

Apart from eliminating duplication, this code is also less tightly coupled: entity classes don't depend on enum values and can be added without having to change other code; while the enum can "enumerate" different classes from different sources - depending on the context where it is used.

Qiniso
  • 2,587
  • 1
  • 24
  • 30
Albert Gevorgyan
  • 171
  • 3
  • 10
5

To my knowledge, this is not possible with annotations:

  • discriminator value must be of type String
  • discriminator value must be a compile-time-constant, i.e. return values from methods on enums are not allowed.
Pascal Thivent
  • 562,542
  • 136
  • 1,062
  • 1,124
  • JPA 2.1 hasn't changed this? – Adriano Machado Aug 08 '13 at 16:15
  • It's not JPA, the Java language does not allow you to call methods for annotation values, see: http://docs.oracle.com/javase/specs/jls/se7/html/jls-9.html#jls-9.7 at 9.7.1, read the list: "The type of `V` is assignment compatible (§5.2) with `T`, and furthermore:". So `MyEnum.ENUM_CONST.x()` is invalid, `MyEnum.ENUM_CONST` would be valid, but `T` is `String` here. – TWiStErRob Oct 08 '13 at 17:34
3

yup ,when you define discriminator the annotation's option are name and discrimatorType

@DiscriminatorColumn (name="MYDISCRIMINATOR", discriminatorType= DiscriminatorType.INTEGER)

of which DiscriminatorType can only be:

DiscriminatorType.STRING
DiscriminatorType.CHAR
DiscriminatorType.INTEGER

unfortunate I didn't see this yesterday but well. That's the way it is

  • 3
    And note that `DiscriminatorType` refers to the DB type and not the Java type. `DiscriminatorValue` must still have a number in quotes as a String. – TWiStErRob Oct 08 '13 at 17:36
2

You can use DiscriminatorType.INTEGER, and map each subclass with @DiscriminatorValue("X"), where X must be the ordinal value of the enum (0,1,2,3...).

It must be the value as a constant String. You can't use YourEnum.SOME_VALUE.ordinal(), because annotation attribute values must be constants. Yes, it is tedious. Yes, it is error-prone. But it works.

tetsuo
  • 10,726
  • 3
  • 32
  • 35
  • This is a hacky workaround, however it works, I'd put huge comments in an `enum` which is used as a `DiscriminatorValue`! Also the same works with enum names (with `DiscriminatorType.String` which may be more stable than the ordinal. – TWiStErRob Oct 08 '13 at 17:38