Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement polymorphic JPA entities with generic relations

I'm trying to use JPA 2.0 to create polymorphic entities with generic relations. There should be two tables, an event table and a notification table. Inside those table are concrete entities that are related to one another, like so:

Event  <---------- Notification<X extends Event>
 |                      |
LoginEvent <------ LoginNotification extends Notification<LoginEvent>

Logically this should be possible in hibernate, as it is possible in SQL:

+----------+    +----------+
| Event    |    | Notif    |
+----------+    +----------+
|          |    | Id       |
| Id       | <- | Evt_id   |
| Type     | <- | Type     |
| ...      |    | ...      |
+----------+    +----------+

This is what I have:

@Entity
@Inheritance
public abstract class Event{

...
}

@Entity
public class LoginEvent extends Event{

...
}

@Entity
@Inheritance
public abstract class Notification<X extends Event>{

 @ManyToOne(optional=false, targetEntity=Event.class)
 @JoinColumn
 private X event;

...
}

@Entity
public class LoginNotification extends Notification<LoginEvent>{

...
}

Using this code, I can persist and fetch any Event, Notification, LoginEvent, or NotificationEvent, but it falls down when I try to use the LoginNotification_.event relation in my JPA 2.0 metamodel queries. This issue explains something similar.

public static volatile SingularAttribute<NotificationEntity, EventEntity> event;

When I try to do a join in a criteria query, I get an error:

EntityManager em = getEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<LoginNotification> query = cb.createQuery(LoginNotification.class);
Root<LoginNotification> root = query.from(LoginNotification.class);

//  This line complains: Type mismatch: cannot convert from
//  Join<LoginNotification,Event> to Join<LoginNotification,LoginEvent>
Join<LoginNotification, LoginEvent> join = 
root.join(LoginNotification_.event, JoinType.INNER);

I can get around this error, by adding a new SingularAttribute to the LoginNotification_ metamodel, but this fails in execution:

public abstract class LoginNotification_ extends Notification_ {

    // Adding this Removes Type mismatch error, but causes run-time error
    public static volatile SingularAttribute<LoginNotification, LoginEvent> event; 

    ...
}

According to some posts, generic relations won't work (How to handle JPA annotations for a pointer to a generic interface), but by using a @ManyToOne(optional=false, targetEntity=Event.class) annotation, we can get them to behave. Unfortunately, the generics seem to break the JPA criteria query.

Are there any suggestions on how I can perform this lookup? I can use LoginNotification.getEvent() in my code, but I cannot use LoginNotification_.event in my JPA metamodel joins. What's the alternative to using generics to accomplish this?

@Pascal Thivent - Can you answer this?

like image 690
logan Avatar asked Jan 02 '11 23:01

logan


1 Answers

One solution to this is to avoid using the 'join' function and do a full cross join instead:

EntityManager em = getEntityManager();
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<LoginNotification> query = cb.createQuery(LoginNotification.class);
Root<LoginNotification> notfRoot = query.from(LoginNotification.class);
Root<LoginEvent> eventRoot = query.from(LoginEvent.class);
...
query.where(cb.equals(notfRoot.get(Notification_.event), eventRoot.get(Event_.id)), ...(other criteria));

I would assume that a decent query optimizer should make short work of this, but if anyone has any insight on the efficiency of this approach I would be keen to hear it!

like image 135
idle Avatar answered Sep 28 '22 00:09

idle