In this question I am working with Hibernate 4.3.4.Final and Spring ORM 4.1.2.RELEASE.
I have an User class, that holds a Set of CardInstances like this:
#Entity
#Table
public class User implements UserDetails {
protected List<CardInstance> cards;
#ManyToMany
public List<CardInstance> getCards() {
return cards;
}
// setter and other members/methods omitted
}
#Table
#Entity
#Inheritance
#DiscriminatorColumn(name = "card_type", discriminatorType = DiscriminatorType.STRING)
public abstract class CardInstance<T extends Card> {
private T card;
#ManyToOne
public T getCard() {
return card;
}
}
#Table
#Entity
#Inheritance
#DiscriminatorOptions(force = true)
#DiscriminatorColumn(name = "card_type", discriminatorType = DiscriminatorType.STRING)
public abstract class Card {
// nothing interesting here
}
I have several types of cards, each extending the Card base class and the CardInstance base class respectivly like this:
#Entity
#DiscriminatorValue("unit")
public class UnitCardInstance extends CardInstance<UnitCard> {
// all types of CardInstances extend only the CardInstance<T> class
}
#Entity
#DiscriminatorValue("leader")
public class LeaderCardInstance extends CardInstance<LeaderCard> {
}
#Entity
#DiscriminatorValue("unit")
public class UnitCard extends Card {
}
#Entity
#DiscriminatorValue("leader")
public class LeaderCard extends AbilityCard {
}
#Entity
#DiscriminatorValue("hero")
public class HeroCard extends UnitCard {
// card classes (you could call them the definitions of cards) can
// extend other types of cards, not only the base class
}
#Entity
#DiscriminatorValue("ability")
public class AbilityCard extends Card {
}
If I add a UnitCardInstance or a HeroCardInstance to the cards collection and save the entity everything works fine.
But if I add a AbilityCardInstance to the collection and save the entity it fails with a org.hibernate.WrongClassException. I added the exact exception + message at the bottom of the post.
I read through some questions, and lazy loading seems to be a problem while working with collections of a base class, so here is how I load the User entity before adding the card and saving it:
User user = this.entityManager.createQuery("FROM User u " +
"WHERE u.id = ?1", User.class)
.setParameter(1, id)
.getSingleResult();
Hibernate.initialize(user.getCards());
return user;
The database entries for "cards"
The database entries for "cardinstances"
org.hibernate.WrongClassException: Object [id=1] was not of the specified subclass [org.gwentonline.model.cards.UnitCard] : Discriminator: leader
Thanks in advance for any clues how to fix this problem. If you need additional information I will gladly update my question!
According to the first paragraph of the JavaDocs for #ManyToOne:
It is not normally necessary to specify the target entity explicitly since it can usually be inferred from the type of the object being referenced.
However, in this case, #ManyToOne is on a field whose type is generic and generic type information gets erased at the type of compilation. Therefore, when deserializing, Hibernate does not know the exact type of the field.
The fix is to add targetEntity=Card.class to #ManyToOne. Since Card is abstract and has #Inheritance and #DiscriminatorColumn annotations, this forces Hibernate to resolve the actual field type by all possible means. It uses the discriminator value of the Card table to do this and generates the correct class instance. Plus, type safety is retained in the Java code.
So, in general, whenever there is the chance of a field's type not being known fully at runtime, use targetEntity with #ManyToOne and #OneToMany.
I solved the problem.
The root cause lies in this design:
#Table
#Entity
#Inheritance
#DiscriminatorColumn(name = "card_type", discriminatorType = DiscriminatorType.STRING)
public class CardInstance<T extends Card> {
protected T card;
}
#Entity
#DiscriminatorValue("leader")
public class LeaderCardInstance extends CardInstance<LeaderCard> {
}
At runtime information about generic types of an class are not present in java. Refer to this question for further information: Java generics - type erasure - when and what happens
This means hibernate has no way of determining the actual type of the CardInstance class.
The solution to this is simply getting rid of the generic type and all extending (implementing) classes and just use one class like this:
#Table
#Entity
#Inheritance
#DiscriminatorColumn(name = "card_type", discriminatorType = DiscriminatorType.STRING)
public class CardInstance {
Card card;
}
This is possible (and by the way the better design) because the member card carries all the information about the card type.
I hope this helps folk if they run into the same problem.
Related
I’m trying to map the inheritance from the superclass LendingLine and the subclasses Line and BlockLine. LendingLine has an ManyToOne association with Lending.
When I try to get the LendingLines from the database without the inheritance it works fine. The association works also. But when i add the inheritance, lendingLines in Lending is empty. I also can't get any LendingLines from the DB with the inheritance.
Can anybody help me?
(Sorry for the bad explanation)
Thanks in advance!
LendingLine:
#Entity
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="TYPE")
#DiscriminatorValue(value="Line")
#Table(name = "LendingLine")
public class LendingLine {
...
public LendingLine(){}
#ManyToOne(cascade = CascadeType.ALL,fetch = FetchType.EAGER, targetEntity=Lending.class)
#JoinColumn(name = "LendingId")
private Lending lending;
...
Lending:
#Entity
#Table(name = "Lending")
public class Lending {
...
public Lending(){}
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER, mappedBy = "lending")
private List<LendingLine> lendingLines;
...
BlockDate:
#Entity
#DiscriminatorValue(value = "BlockLine")
public class BlockLine extends LendingLine {
public BlockLine(){
}
}
LendingLineRepository:
This class only reads from the db because the db was created by another application ( C#) where the objects are added to the db.
public class LendingLineRepository extends JpaUtil implement LendingLineRepositoryInterface {
#Override
protected Class getEntity() {
return LendingLine.class;
}
#Override
public Collection<LendingLine> findAll() {
Query query = getEm().createQuery("SELECT l FROM LendingLine l");
System.out.println(query.getResultList().size());
return (Collection<LendingLine>) query.getResultList();
}
Table LendingLine:
Choose your type of superclass according to your needs:
Concrete Class
public class SomeClass {}
Define your superclass as a concrete class, when you want to query it and when you use a new operator for further logic. You will always be able to persist it directly. In the discriminator column this entity has it's own name. When querying it, it returns just instances of itself and no subclasses.
Abstract Class
public abstract class SomeClass {}
Define your superclass as an abstract class when you want to query it, but don't actually use a new operator, because all logic handled is done by it's subclasses. Those classes are usually persisted by its subclasses but can still be persisted directly. U can predefine abstract methods which any subclass will have to implement (almost like an interface). In the discriminator column this entity won't have a name. When querying it, it returns itself with all subclasses, but without the additional defined information of those.
MappedSuperclass
#MappedSuperclass
public abstract class SomeClass {}
A superclass with the interface #MappedSuperclass cannot be queried. It provides predefined logic to all it's subclasses. This acts just like an interface. You won't be able to persist a mapped superclass.
For further information: JavaEE 7 - Entity Inheritance Tutorial
Original message
Your SuperClass LendingLine needs to define a #DiscriminatorValue as well, since it can be instantiated and u use an existing db-sheme, where this should be defined.
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.
I've run into a problem and I don't know, if is possible to solve it. Let me show you my code and explain the situation. I have an abstract class User mapped as superclass.
#MappedSuperclass
public abstract class User extends AbstractEntity {
}
Then I have two classes, Person and Company, extending the superclass.
#Entity
public class Person extends User {
}
#Entity
public class Company extends User {
}
Since this, everything is ok. I have two tables called by the entity names and it works june fine. But, I have some entity called Invoice, where I have to map Person or Company into.
#Entity
public class Invoice extends AbstractEntity {
#ManyToOne()
#JoinColumn(name="user_id", updatable=false)
private Class<? extends User> user;
}
The problem is that I don't know, which implementation of User will be mapped to this Invoice entity. With this solution, Hibernate gives me an error org.hibernate.AnnotationException: #OneToOne or #ManyToOne on com.xxx.user references an unknown entity: java.lang.Class
I understand it, but please, is there any way to implement this behaviour without an exception ? Or am I completely wrong and nothing similar can be done in ORM ?
I cannot use private User user because User is a #MappedSuperclass, not an #Entity.
Thanks !
Well instead of using lower bound, why not just use the type User:
#Entity
public class Invoice extends AbstractEntity {
#ManyToOne()
#JoinColumn(name="user_id", updatable=false)
private User user;
}
Like this, you should still be able to put any class that extends from the User class as the previous declaration. However this will not work as User is not an Entity.
It should however work, if you declare User as entity but set inheritance strategy to TABLE_PER_CLASS.
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstract class User extends AbstractEntity {
}
Hibernate won't see the generic "User" at runtime (type erasure).
Use User instead of Class<? extends User>
Note that even if Hibernate could see the generic type, you'd still have a user Class not a User...
i have a generic class which is supper class of some non-generic class and those are just setting its generic parameter like this:
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
class A<T>{
#Id
getId(){..}
setID(int id){..}
int id
T t;
T getT(){...}
setT(T t){...}
}
and
#Entity
class B extends A<Integer>{}
but hibernate says that B does not have an identifier what should I do?
If A won't be directly persisted, but you do want it's subclasses to pick up some (or all) of its Hibernate annotations, you should use #MappedSuperclass:
#MappedSuperclass
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
class A<T>{
#Id
getId(){..}
setID(int id){..}
int id
T t;
T getT(){...}
setT(T t){...}
}
You need to add the #Entity annotation to class A as well.
The #Transient annotation on attribute t should help with your second exception
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
class A<T> {
#Id
getId(){..}
setID(int id){..}
int id
#Transient
T t;
T getT(){...}
setT(T t){...}
}
I agree with reply No. 1, use #MappedSuperclass for A - don't make something abstract an Entity.
You should probably make this class specifically abstract too.
#MappedSuperclass
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
abstract class A<T>{
#Id
getId(){..}
setID(int id){..}
int id
T t;
T getT(){...}
setT(T t){...}
}
A table-per-class strategy often requires this kind of abstract base.
Then the subclass specifies the table name, and additional fields.
#Entity
#Table(name="MY_INTEGERS")
class B extends A<Integer>{}
(Personally I would move this variable type into the subclass, but I don't know what you're trying to achieve).
After lots of testing, trying to get Java parameterisation working with an abstract parent (Single-table inheritance), and an abstract child table (one-table-per-class inheritance), I've given up.
It may be possible, but often you get problems where Hibernate tries to instantiate an abstract (parameterised) class as an entity. this is when you get the error "A has an unbound type and no explicit target entity."
It means Hibernate doesn't have a parameter value for a parameterised type.
I found that tests for the extending classes were fine, but tests around parent entities would break.
I would suggest rewriting it using the JPA inheritance, moving the parameterised stuff down into extending classes. That way you get the same polymorphism back from the database.
#MappedSuperclass
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "CLASS_TYPE", discriminatorType = DiscriminatorType.STRING)
public abstract class ClassA {
[...]
}
extension B:
#Entity
#DiscriminatorValue=("B")
public class ClassB extends ClassA {
#OneToOne
#JoinColumn(name = "mycolumn_id")
private Integer instance;
[...]
}
extension C:
#Entity
#DiscriminatorValue=("C")
public class ClassC extends ClassA {
#OneToOne
#JoinColumn(name = "mycolumn_id")
private String instance;
[...]
}
I am developing an application using Hibernate and am trying to model the following scenario:
I have an Activity Abstract class, defined as follows:
#Entity
#Inheritance(strategy=InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name="activityType")
#Table(name = "BF_ACTIVITY")
public abstract class Activity extends PersistableObject {
#ManyToOne
#JoinColumn(name = "ASSIGNED_TO")
protected Contactable assignedTo;
#ManyToOne
#JoinColumn(name = "RAISED_BY")
protected Contactable raisedBy;
Note I am using Single Table inheritance (so all implementing objects will use the same table) and have set a DiscriminatorColumn.
Now I have two objects that extend Activity: ToDo and Status:
#Entity
#Table(name = "BF_TODO")
#DiscriminatorValue("Todo")
public class ToDo extends Activity {
...
#Entity
#Table(name = "BF_STATUS")
#DiscriminatorValue("Status")
public class Status extends Activity {
...
Again note that for both implementations i have set a DiscriminatorValue.
Finally, I want to have a Person object, and the person can have a list of Status and a list of ToDo - I also want to capture the bi-directional relationship, so am modelling it using the mappedBy configuration, but in both cases using the "raisedBy" field that exists in the super class "Activity":
public abstract class Person {
#ManyToMany(mappedBy="raisedBy", targetEntity=Activity.class)
private List<ToDo> todoItems = new ArrayList<ToDo>();
As i am using mappedBy with the member variable "raisedBy" from the super class I have also specified the targetEntity (otherwise it is not able to find the field in the ToDo object).
The problem is when I try to call getTodoItems() - it is actually just returning all "Activity" objects linked by "raisedBy" to the current person. e.g. it throws a cast exception because it is expecting the list of ToDos but Hibernate is returning Status objects in the list as well.
I was hoping the mappedBy config along with DiscriminatorValue would be enough to make this work - has anyone come across this or resolved it?
Thanks
EDIT
I have just found this post:
Can someone point me in the direction of a good #Where overview and example? could I just update my person as follows to use #Where with the discriminator column?
public abstract class Person {
#ManyToMany(mappedBy="raisedBy", targetEntity=Activity.class)
#Where(clause="activityType=Todo")
private List<ToDo> todoItems = new ArrayList<ToDo>();
Add the annotation :
#Where(clause = "activityType= 'Todo'")
But it's a hibernate annotation, not JPA