Hibernate: Unable to eagerly fetch child collection of a child collection - java

I have a Session class, which has a mapped collection of Photo objects:
#OneToMany(fetch = FetchType.EAGER)
#Fetch(FetchMode.SUBSELECT)
#JoinColumn(name = "sessionId", insertable = false, updatable = false)
private SortedSet<Photo> photos = new TreeSet<>();
Each Photo in turn has a collection of Comment objects, which is designated as being FetchType.LAZY (because I don't always want them):
#OneToMany(cascade = CascadeType.DETACH, fetch = FetchType.LAZY)
#BatchSize(size=20)
#Fetch(FetchMode.SUBSELECT)
#JoinColumn(name = "photoId", insertable = false, updatable = false)
private SortedSet<Comment> comments = new TreeSet<>();
In my query for Sessions, I'd like to be able to programmatically decide, on-the-fly, whether to include the Comments or not. I've tried (among other variations):
Criteria criteria = hibSession.createCriteria(Session.class, "s")
.createAlias("photos", "p")
.setFetchMode("p.comments", FetchMode.JOIN);
But that doesn't do it. Calling photo.getComments() on any of the returned Photo sub-objects throws a LazyInitializationException.
If I (still within the scope of the original Hibernate session) iterate over all the Sessions, and within that all the Photos, and call photo.getComments().size(), that will fetch the Comments (in batches, as specified).
But is there any way to tell the initial query to just eagerly get all the Comments the first time around, without the need to iterate afterwards?
Thanks.

It's probably well known Hibernate bug HHH-3524, setFetchMode doesn't work as expected in Criteria queries. It was closed as stale issue, but some users report it for Hibernate versions 4.x.x.
To fix that you can use HQL since it works properly:
session.createQuery("SELECT s FROM PhotoSession s " +
"JOIN FETCH s.photos p " +
"JOIN FETCH p.comments");
Or use workaround solution with createCriteria(associationPath, JoinType.LEFT_OUTER_JOIN):
Criteria criteria = session.createCriteria(PhotoSession.class, "s");
criteria.createAlias("photos", "p");
criteria.createCriteria("p.comments", "c", JoinType.LEFT_OUTER_JOIN);
criteria.setFetchMode("c", FetchMode.JOIN);

Related

How to perform soft deletes on an association mapping table in Hibernate?

So although I personally hate soft deletes, im working in a project for which every table must only soft delete. Im not sure how to handle soft deletes on an association table, the field for which looks like this:
#ManyToMany(targetEntity = AdvertisementVendor.class, fetch = FetchType.EAGER)
#JoinTable(name = "advertisement_version_advertisement_vendor_association",
joinColumns = #JoinColumn(name = "advertisement_version_id"),
inverseJoinColumns = #JoinColumn(name = "advertisement_vendor_id"))
private Set<AdvertisementVendor> _advertisement_vendors = new HashSet<>();
I've seen how to do soft deletes, but I'm not sure how I would apply that to the association table.
UPDATE:
Taking Dragan Bozanovic's advice I updated my column to:
#ManyToMany(targetEntity = AdvertisementVendor.class, fetch = FetchType.EAGER)
#JoinTable(name = "advertisement_version_advertisement_vendor_association",
joinColumns = #JoinColumn(name = "advertisement_version_id"),
inverseJoinColumns = #JoinColumn(name = "advertisement_vendor_id"))
#WhereJoinTable(clause = "is_deleted = 0")
#SQLDelete(sql = "UPDATE advertisement_version_advertisement_vendor_association SET is_deleted = 1 WHERE advertisement_version_id = ? AND advertisement_vendor_id = ?", check = ResultCheckStyle.COUNT)
#SQLInsert(sql = "INSERT INTO advertisement_version_advertisement_vendor_association " +
"(advertisement_version_id, advertisement_vendor_id, is_deleted) VALUES(?, ?, 0) " +
"ON DUPLICATE KEY UPDATE is_deleted = 0")
private Set<AdvertisementVendor> _advertisement_vendors = new HashSet<>();
But this doesnt seem to be working. It seems to ignore #SQLDelete and just removes the mapping.
UPDATE 2:
Ignore the first update, it had to do with different code. The above example works as is.
You can use #WhereJoinTable for filtering conditions on join tables:
Where clause to add to the collection join table. The clause is
written in SQL. Just as with Where, a common use case is for
implementing soft-deletes.
I had a similar problem, and while your solution does work, I also had to add an #SQLDeleteAll annotation in addition to #SQLDelete.
The problem I had is it was still calling the default delete statement when I cleared all entries from the HashSet or deleted the parent object.
Apologies if my terminology is a little off, I'm still pretty new to Hibernate.

When use the JPA innerJoin, How can i change 'and' to 'or'?

