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.
Related
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.
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")
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.
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...
I have an inheritance relationship of entities with joined type.
#Entity
#Table(name = "MSM_SUBSCRIPTION")
#DiscriminatorColumn(name = "SUBSCRIPTIONTYPE", discriminatorType = DiscriminatorType.STRING, length = 100)
class subscription {
}
#DiscriminatorValue("com.xxx.XXXSubscription")
#Table(name = "XXX")
public class XXXSubscription extends Subscription implements Serializable {
}
When I'm trying to use a named query such as
SELECT s.class AS subscriptiontype,
FROM
Subscription s
It is resulting in the following query
select
case
when s1_.subscriptionId is not null then com.xxx.XXXSubscription
when s.subscriptionId is not null then 'Subscription'
end AS subscriptiontype,
from
MSM_SUBSCRIPTION s
left outer join
XXXSubscription s1_
on s.subscriptionId=s6_.subscriptionId
Which throws an error as below.
-ORA-00904: "COM"."xxx"."MMSSUBSCRIPTION": invalid identifier
As I noticed, there are no tags around the case statement in generated query, on manually firing this query with tags around the DiscriminatorValue 'com.xxx.XXXSubscription', the query is running fine.
Can someone please help ??
try this
you can define super class with #inheritance annotation
#Entity
#Table(name = "MSM_SUBSCRIPTION")
**#Inheritance(strategy = InheritanceType.SINGLE_TABLE)**
#DiscriminatorColumn(name = "SUBSCRIPTIONTYPE", discriminatorType = DiscriminatorType.STRING, length = 100)
class subscription {
}
#DiscriminatorValue("com.xxx.XXXSubscription")
#Table(name = "XXX")
public class XXXSubscription extends Subscription implements Serializable {
}