SINGLE_TABLE inheritance strategy using enums as discriminator value - java

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

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.

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.

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
}

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.

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.

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

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.

Related

Unable to properly compare table columns with different types

I'm trying to use querydsl to build a query which joins two tables. However, a slight discrepancy between the corresponding Java class data types seems to prevent me from directly comparing the two columns values in my query. The data type within the object corresponding to table A is java.util.UUID while the data type of the object corresponding to table B is String.
I have something like the following:
#Table(name = "TABLE_A")
public class TableA {
#Column(name = "uuid")
#Type(type = "uuid-char")
private UUID uuid;
}
#Table(name = "TABLE_B")
public class TableB {
#Column(name = "uuid")
private String uuid;
}
#Service
public class QueryService {
private final JPQLQueryFactory queryFactory;
public UUID getData(UUID input) {
return queryFactory.select(QTableA.tableA.uuid)
.from(QTableA.tableA)
.innerJoin(QTableB.tableB)
.on(QTableB.tableB.uuid.eq(QTableA.tableA.uuid.toString()))
.where(QTableA.tableA.uuid.eq(input))
.fetchOne();
}
}
The above code does not return anything. However, the below code seems to work:
#Service
public class QueryService {
private final JPQLQueryFactory queryFactory;
public UUID getData(UUID input) {
return queryFactory.select(QTableA.tableA.uuid)
.from(QTableA.tableA)
.innerJoin(QTableB.tableB)
.on(QTableA.tableA.uuid.eq(input)
.and(QTableB.tableB.uuid.eq(input.toString()))
.where(QTableA.tableA.uuid.eq(input))
.fetchOne();
}
}
I don't understand why directly comparing the columns doesn't work, but comparing them to a common variable does work. Would QTableA.tableA.uuid.toString() not call the proper toString() method?
Please try:
.on(QTableB.tableB.uuid.toString().eq(QTableA.tableA.uuid.toString()))
Apply toString on QTableB.tableB.uuid
It's looking like a direct comparison is not possible with the current version. Querydsl's type checking is too strict for a direct comparison to be done on these kinds of differing data types.

JPA Inheritance Two Or More Superclass

I made a research about Inheritance in JPA and resources that I found uses just one superclass for each entity. But there is not an example that uses 2 or more superclass.
What about this:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = “Abstract_One”)
public abstract class AbstractOne {
#Id
protected Long id;
…
}
#Entity(name = “A”)
#DiscriminatorValue(“A”)
public class A extends AbstractOne {
#Column
private int a;
…
}
#Entity(name = “B”)
#DiscriminatorValue(“B”)
public class B extends A {
#Column
private int b;
…
}
Is it possible to do that?
If it is possible, which Inheritance Strategy allows that and gives the best data consistency?
I can imagine only the following example
#MappedSuperclass
public class A
{
...
#Id
#Column(name = "RECID")
public Long getId()
...
}
#MappedSuperclass
public class B extends A
{
...
#Column(name = "COL1")
public String getColumn1()
...
}
#Entity(name="INH_TAB1")
public class C extends B
{
...
#Column(name = "COL2")
public String getColumn2()
...
}
Also at the excellent book "Java Persistence with Hibernate" by Bauer, King, Gregory I found the following plase what can be useful in the context of this question:
6.5 Mixing inheritance strategies
You can map an entire inheritance hierarchy with the TABLE_PER_CLASS,
SINGLE_TABLE, or JOINED strategy. You can’t mix them — for example, to switch from a
table-per-class hierarchy with a discriminator to a normalized table-per-subclass
strategy. Once you’ve made a decision for an inheritance strategy, you have to stick with it. This isn’t completely true, however. By using some tricks, you can switch
the mapping strategy for a particular subclass. For example, you can map a class
hierarchy to a single table, but, for a particular subclass, switch to a separate
table with a foreign key–mapping strategy, just as with table-per-subclass.
However, I can not imagine any real case when such complex inheritance hierarchy will be required/useful and also it can affect performance.

Play 2.3: Ebean Inheritance Single-Table with OneToOne field

