Reputation: 20909
In our Database we have a lot of Objects that are shared between Users. For Instance Contacts
, Customers
, Companies
, etc...
There are also a lot of private Objects, that are mapped to one of the shared entity, but should be visible only to the user that created it - for example Notes
Our Customer Entity contains a @OneToMany
Mapping for Notes
and Contacts
@Entity
public Class Customer{
@OneToMany
Set<Contact> contacts;
@OneToMany
Set<Note> notes;
}
Currently I would need to load thausands of notes, just to figure out : The current user has no note for that customer...
So i thought about something to "filter" the collection before loading it - and indeed, Hibernate does support the @Filter
annotation for that purpose (it seems), so, I would need to apply the following annotation as well:
@OneToMany
@Filter(name="userIdFilter", condition=" :userIdFromSession = userId")
Set<Note> notes;
But on everything else, the documentation leaves some questions. Every example now continues with showing how to set the filters value (:userIdFromSession
), when using session.createQuery()
Session session = ...;
session.enableFilter("userIdFilter").setParameter("userIdFromSession", session.getUser().getId());
List results = session.createQuery(...)
But how can I enable the filter (and set the targetvalue) when using the Entity-Manager along with em.find()
, sot the target value is "applied" for every Filter condition in the Object-Tree?
Upvotes: 1
Views: 1556
Reputation: 20909
I managed to solve it. Due to the project layout it is a little more tricky and hacky than expected, but after all the final result is now exactly what I expected from the change:
No matter how i'm going to load entities (Service, Lazy initalization) Hibernate is applying the user-id filter whenever loading private entities.
First, the definition of the Filter
requires an additional annotation: @FilterDef
on one of the entities:
@Entity
@FilterDef(name = "userFilter", parameters = @ParamDef(name = "user", type = "java.lang.Integer"), defaultCondition = "user_id = :user")
public class Customer extends AbstractEntity implements Serializable {
The Filter now can be applied to any Collection in order to let Hibernate apply the filter:
@OneToMany
@Filter(name="userFilter")
Set<Note> notes;
As outlined in the question I'm using the EntityManager which has no exposed methods for the filters. However, since we can get the actual Session
from the EntityManager, I was able to enable the filter like this (This needs to be done everytime you obtain a new EntityManager Instance (or let's say, when the prior Session has been closed)):
org.hibernate.Session sess = (org.hibernate.Session) this.em.getDelegate();
sess.enableFilter("userFilter").setParameter("user", this.session.getCurrentUser().getId());
Now the tricky part: First Hibernate was running in Circles throwing exceptions and not working at all. This is where the project dependent setting comes into play. I'm using a generic Dataservice to handle stuff like fetchAll, fetchByPK, save, ...
So the method(s) looked like this at first:
public <T> LinkedList<T> fetchAll(Class<T> clazz) {
LinkedList<T> result = new LinkedList<>();
org.hibernate.Session sess = (org.hibernate.Session) this.em.getDelegate();
sess.enableFilter("userFilter").setParameter("user", this.session.getCurrentUser().getId());
result = new LinkedList<T>(this.em.createQuery("SELECT x FROM " + clazz.getSimpleName() + " x", clazz).getResultList());
return result;
}
Since I used the very same approach to fetch the User
itself - I ended up with a loop, cause everytime I tried to setup the filter, Hibernate tried to initialize my session and fetching an user object - which in turn tried the same again.
Solution ofc. was to exclude the fetching of User.class
from enabling filters:
public <T> LinkedList<T> fetchAll(Class<T> clazz) {
LinkedList<T> result = new LinkedList<>();
org.hibernate.Session sess = (org.hibernate.Session) this.em.getDelegate();
if (clazz != User.class) {
sess.enableFilter("userFilter").setParameter("user", this.session.getCurrentUser().getId());
} else {
sess.disableFilter("userFilter");
}
result = new LinkedList<T>(this.em.createQuery("SELECT x FROM " + clazz.getSimpleName() + " x", clazz).getResultList());
return result;
}
(this also works along with em.find
)
All my Collections are loaded lazy, and since this might happen during Ajax Requests as well, I needed to use multiple sessions across the project.
To ensure the initializeCollection()
Method used for LazyLoading applies the very Same filters, I had to modify it, again taking into account, that there are LazyLoaded Collections inside the User Object itself.
So, to avoid a endless loop in case of LazyLoading and having all other sessions to use the filter as well, I just applied the principle used in the Generic DataService. (the method to initialize collections is part of the most abstract Entity which is the parent for all entities)
User u = null;
if (this instanceof User) {
u = (User) this;
} else {
u = ContextHelper.getBean("mysession", my.namespace.Session.class).getCurrentUser();
}
//...
newSession.enableFilter("userFilter").setParameter("user", u.getId());
The only drawback of evaluating EL-Expressions (Helper.getBean) from within an entity is that this entity is now literaly wired to be only used when the FacesContext
is available. But for the scope of this Project, that can be guaranteed, so not really a limitation. (this is also more a reason of the way I designed the lazy initialization rather than caused by using filters)
Upvotes: 1