Hibernate manyToOne filter on base entity - java

I have an entity "Event" that has a ManyToOne relationship with the entity "Organization". So an Organization can have multiple events.
What I originally wanted to do was to filter the entity Event using a property of the Organization entity. So basically when I fetch events, only return the events that have an Organization.code= :codeParam.
To accomplish that I implemented a hibernate filter with :
#FilterDef(name="codeFilter", parameters=#ParamDef( name="codeParam", type="string" ) )
...
#ManyToOne
#JoinColumn(name="Organization_Id")
#Filter(name="codeFilter", condition=" code = :codeParam")
private Organization organization;
...
Filter hibernateFilter = sess.enableFilter("codeFilter");
hibernateFilter.setParameter("codeParam", "hola");
Unfortunately according to a post from the Hibernate Team on the hibernate forums, this is not possible :
A Hibernate data filter does not change the multiplicity of an association. By definition it therefore does not filter many-to-one, one-to-one, or any load() or get() operation.
What is it supposed to do, return NULL instead of an instance? NULL does not mean FILTERED, it means NULL. You guys are using filters wrong.
So my question is : is there any way to filter the base entity ("Event") with a condition on the entity from a manyToOne relationship (Organization.code= :codeParam)?
I need this to be enforced every time there is a fetch of events, so a solution using the already existing hibernate filters or something similar would be greatly appreciated.
EDIT1: The question is a simple example of what needs to be done on a significantly bigger scale. Basically, we want to add security to all our Entities and their own nested Entities through the use of a globally defined filter on a Unix permissions row that all our tables have.

WARNING: Do not do this, it is dependent on Hibernate internals and prone to breaking on schema changes, and possibly on variations in individual query setup.
Set Hibernate to show its generated sql, run the query you want to filter (in this case, loading some Event objects), and check what name it assigns to the join used for fetching the related Organization. For example, the generated sql might include inner join Organization someNameHere on this_.Organization_Id = someNameHere.OrganizationId. Then apply the filter, not to the association, but to the Event class, with condition "someNameHere.code = :codeParam".
This is, unfortunately, the only way I've been able to find to filter one class by the properties of an associated class.
I'm trying to make a more robust solution, but it's a complex issue and I'm still in the research stage for that. I expect it will use code generation (through an annotation processor) and programmatic modification of Hibernate's mapping information on startup, but I'm not sure what else yet.

Related

Override lazy OneToMany relationship

Have an entity with a OneToMany relationship in Spring Data that I would need to filter by a specific column. I've been unable to find a satisfying solution to get it with a single query (both parent entity and the filtered OneToMany in one go) so I'm now thinking in having it lazy fetched and after getting the parent entity, have a separate query to retrieve the linked entities and just using a setter to link them together (ie: parent.setChildren(childrenRepository.findAllBy...)).
Question is: would this effectively disable any future trigger to the lazy fetch so my manually set children would remain? Use case here is to add these children manually in certain cases, and using the default lazy fetch when the conditions don't apply.
I tested this and seems to be working as intended, but could not find information on when the lazy fetch should be triggered as to make sure my logic would work in any scenario (is it disabled as soon as the setter is invoked? is this documented anywhere?)
Thanks in advance.

Get PersistentSet from EntityManager or Hibernate Session

Hibernate returns org.hibernate.collection.internal.PersistentSet as Set implementation on #OneToMany relation:
#OneToMany(mappedBy = "group", cascade = CascadeType.PERSIST)
private Set<Student> studentSet;
Hibernate tracks all changes on PersitanceSet (if some entity will is added to Set than it will be inserted into a database and etc.). Is it possible to have the same functionality for collections got by JPA EntityManager, org.hibernate.Session or by another way?
For example:
entityManager.createQuery(query, Student.class)
.setParameter("name", name)
.getResultList();
Doesn't return such kind of collection.
So I am searching the way to get elements by custom query and collect elements into a collection that Hibernates tracks all changes(inserting on adding new transient entities, updating on changing managed entities, deleting on removing from the collection)
What you're asking for is not possible in Hibernate.
Hibernate tracks all changes on PersitanceSet (if some entity will is added to Set than it will be inserted into a database and etc.)
That statement is not really accurate. Hibernate will not automatically insert an entity added to the set into a database. You need to opt in for that functionality specifically by declaring the appropriate cascading option (CascadeType.PERSIST in this case).
What Hibernate will do, however, is track associations between entities. If a collection represents the owning side of a to-many association, changes to the collection will establish/destroy associations between entities. In fact, Hibernate will track all other entity state, not just associations. That's the idea behind managed entities - to be able to work with a domain object just like with any other Java object, and let Hibernate take care about persistence behind the scenes.
A collection retrieved from a query does not represent part of a single entity state. Therefore, there would be little sense for Hibernate to track the structural state of the list. Suppose you made two queries for the same data within a single transaction. You then modify one of the result lists and leave the other intact. What do you think should happen in such a scenario?
Note that by 'not possible', I mean to say that Hibernate does not provide such a functionality out of the box. However, if you want to track changes to an arbitrary list, there are list implementations that allow that (see e.g. Glazed Lists or Apache Commons Events). You could combine them with Hibernate API to get the behavior you want.