I have an abstract class to represent a type of settings. The inheritance type is in a single table as I wish to be able to access all types of settings irrespective of concrete type. Here is my parent abstract class:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(discriminatorType = DiscriminatorType.STRING)
public abstract class Settings extends Model {
#Id
public Long settingId;
public static Model.Finder<Long, Settings> find = new Model.Finder<>(Long.class, Settings.class);
public abstract void run();
}
This is one of my concrete types:
#Entity
#DiscriminatorValue("text")
public class TextSettings extends Settings {
public boolean type;
#OneToOne(cascade = CascadeType.ALL)
public EmailFields emailFields;
public static Finder<Long, TextSettings> find = new Finder<>(Long.class, TextSettings.class);
public static TextSettings get() {
if (find.all().size() > 0)
return find.all().get(0);
else {
TextSettings settings = new TextSettings();
settings.emailFields = new EmailFields();
settings.emailFields.test = "Test"; \\this field is null if you try to get this field with a get on the TextSettings ebean object
settings.save();
return settings;
}
}
}
This concrete type actually contains another ebean model with the OneToOne relationship. Here is the code for that model:
#Entity
#DiscriminatorValue("email")
public class EmailFields extends Model {
#Id
public Long id;
public String test;
public static Finder<Long, EmailFields> find = new Finder<>(Long.class, EmailFields.class);
}
When I try to get the EmailFields model through the TextSettings model, I get the correct id and the object exists in the database, but the field test is null. Any field I add to it is always null.
This type of set up works for me in a non-inheritance ebean model so I can only think it has something to do with the single table. Does anyone know a solution for this, or will I have to copy the test field into the TextSettings model?
Note: I have simplified the code so logically it might not make sense as to why I have one field in EmailFields but the assumption is that I do need it as a separate model as some settings will have this model and some won't. So I don't want boilerplate code in those settings' classes.
Update
So for now I am using the #Embedded and #Embeddable annotations.
#Embeddable
public class EmailFields extends Model
And in TextSettings
#Embedded
public EmailFields emailFields;
This simply copies EmailFields' fields into the TextSettings object and not as a separate entity. Only drawback with this is that it increases the size of the table.

Persist an implementation which has no properties

Given the following classes and interface I am wondering how JPa handles it:
public interface Operator{
public String getOperator();
}
Let's stay I then have two implementations of this class:
public class PlusOperation implements Operator, Serializable {
public String getOperator(){
return "+";
}
}
And:
public class MinusOperation implements Operator, Serializable {
public String getOperator(){
return "-";
}
}
There exists a class marked as #Entity which has a property Operator:
#Entity
public class Function {
#Id #GeneratedValue
private Long id;
private Operator operator;
// Other methods & properties omitted for clarity
}
In the Function class therefore, it could hold either a PlusOperation or MinusOperation and neither of these classes have been annotated with #Entity as they don't have any properties that require persisting but they each are different. How does JPA handle this? When persisting a Function object does it automatically (and 'behind the scenes') mark the operator property with the correct Operation implementation?
Just add a JPA AttributeConverter that stores the "operator" field as a String or Integer. That way it doesn't matter one iota whether this other type (which isn't an Entity) doesn't have any properties of its own. You converter is then responsible for deciding what is persisted, and how it is retrieved.

Mapping Multiple Classes to a Table in Hibernate, Without a DTYPE Column

I have two hibernate classes: a base class, and an extended class that has additional fields. (These fields are mapped by other tables.)
For example, I have:
#Entity
#Table(name="Book")
public class A {
private String ID;
private String Name;
// ...
}
#Entity
#Table(name="Book")
public class B extends A {
public String node_ID;
// ...
}
public class Node {
public String ID; // maps to B.node_ID
// ...
}
How do I map this in Hibernate? The hibernate documentation states three types of inheritence configurations: one table per class, one table with a type column, and a join table -- none of which apply here.
The reason I need to do this is because class A is from generic framework that's reused over multiple projects, and class B (and Node) are extensions specific to one project -- they won't be used again. In the future, I may have perhaps a class C with a house_ID or some other field.
Edit: If I try the above pseudo-code configuration (two entities mapped to the same table) I get an error that the DTYPE column doesn't exist. The HQL has a "where DTYPE="A" appended.
This is possible by mapping the #DiscriminatorColumn and #DiscriminatorValue to the same values for both classes; this can be from any column you use that has the same data regardless of which type (not sure if it works with null values).
The classes should look like so:
#Entity
#Table(name="Book")
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="published")
#DiscriminatorValue(value="true")
public class A {
private String ID;
private String Name;
// ...
}
#Entity
#Table(name="Book")
#DiscriminatorValue(value="true")
public class B extends A {
public String node_ID;
// ...
}
For anyone who got here like me and does not want to have the dtype column but instead want to use the same table for more than one entity as is I would recommend using this
Basically you can create a Base like this
#MappedSuperclass
public abstract class BaseBook<T extends BaseBook> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
... any other variables, getters + setters
}
#Entity
#Table(name= "book")
public class BookA extends BaseBook<BookA>{
//Default class no need to specify any variables or getters/setters
}
#Entity
#Table(name= "book")
public class BookB extends BaseBook<BookB>{
#Column(name = "other_field")
private String otherFieldInTableButNotMapedInBase
... Any other fields, getter/setter
}
From the above we have created base super class which does not have any entity or table mapping. We then create BookA to be default with the Entity + Table mapping. From there we can create other Entities all extending from BaseBook but pointing to one table

Categories