How to access discriminator column in JPA - java

I have DisseminationArea as subcalss for Feature with the following code:
#Entity
#Table(name = "features")
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "subtype_id", discriminatorType = DiscriminatorType.INTEGER)
public class Feature {
#Id
#Column(name="id")
#GeneratedValue(generator="sqlite")
#TableGenerator(name="sqlite", table="sqlite_sequence",
pkColumnName="name", valueColumnName="seq",
pkColumnValue="features")
#Getter
#Setter
private long id;
#ManyToOne
#JoinColumn(name = "subtype_id")
#Getter
#Setter
private FeatureSubtype featureSubtype;
#ManyToOne
#JoinColumn(name = "parent_id")
#Getter
#Setter
private Feature parent;
...
}
Unfortunately, this causes an exception when save this entity to database, because subtype_id field is used twice.
Can I annotate it somehow so that JPA know it is the same field?

If a discriminator column makes sense with InheritanceType.JOINED is worth discussing. I would tend to omit it on joined strategy and only use it with InheritanceType.SINGLE_TABLE.
Nevertheless, it's possible to map it without duplicate column errors.
If your superclass entity has an inheritance / discriminator definition like:
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "subtype_id", discriminatorType = DiscriminatorType.INTEGER)
You just have to adjust the mapping to not update the value as usual by setting it readonly:
#Column(name="subtype_id", insertable = false, updatable = false)
protected int subTypeId;
public int getSubTypeId() {
return subTypeId;
}
Now you can access the discriminator value of the entity.

Same column name is used for relation FK to FeatureSubtype.
Use other name for discriminator or don't use discriminator at all.
In Hibernate, discriminator column on joined inheritance is supported but not required. Hibernate is querying all subtables to determine correct subclass.

Or use for example:
th:text="${OBJECTNAME.class.getSimpleName()}"
This is far simple the using #DiscriminatorColumn...

Related

#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) problem with #Id and sequence