When mapping two or more columns in innerjoin there is a map to the conditions and
Can I change or a condition?
Parent table
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "user")
private List<KeyboxDept> keyboxDept;
Child table
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "KEY_TARGET_ID", referencedColumnName = "DEPT_ID", insertable = false, updatable = false),
#JoinColumn(name = "KEY_TARGET_UPPER_ID", referencedColumnName = "DEPT_ID", insertable = false, updatable = false)
})
private User user;
Query is made
select
user0_.user_id as user_id1_3_,
user0_.dept_id as dept_id2_3_,
user0_.posi_id as posi_id3_3_
from
cm_user user0_
inner join
cm_keybox_dept keyboxdept2_
on user0_.dept_id=keyboxdept2_.key_target_id
and user0_.dept_id=keyboxdept2_.key_target_upper_id
where
user0_.user_id=? limit ?
Can i switch and -> or ???
I am not sure about JPA but In Hibernate Using Criteria we can do this....
Criteria criteria = session.createCriteria(persistentClass);
criteria.createCriteria("propertyName", Criteria.LEFT_JOIN);
You have cascade = CascadeType.ALL in entity definition. That means, The operations that must be cascaded to the target of the association. So the target (KeyboxDept in your code) must exist. If you remove it, means no operations being cascaded, I think the generated sql will have no inner.
If your search criteria is not a foreign key matching to the joined table key then you cannot use this syntax because there is obviously an AND relationship between the separate column parts (#JoinColumn elements) of the foreign key. This is why the generated native SQL contains the AND. If you need the OR, you need to initialize that member with a separate JPQL query which contains the OR and you do not need the annotation #ManyToOne.

Hibernate left join constraint in a many-to-many relationship

I have built a list of taggable documents, with a many-to-many relationship between the tags and the documents. I would now like to use the hibernate criteria mechanism to query a "summary" of each tag, which includes a count of how often a particular tag has been used, with an additional restriction on whether or not the document has been published.
The entities I'm using roughly look like this (You'll note an SQL join table in the middle there):
#Entity
public class DocumentTag {
... various things ...
#ManyToMany(fetch = FetchType.LAZY, mappedBy = "tags")
private List<Document> documents = new ArrayList<>();
}
#Entity
public class Document {
... various things ...
#Basic
#Column(name = "published", columnDefinition = "BIT", length = 1)
protected boolean published = false;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "document_tag_joins",
uniqueConstraints = #UniqueConstraint(
columnNames = {"document", "tag"}
),
joinColumns = {#JoinColumn(name = "document")},
inverseJoinColumns = {#JoinColumn(name = "tag")})
private List<DocumentTag> tags = new ArrayList<>();
}
Given the above, I've managed to figure out that building the query should work more or less as follows:
Criteria c = session.createCriteria(DocumentTag.class);
c.createAlias("documents", "docs",
JoinType.LEFT_OUTER_JOIN,
Restrictions.eq("published", true)
);
c.setProjection(
Projections.projectionList()
.add(Projections.alias(Projections.groupProperty("id"), "id"))
.add(Projections.alias(Projections.property("createdDate"), "createdDate"))
.add(Projections.alias(Projections.property("modifiedDate"), "modifiedDate"))
.add(Projections.alias(Projections.property("name"), "name"))
.add(Projections.countDistinct("docs.id"), "documentCount"));
// Custom response entity mapping
c.setResultTransformer(
Transformers.aliasToBean(DocumentTagSummary.class)
);
List<DocumentTagSummary> results = c.list();
Given the above, the hibernate generated SQL query looks as follows:
SELECT
this_.id AS y0_,
this_.createdDate AS y1_,
this_.modifiedDate AS y2_,
this_.name AS y3_,
count(DISTINCT doc1_.id) AS y5_
FROM tags this_
LEFT OUTER JOIN tag_joins documents3_
ON this_.id = documents3_.tag AND (doc1_.published = ?)
LEFT OUTER JOIN documents doc1_
ON documents3_.document = doc1_.id AND (doc1_.published = ?)
GROUP BY this_.id
As you can see above, the publishing constraint is applied to both of the left outer joins. I'm not certain whether that is by design, however what I need is for the published constraint to be applied ONLY to the second left outer join.
Any ideas?
I was able to circumvent this problem by coming at it sideways. First, I had to change the "published" column to use an integer rather than a bit. Then I was able to slightly modify the projection of the result as follows:
// Start building the projections
ProjectionList projections =
Projections.projectionList()
.add(Projections.alias(
Projections.groupProperty("id"), "id"))
.add(Projections.alias(
Projections.property("createdDate"),
"createdDate"))
.add(Projections.alias(
Projections.property("modifiedDate"),
"modifiedDate"))
.add(Projections.alias(
Projections.property("name"), "name"));
if (isAdmin()) {
// Give the raw count.
projections.add(Projections.countDistinct("docs.id"), "documentCount");
} else {
// Use the sum of the "published" field.
projections.add(Projections.sum("docs.published"), "documentCount");
}
I acknowledge that this doesn't actually answer the question about why hibernate criteria constraints on many-to-many tables get applied to all tables, but it solved my problem.

#ManyToMany not all values obtained from DB

I have in project user which have authorities. Authorities types stored in database and linked with user as many to many relation.
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_authority", joinColumns = #JoinColumn(name = "uauth_user"), inverseJoinColumns = #JoinColumn(name = "uauth_authority"))
private Set<Authority> userAuthorities;
But when I try to get all authorities of selected user I obtain just one of them. Hibernate just get first of them and put it to list, but ignore all other authorities of user.
I already check database and it store this data. Also I found solution with adding one not JPA annotations. It works with #Fetch(FetchMode.SUBSELECT) but I still don't understand what is wrong with it.
The following solution works on any JPA 2.0 - compliant implementation.
Table user has primary key id.
Table authority has primary key id.
Table user_authority has fields user_id, authority_id.
Entity class User
#JoinTable(name = "user_authority",
joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "authority_id", referencedColumnName = "id")})
#ManyToMany(fetch = FetchType.EAGER)
private Set<Authority> authoritySet;
Entity class Authority
#ManyToMany(mappedBy = "authoritySet", fetch = FetchType.EAGER)
private Set<User> userSet;
The Table user_authority doesn't have an Entity represention in JPA.
Ok problem was in different place.
My fault was that when I tried it with Hibernate annotation and it is just works I started thinking that this is an annotation problem, but actually it was caused with method of obtaining this values.
While refactoring we leae in code one mistake:
return (T) criteria.setMaxResults(1).uniqueResult();
He we set maximal count of results as 1 and in our case it converted to SQL as
SELECT * FROM user OUTER JOIN user_authority ON usr_id = uauth_user INNER JOIN authority ON uauth_authority = auth_id LIMIT 1
This limit removes all authorities, and leave just first authority from first line. But when we specify Hibernate FetchMode as SUBSELECT. It execute this get in two separate SQL queries. In which just main have limit.