How to update an entity property if it is used in a relationship?

Persisting of, updates to and deleting of an entity can be tracked with #Pre/PostPersist, #Pre/PostUpdate and #Pre/PostDelete JPA annotations. I'd like to change an entity property if it has been used in a relationship, i.e. if another entity has used it as a value of a relationship field or added to a relationship collection.
Using #PrePostLoad is difficult because it's hard to exclude loads which are unrelated to usage in relationships.
I'd like to use a pura JPA/provider-portable solution, but I'm curious about provider specific solutions as well.
The concrete idea is let the user create and persist entities (e.g. instances of contact information) in a form and then associate them with another entity created in another form (e.g. a document with a sender property). The (already persisted) available entities for associating are displayed in a table. This table should be sortable by a counter for the most frequent or a timestamp for most recent usage of the entities.
I'm using JPA 2.1 (EclipseLink 2.6.4 currently).
I handled the update in the UI by adding a Storage interface which wraps EntityManager and allows to register pre and post persistence callback (functional interfaces are a real blessing) like
#FunctionalInterface
interface Callback {
void callback(EntityManager e);
This suggestion has been proposed by another user, but then deleted apparently.

Efficient Hibernate criteria for join returning many partial duplicates

I'm fetching a long list of entities which refer to others which refer to... and, at the end, usually of all them refer to a single user as their owner. Not really surprising as what's queried are entities belonging to a single user. There are more parts duplicated in many rows; actually, just a small percentage are unique data. As the query seems to be slow, I though I could gain a bit by fetching things separately using
criteria.setFetchMode(path, FetchMode.SELECT);
This works in my above case, but when querying over many users (as admin), it gets terrible, as hibernate issues a separate query for every user, instead of something like
SELECT * FROM User WHERE id IN (?, ?, ..., ?)
or not fetching them at all (which can't get any worse than one query per entity). I wonder what am I missing?
So instead of fetching a lot of redundant data, I ran into the 1+N problem, where obviously 1+1 queries would do.
Is there a way to instruct Hibernate to use the right query?
Is there a way to prevent Hibernate from fetching the owners by specifying it in the criteria itself (rather than putting fetch=FetchType.LAZY on the field; the laziness should be query-specific)?
I don't think it matters, but my classes are like
class Child {
#ManyToOne Father father;
#ManyToOne Mother mother;
...
}
class Father {
#ManyToOne User owner;
...
}
class Mother {
#ManyToOne User owner;
...
}
and the query is like
createCriteria(Child.class)
.add(Restrictions.in("id", idList))
.add(Restrictions.eq("isDeleted", false))
.createAlias("Father", "f")
.add(Restrictions.eq("f.isDeleted", false))
.setFetchMode("f.owner", FetchMode.SELECT)
.createAlias("Mother", "m")
.add(Restrictions.eq("m.isDeleted", false))
.setFetchMode("m.owner", FetchMode.SELECT)
.list();
The important part is that owner does not get used and can be proxied. The javadoc for FetchMode.SELECT says
Fetch eagerly, using a separate select
so it basically promises 1+1 querying which I want rather than "using a separate select per entity".
Fetch profiles are meant to help you achieve what you want, but are very limited at the time being and you can override the default fetch plan/strategy only with the join-style fetch profiles (you can make a lazy association eager, but not vice versa). However, you could use this trick to invert that behaviour: Make the association lazy by default and enable the profile by default for all sessions/transactions. Then disable the profile in transactions in which you want lazy loading.
IMHO the solution above looks too cumbersome, and the approach I use in most use cases to avoid both loading of redundant data and N+1 selects problem is to make associations lazy and define batch size.
unless the property is declared with
#ManyToOne(fetch=FetchType.LAZY), you can't change anything
True, at least for the time being, until fetch profile capabilities are extended to provide the ability to change eager loading to lazy.
the default is FetchType.EAGER, which is stupid, as it can't be
overridden
True, and I agree that it is bad, but in Hibernate native API everything is lazy by default; it is JPA that mandates to-one associations to be eager unless explicitly specified otherwise.
using criteria.setFetchMode(path, FetchMode.SELECT) is pointless as
it's always a no-op (either it gets ignored because of the
non-overridable eagerness of the property or the property is lazy
already)!
With it you should be able to override other lazy fetch modes. See HHH-980 and this comment from one of the lead Hibernate contributors about the javadoc confusion.
fetching lazily leads by default to the 1+N problem
It has nothing to do with lazy loading specifically, it is the default for eager loading as well if you don't fetch the eagerly loaded association in the same query.
it can be controlled via a class-level #BatchSize annotation
You have to place it on class-level for it to take effect on to-one associations with that entity; this answer is helpful. For collection associations (to-many associations with that entity defined in other entities) you have the flexibility to define it separately for each association.
To summarize my frustration... Hibernate is full of surprises (bugs?) in this respect:
unless the property is declared with #ManyToOne(fetch=FetchType.LAZY), you can't change anything
the default is FetchType.EAGER, which is stupid, as it can't be overridden
using criteria.setFetchMode(path, FetchMode.SELECT) is pointless as it's always a no-op (either it gets ignored because of the non-overridable eagerness of the property or the property is lazy already)!
fetching lazily leads by default to the 1+N problem
it can be controlled via a class-level #BatchSize annotation
placing a #BatchSize annotation on a scalar field gets silently ignored
In order to get what I wanted (two SQL queries), I need just two things:
declare the property with #ManyToOne(fetch=FetchType.LAZY)
place #BatchSize(size=aLot) on the class of the property
That's simple, but a bit hard to find out (because of all the ignored things above). I haven't looked into fetch profiles yet.
I wrote a small project to demonstrate the behavior. The SQL generated from your criteria is as follows:
select
this_.id as id1_0_4_,
this_.father_id as father_i3_0_4_,
this_.isDeleted as isDelete2_0_4_,
this_.mother_id as mother_i4_0_4_,
f1_.id as id1_1_0_,
f1_.isDeleted as isDelete2_1_0_,
f1_.owner_id as owner_id3_1_0_,
user5_.id as id1_3_1_,
user5_.isDeleted as isDelete2_3_1_,
m2_.id as id1_2_2_,
m2_.isDeleted as isDelete2_2_2_,
m2_.owner_id as owner_id3_2_2_,
user7_.id as id1_3_3_,
user7_.isDeleted as isDelete2_3_3_
from
Child this_
inner join
Father f1_
on this_.father_id=f1_.id
left outer join
User user5_
on f1_.owner_id=user5_.id
inner join
Mother m2_
on this_.mother_id=m2_.id
left outer join
User user7_
on m2_.owner_id=user7_.id
where
this_.id in (
?, ?
)
and this_.isDeleted=?
and f1_.isDeleted=?
and m2_.isDeleted=?
Changing the FetchMode in the criteria API did not affect the query. The owner data is still queried.
The Ids are in the "in" clause and Hibernate did not issue separate queries for each Id.
As mentioned in other answers, if the entity relation is set to EAGER, ie JPA default, then you can't change the fetch mode in the Criteria API. The fetch mode needs to be changed to LAZY.
You can see it here

JPA - Redirect one entity Update/Delete statements to a mirror table

I am working with Java EE 7 on a Wildfly server. I have a strange scenario, where the client has two tables - "employees" and "employees_modified". The second table has the exactly same structure as the first one and servers as a modification storage. So if an employee changes his name from "john" to "john-1", we will write to employees_modified
insert into employees_modified(first_name) values("john")
Please note that the other fields in the table "employees_modified" are empty.
The question is: is there a way to somehow map the two tables and overwrite the values from employees by those in employees_modified where they are present.
I looked at #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) and #AttributeOverrides but those solutions don't seem to fit to my scenario.
Look at hibernate envers, it solves your problem simply. Attach envers to your project. Place the first table under audit with annotation #Audited and #AuditTable(value = "employees_modified"). But as pointed by #Predrag Maric it is important to leave other fields of the second table empty, you can use #PostPersist (or listener in pure hibernate) method in entity. In this method you can describe additional logic employees_modified entity creation and persisting.
You can use #SQLUpdate and #SQLDelete to customize the CRUD statements to be redirected to a different table:
#Entity
#SQLUpdate( sql="UPDATE employees_modified SET name = ? WHERE id = ?")
#SQLDelete( sql="DELETE FROM employees_modified WHERE id = ?")
public class Employees {
...
}
If it's only for auditing, I agree with the answers before me (triggers, events etc).
If you actually want to access that "employees_modified" table (e.g. run complex Hibernate queries) than you can use a second persistent unit. The following post: https://developer.jboss.org/thread/237078 seems to indicate that's a recommendation from hibernate. Obviously the 2nd unit will need xml configuration rather than annotation - at least it can't rely on the same #Table annotation.
BTW there's also some documentation about a #SecondaryTable annotation, but it's my understanding that it doesn't match your case (because your business probably needs to treat those tables differently - sometimes you want to view just the history, sometimes just the live data)/

Categories