I am facing troubles with #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) and autogenerated table Ids.
Here below is my model:
#Getter
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
#DiscriminatorColumn(name = "type")
#SuperBuilder
#NoArgsConstructor
public abstract class MenuEntity {
#Id
#EqualsAndHashCode.Include
#GeneratedValue(strategy = GenerationType.TABLE)
private Long id;
//...
}
#Getter
#Entity
#Table(name = "tasting_menu")
#SuperBuilder
#NoArgsConstructor
public class TastingMenuEntity extends MenuEntity {
//...
}
#Getter
#Setter
#Entity
#Table(name = "simple_menu")
#SuperBuilder
#NoArgsConstructor
public class SimpleMenuEntity extends MenuEntity {
//...
}
In development process, I have defined a data.sql scripts which inserts test data into my H2 inmemory database. Those scripts will work fine only if I specify the ID value in the statement, in other case I get NULL not allowed for column "ID". Thus, I have to set the ID in the insert statement (the issue does not appear in other entities with no inheritance strategy and #GeneratedValue(strategy = GenerationType.IDENTITY)).
A new problem comes: when the application tries to insert a SimpleMenuEntity on execution time an exception is thrown: org.h2.jdbc.JdbcSQLException: Unique index or primary key violation: "PRIMARY KEY ON PUBLIC.SIMPLE_MENU(ID) because the sequence is still value 1 even it already has some test data.
I have some questions so far.
1. Is my model right?
I have to use #Entity instead of #MappedSuperclass in my abstract class because it has #ManyToOne annotation.
ID property is common for all child tables, but I would like that each table has its own id sequence.
2. How should I handle the autogenerated ID for SQL INSERT statements?
I definitive need the test data in my application for development porpouse.

Relationship with a subclass of SINGLE_TABLE inheritance

I translated some hbm configuration to annotated java class. In the hbm some classes were defined with inheritance strategy "SINGLE_TABLE" and some other entity refer to it with many to one relationship as Map.
when I try to lauch the application I get the following error :
Caused by: org.hibernate.AnnotationException: Map key property not found: com.package.MyClass.Id
I searched for some explanation online, but nothing describing at the same time the SINGLE_TABLE inheritance strategy and the OneToMany behavior in this case.
I have the parent class as follows :
#Entity
#Table(name = "parentclass")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "type", length = 10, discriminatorType = DiscriminatorType.INTEGER)
#DiscriminatorValue("100")
public abstract class ParentClass {
#Id
#Column(name = "Id", length = 11)
#GeneratedValue(strategy=GenerationType.AUTO)
private Integer id;
....
}
the child class :
#Entity
#DiscriminatorValue("2")
public abstract class ChildClass {
....
}
the class with the relation :
#Entity
#Table(name = "otherclass")
#PrimaryKeyJoinColumn(name = "IdSys")
public class OtherClass extends OtherParent {
....
#OneToMany
#JoinColumn(name = "IdOther")
#MapKey(name = "Id")
#Where(clause = "type = 2")
private Map<String, ChildClass> childClassMap;
....
}
It worked when it was defined in hbm so I guess it should work with annotation.
I finally find out what was the issue.
In hbm file, the MapKey name refer to the column name. But the annotation refer to the field name.
So instead of
#MapKey(name = "Id")
I must have
#MapKey(name = "id")

Hibernate database migration - merging duplicate entities

Premise:
I have been recently assigned to work with a Java + JPA + Hibernate application.
This application has 4 different "modules" and each is a "copy+paste" of each other, with minor changes.
I want to remove all duplications and work with a single database schema (currently, there is one schema for each "module").
I am trying to start in the least "invasive" way, not changing too much of what's already there.
What I did was:
I created a base module and moved some hibernate entities there.
I made these entities abstract and created implementations for each module.
I create a new schema and moved every other module record to it (I had to disable the database constraints for now).
Example:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "SYSTEM", length = 10, discriminatorType = DiscriminatorType.STRING)
#Table(name = "GROUP")
public abstract class Group<U extends UserGroup> {
#Id
private Integer id;
#Column(name = "CODE")
private String code;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "group")
private List<U> users;
#Entity
#Table(name = "USER_GROUP")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "SYSTEM", length = 10, discriminatorType = DiscriminatorType.STRING)
public abstract class UserGroup<G extends Group> {
#Id
private Integer id;
#Column(name = "name")
private String name;
#ManyToOne(optional=false)
#JoinColumn(name = "GROUP_ID", referencedColumnName = "ID", insertable=false, updatable=false)
private G group;
The implementations just define a discriminator column.
Issue:
The following query:
public interface UserGroupRepository<T extends UserGroup> extends CrudRepository<T, Integer> {
#Query(value = "select grp.code from #{#entityName} ug join ug.group grp where ug.name= ?1")
Iterable<String> listGroupByUser(String name);
Is returning 4 items because my user has a record for each module in the database (it should return only 1 item).
Question:
Using "#Query", can I somehow filter by the discriminator value properly?
According to Hibernate Inheritance doc, you can get this by table per subclass instead of table per class hierarchy strategy.
Yes you can try using the where clause. There is a special class property that you can use to restrict a query to a subtype. Below is the hibernate reference documentation.
In your case you can use the subEntity name instead of DomesticCat.
Click here to see the hibernate documentation of the where clause.

Bi-directional one-to-many with inheritance not working (jpa with hibernate 3.5.4)

I am trying to map a bi-directional one-to-many relationship. I am having some trouble as the "many" side references an abstract superclass. While searching the internet for possible causes I discovered that this is a known problem but I wasn't able to find a solution for my case.
I have checked the workarounds on this blog and the "Single table, without mappedBy" looks like a solution but I really need the bi-directional association.
These are the classes I am trying to map:
Owning Side
#Entity(name = "CC_Incl_Site")
public class IncludedSite {
#OneToMany(fetch=FetchType.LAZY, mappedBy = "includedSite")
private Set<CtaContractBase> ctas = new HashSet<CtaContractBase>();
#OneToMany(fetch=FetchType.LAZY, mappedBy = "includedSite")
private Set<WoContractBase> wos = new HashSet<WoContractBase>();
}
Other Side:
#Entity
public abstract class SCContract extends Contract {
#ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.LAZY)
#JoinColumn(name = "incl_site_id")
private IncludedSite includedSite;
}
Contract (the superclass of SCContract):
#Entity(name = "CC_CONTRACT")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "contractType", discriminatorType = DiscriminatorType.STRING)
#ForceDiscriminator
public abstract class Contract {
...
}
When trying to run the application I get this exception:
mappedBy reference an unknown target entity property:
CtaContractBase.includedSite in IncludedSite.ctas
Another solution appears to be replacing the #Entity annotation in SCContract with #MappedSuperClass but this results in another exception (Use of #OneToMany or #ManyToMany targeting an unmapped class: StudyContract.contracts[SCContract]) because in another class (StudyContract) I have
#OneToMany(fetch = FetchType.LAZY, mappedBy = "studyContract", targetEntity = SCContract.class)
#BatchSize(size = 10)
private Set<SCContract> contracts;
and as the blog explains having a collection of the superclass is not possible anymore using this approach.
Are there any other workarounds or am I missing something?
The association in IncludedSite is defined as
#OneToMany(fetch=FetchType.LAZY, mappedBy = "includedSite")
private Set<CtaContractBase> ctas = new HashSet<CtaContractBase>();
So Hibernate looks for an attribute of type IncludedSite named includedSite in the class CtaContractBase. There is no such field. The field only exists in the subclass SCContract. This means that only SCContract instances can be the target of this association, and the association should thus be defined as
#OneToMany(fetch=FetchType.LAZY, mappedBy = "includedSite")
private Set<SCContract> ctas = new HashSet<SCContract>();

Hibernate won't add Discriminator Column to Table

I previously had this entity
#Entity
#Table(name = "SOMETABLE")
#AccessType("field")
public class SomeEntity implements java.io.Serializable {
Then I changed it to this
#Entity
#Table(name = "SOMETABLE")
#AccessType("field")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING, length = 20)
#DiscriminatorValue(value = "Some")
public class SomeEntity implements java.io.Serializable {
and I added some subclasses
#Entity
#DiscriminatorValue(value = "SomeOther")
public class SomeOtherEntity extends SomeEntity {
Hibernate does not add a new Discriminator column to SOMETABLE for rows that were previously there.
It is adding other new columns I have defined in SomeEntity. #ForceDiscriminator didn't seem to do anything. How do I get the column to show up in a clean way?
I answered my own question. Hibernate can't add a new not-null column and the Discriminator is a not-null column. A way to fix it is to add columnDefinition to specify a default
#DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING, columnDefinition = "varchar default 'SomeOther'" , length = 20)
So this would default all the rows that don't have the discriminator to be of the type SomeOtherEntity.
However, this is not a good solution because it forces you to use varchar which may be database specific. So thanks Hibernate for not allowing to change a declared entity into a superclass.

Categories

Resources