JPA 2.0 CriteriaQuery on tables in #ManyToMany relationship

I have two entities in a #ManyToMany relationship.
// Output has 4 other #ManyToOne relationships if that matters
#Entity #Table public class Output {
#Id public String address;
#ManyToMany(targetEntity = Interval.class,
cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
#JoinTable(name = "output_has_interval",
joinColumns = {#JoinColumn(name = "output_address",
referencedColumnName = "address")},
inverseJoinColumns = {#JoinColumn(name = "interval_start",
referencedColumnName = "start"),
#JoinColumn(name = "interval_end",
referencedColumnName = "end")})
Collection<Interval> intervals;
#IdClass(IntervalPK.class) // I'll omit this one.
#Entity #Table public class Interval {
#Id public Calendar start;
#Id public Calendar start;
#ManyToMany(targetEntity = Output.class,
mappedBy = "intervals",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
public Collection<Output> outputs;
The join table is called output_has_interval between output and interval.
How do I do CriteriaQuery like this?
SELECT `output`.`address`
FROM `output`, `output_has_interval`, `interval`
WHERE `output`.`address` = `output_has_interval`.`output_address`
AND `interval`.`start` = `output_has_interval`.`interval_start`
AND `interval`.`end` = `output_has_interval`.`interval_end`
AND `interval`.`start` >= '2011-04-30'
This works as expected if I issue it in MySQL.
(I have the corresponding static meta model classes as well, on request I'll could post them - nothing fancy tho'.)
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Output> cq = cb.createQuery(Output.class);
Root<Output> root= cq.from(Output.class);
CollectionJoin<Output, Interval> join = root.join(Output_.intervals);
Expression<Calendar> start = join.get(Interval_.start);
Predicate pred = cb.greaterThanOrEqualTo(start, /* calendar for '2011-04-30' */);
cq.where(pred);
TypedQuery<Output> tq = em.createQuery(cq);
However tq.getResultList returns every output row from my database. Any idea?
(On a side note: Hibernate (the provider I'm using) generates many select statements when I issue this query, one for every relationship Output has, sometimes more.)
Edit.: I wrote:
tq.getResultList returns every
output row from my database
To clarify it: it returns more than just every output row from my database. It actually does a join using output and interval however the predicate:
`interval`.`start` >= '2011-04-30'
doesn't get satisfied.
Ok, I'll managed to solve my riddle on my own.
First of all: the whole problem originated from the fact that I'm a lousy programmer. I iterated over TypedQuery<Output>.getResultList() and accessed every Interval in Output.intervals in a recursive manner, thus Hiberate loaded lazily the requested objects generating a handful of select statements.
However I had to get a hold of those Interval instaces somehow. The following change to my CriteriaQuery did the trick.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery(); // or createQuery(Tuple.class)
Root<Output> root= cq.from(Output.class); // from clause
CollectionJoin<Output, Interval> join = root.join(Output_.intervals);
Path<String> addressPath = root.get(Output_.address); // mind these Path objects
Path<Calendar> startPath = join.get(Interval_.start); // these are the key to success!
cq.multiselect(addressPath, startPath); // select clause
Expression<Calendar> start = join.get(Interval_.start);
Predicate pred = cb.greaterThanOrEqualTo(start, /* calendar for '2011-04-30' */);
cq.where(pred); // where clause
TypedQuery<Tuple> tq = em.createQuery(cq); // holds Tuples
for (Tuple tuple : tq.getResultsList()) {
String address = tuple.get(addressPath);
Calendar start = tuple.get(startPath);
...
Edit
I've just realized that I could've used Path<T> objects instead Expression<T> objects (or vice versa) as Path<T> extends Expression<T>. Oh well...

Categories