Is there a way of inheriting Hibernate's #Where annotation? - java

We have a DB table user that evolved quite a bit and we don't want to load legacy users into the app. Legacy user is identified by user_type column.
If I use following mapping then everything works as expected:
#Entity
#Table(name="user")
#Where("user_type = 2") // 1 is legacy
class User {
#Column(name="user_type")
int type;
}
I need to map user table multiple times and I want to stay DRY. So I thought I can extract #Where bit to a super class and inherit it like so:
#Where("type = 2") // 1 is legacy
abstract class BaseUser {
}
#Entity
#Table(name="user")
class User extends BaseUser {
}
I have a following test (I hope it's self-explanatory enough) that fails though:
#Test
#DbUnitData("legacy_user.xml") // populates DB with 1 user (id=1) with type=1
public void shouldNotGetLegacyUser() {
assertThat(em.find(User.class, 1L)).isNull();
}
Is there a way of inheriting a class with Hibernate's #Where annotation?

What you are really looking for is not the #Where but the #DiscriminatorColumn and #DiscriminatorValue. These annotations allow you to map two #Entity objects to the same table based on a #DiscriminatorColumn.
The Hibernate manual has a paragraph on it:
Mapping inheritance
You would basically create a superclass, BaseUser and two Sub classes, LegacyUser and User:
#Entity
#Table(name = "COM_ORDER")
#DiscriminatorColumn(name = "COM_ORDER_TYPE", discriminatorType = DiscriminatorType.INTEGER)
public class BaseUser {
#Id
private Long id;
<Enter your generic columns here, you do not need to add the user_type column>
}
#Entity
#DiscriminatorValue("1")
public class LegacyUser extends BaseUser {
<Enter your legacy specific fields here>
}
#Entity
#DiscriminatorValue("2")
public class LatestUser extends BaseUser {
<Enter your new and improved user fields here>
}
With this setup, you can easliy expand the number of user types by creating new classes which extend the BaseUser class. You need to keep in mind that the fields on the actual table can only be not-null for fields in the BaseUser class. Fields in the UserType related classes should always be nullable in the database since they will only ever be used by a specific user type.
Edit:
I've edit the example to conform to the setup I'm currently using in my own project. This setup works fine for me.

Related

org.hibernate.WrongClassException on saving an entity via Hibernate

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.

JPA Inheritance Type JOINED Update (changing subtype)

The context of the problem is a product management system for CRUD operations. Common product properties are the same for all products, each concrete product type has some additional fields.
When the user selects the product type, the additional fields are dynamically loaded.
I'm using JPA and have the following inheritance using InheritanceType.JOINED:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class AbstractProduct { ... }
#Entity
#PrimaryKeyJoinColumn(name = "ID", referencedColumnName = "ID")
public class ProductA extends AbstractProduct { ... }
#Entity
#PrimaryKeyJoinColumn(name = "ID", referencedColumnName = "ID")
public class ProductB extends AbstractProduct { ... }
Further on I use JpaRepositories for the CRUD operations.
It works fine for CRUD operation, except when the user wants to change the type of a product. If I have read an oject of type ProductA, is there any possibility to use the JPA update functions to change the product type to ProductB?
edit: When using the repository to save the update, the old entry is not updated (e.g. removing the entry from ProductA, updating the entry in AbstractProduct, inserting the entry in ProductB), instead i tries to insert a new (additional) object.

Hibernate mapping generic class with #MappedSuperClass ancestor

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...

Hibernate (JPA) inheritance mapping of abstract super classes

My data model represents legal entities, such as a Business or a Person. Both are tax-paying entities, and both have a TaxID, a collection of phone numbers, and a collection of mailing addresses.
I have a Java model with two concrete classes that extend an abstract class. The abstract class has properties and collections that are common to both concrete classes.
AbstractLegalEntity ConcreteBusinessEntity ConcretePersonEntity
------------------- ---------------------- --------------------
Set<Phone> phones String name String first
Set<Address> addresses BusinessType type String last
String taxId String middle
Address Phone
------- -----
AbsractLegalEntity owner AbstractLegalEntity owner
String street1 String number
String street2
String city
String state
String zip
I'm using Hibernate JPA Annotations on a MySQL database, with classes like this:
#MappedSuperclass
public abstract class AbstractLegalEntity {
private Long id; // Getter annotated with #Id #Generated
private Set<Phone> phones = new HashSet<Phone>(); // #OneToMany
private Set<Address> address = new HashSet<Address>(); // #OneToMany
private String taxId;
}
#Entity
public class ConcretePersonEntity extends AbstractLegalEntity {
private String first;
private String last;
private String middle;
}
#Entity
public class Phone {
private AbstractLegalEntity owner; // Getter annotated #ManyToOne #JoinColumn
private Long id;
private String number;
}
The problem is that Phone and Address objects need to refer to their owner, which is an AbstractLegalEntity. Hibernate complains:
#OneToOne or #ManyToOne on Phone references an unknown
entity: AbstractLegalEntity
It seems like this would be a fairly common Java inheritance scenario, so I hope that Hibernate would support it. I've tried changing the mapping for AbstractLegalEntity based on a Hibernate forum question, no longer using #MappedSuperclass:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
However, now I get the following error. When reading up on this inheritance mapping type, it looks like I have to use SEQUENCE not IDENTITY, and MySQL doesn't support SEQUENCE.
Cannot use identity column key generation with <union-subclass>
mapping for: ConcreteBusinessEntity
I'm making more progress toward getting things working when I use the following mapping.
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(
name="entitytype",
discriminatorType=DiscriminatorType.STRING
)
I'm thinking I should continue down this path. My concern is that I'm mapping it as an #Entity when I really don't ever want an instance of AbstractLegalEntity to ever exist. I'd like to know if this is the right approach. What is the correct approach I should be taking for this situation?
Use:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
AbstractLegalEntity
In the database you will have one table for AbstractLegalEntity, and tables for classes, which extend AbstractLegalEntity class. You won't have instances of AbstractLegalEntity if it's abstract. Polymorphism can be used here.
When you use:
#MappedSuperclass
AbstractLegalEntity
#Entity
ConcretePersonEntity extends AbstractLegalEntity
This will create only one table in your database called ConcretePersonEntity, containing columns from both classes.
Add #Entity annotation to AbstractLegalEntity. Instance of AbstractLegalEntity will never exist - hibernate will load appropriate extending instances - ConcreteBusinessEntity or ConcretePersonEntity according to Id field.
You have to declare AbstracLegalEntity as an #Entity. Even with the #Entity annotation, your class remains abstract. consequently, you will only have instance of concrete subclasses.

Why #OneToMany does not work with inheritance in Hibernate

#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Problem {
#ManyToOne
private Person person;
}
#Entity
#DiscriminatorValue("UP")
public class UglyProblem extends Problem {}
#Entity
public class Person {
#OneToMany(mappedBy="person")
private List< UglyProblem > problems;
}
I think it is pretty clear what I am trying to do. I expect #ManyToOne person to be inherited by UglyProblem class. But there will be an exception saying something like: "There is no such property found in UglyProblem class (mappedBy="person")".
All I found is this. I was not able to find the post by Emmanuel Bernard explaining reasons behind this.
Unfortunately, according to the Hibernate documentation "Properties from superclasses not mapped as #MappedSuperclass are ignored."
Well I think this means that if I have these two classes:
public class A {
private int foo;
}
#Entity
public class B extens A {
}
then field foo will not be mapped for class B. Which makes sense. But if I have something like this:
#Entity
public class Problem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
#Entity
public class UglyProblem extends Problem {
private int levelOfUgliness;
public int getLevelOfUgliness() {
return levelOfUgliness;
}
public void setLevelOfUgliness(int levelOfUgliness) {
this.levelOfUgliness = levelOfUgliness;
}
}
I expect the class UglyProblem to have fileds id and name and both classes to be mapped using same table. (In fact, this is exactly what happens, I have just checked again). I have got this table:
CREATE TABLE "problem" (
"DTYPE" varchar(31) NOT NULL,
"id" bigint(20) NOT NULL auto_increment,
"name" varchar(255) default NULL,
"levelOfUgliness" int(11) default NULL,
PRIMARY KEY ("id")
) AUTO_INCREMENT=2;
Going back to my question:
I expect #ManyToOne person to be inherited by UglyProblem class.
I expect that because all other mapped fields are inherited and I do not see any reason to make this exception for ManyToOne relationships.
Yeah, I saw that. In fact, I used Read-Only solution for my case. But my question was "Why..." :). I know that there is an explanation given by a member of hibernate team. I was not able to find it and that is why I asked.
I want to find out the motivation of this design decision.
(if you interested how I have faced this problem: I inherited a project built using hibernate 3. It was Jboss 4.0.something + hibernate was already there (you'd download it all together). I was moving this project to Jboss 4.2.2 and I found out that there are inherited mappings of "#OneToMany mappedBy" and it worked fine on old setup...)
In my case I wanted to use the SINGLE_TABLE inheritance type, so using #MappedSuperclass wasn't an option.
What works, although not very clean, is to add the Hibernate proprietary #Where clause to the #OneToMany association to force the type in queries:
#OneToMany(mappedBy="person")
#Where(clause="DTYPE='UP'")
private List< UglyProblem > problems;
I think it's a wise decision made by the Hibernate team. They could be less arrogante and make it clear why it was implemented this way, but that's just how Emmanuel, Chris and Gavin works. :)
Let's try to understand the problem. I think your concepts are "lying". First you say that many Problems are associated to People. But, then you say that one Person have many UglyProblems (and does not relate to other Problems). Something is wrong with that design.
Imagine how it's going to be mapped to the database. You have a single table inheritance, so:
_____________
|__PROBLEMS__| |__PEOPLE__|
|id <PK> | | |
|person <FK> | -------->| |
|problemType | |_________ |
--------------
How is hibernate going to enforce the database to make Problem only relate to People if its problemType is equal UP? That's a very difficult problem to solve. So, if you want this kind of relation, every subclass must be in it's own table. That's what #MappedSuperclass does.
PS.: Sorry for the ugly drawing :D
Unfortunately, according to the Hibernate documentation "Properties from superclasses not mapped as #MappedSuperclass are ignored." I ran up against this too. My solution was to represent the desired inheritance through interfaces rather than the entity beans themselves.
In your case, you could define the following:
public interface Problem {
public Person getPerson();
}
public interface UglyProblem extends Problem {
}
Then implement these interfaces using an abstract superclass and two entity subclasses:
#MappedSuperclass
public abstract class AbstractProblemImpl implements Problem {
#ManyToOne
private Person person;
public Person getPerson() {
return person;
}
}
#Entity
public class ProblemImpl extends AbstractProblemImpl implements Problem {
}
#Entity
public class UglyProblemImpl extends AbstractProblemImpl implements UglyProblem {
}
As an added benefit, if you code using the interfaces rather than the actual entity beans that implement those interfaces, it makes it easier to change the underlying mappings later on (less risk of breaking compatibility).
I think you need to annotate your Problem super-class with #MappedSuperclass instead of #Entity.
I figured out how to do the OneToMany mappedBy problem.
In the derived class UglyProblem from the original post. The callback method needs to be in the derived class not the parent class.
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#ForceDiscriminator
public class Problem {
}
#Entity
#DiscriminatorValue("UP")
public class UglyProblem extends Problem {
#ManyToOne
private Person person;
}
#Entity
public class Person {
#OneToMany(mappedBy="person")
private List< UglyProblem > problems;
}
Found the secret sauce for using Hibernate at least. http://docs.jboss.org/hibernate/stable/annotations/api/org/hibernate/annotations/ForceDiscriminator.html The #ForceDiscriminator makes the #OneToMany honor the discriminator
Requires Hibernate Annotations.
In my opinion #JoinColumn should at least provide an option to apply the #DiscriminatorColumn = #DiscriminatorValue to the SQL "where" clause, although I would prefer this behaviour to be a default one.
I am very surprised that in the year 2020 this is still an issue.
Since this object design pattern is not so rare, I think it is a disgrace for JPA not yet covering this simple feature in the specs, thus still forcing us to search for ugly workarounds.
Why must this be so difficult? It is just an additional where clause and yes, I do have a db index prepared for #JoinColumn, #DiscriminatorColumn combo.
.i.. JPA
Introduce your own custom annotations and write code that generates native queries. It will be a good exercise.

Categories