HQL left join with condition - java

This may seem basic but it's late and I'm having trouble with the following.
class Group {
#Id
String id;
}
class Participation {
#Id
String id;
#ManyToOne
#JoinColumn(name = "GROUP_ID")
Group group;
#ManyToOne
#JoinColumn(name = "USER_ID")
User user;
}
class User {
#Id
String id;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
Set<Participation> participations;
}
Class diagram
So
Participation -->1 Group
and User 1<-->N Participation
How can I retrieve all Groups with, for a given User, the associated Participation (or null is there is none)? I've been playing with join fetches but to no avail so far...
Many thanks,
CN
PS. I can do this in SQL thus :
select g.d, p.id
from group as g
left join participation as p
on p.group_id = g.id and p.user_id = 2;

(Probably some typo in the HQL itself but the idea should be correct)
What you are asking, based on your SQL and description, is find out all Participation (and its corresponding Group) based on User, which is simply
select p.id, p.group.id from Participation p where p.user.id = :userId
To make it better, you should fetch the entities instead:
from Participation p left join fetch p.group where p.user.id = :userId
There were some confusions in understanding what you were trying to do:
You want all groups (regardless of condition). And, for a given user, you want to find all groups and participations that user is involved.
Though it should be possible using Right-outer-join:
select g, p from Participation p
right outer join p.group g
where p.user.id=:userId
Or, in later version of Hibernate (>= 5.1 ?), it allow explicit join (haven't tried before, you may give it a try) (Replace with with on if you are using JPQL):
select g, p from Group g
left outer join Participation p
with p.group = g
left outer join p.user u
where u.id = :userId
Or you may use other techniques such as subquery etc. However I would rather separate them into simpler queries in your case and do a simple aggregation in your code.
The basic idea is to have
Query for all groups: from Groups
Query for all participations for a user: from Participation p join fetch p.group where p.user.id=:userId
Then you can aggregate them easily to the form you want, e.g. Map<Group, List<Participation>>, or even more meaningful value object.
The benefit is the data access query is simpler and more reusable (esp if you are wrapping them in DAO/Repository finder method). One more round trip to DB shouldn't cause any obvious performance impact here.

You need to map the participation relationship in the Group entity. If the relationship between Participation and Group is 1..N:
class Group {
String id
List<Participation> participations
}
The JPQL can be:
SELECT g.id, p.id FROM Group g
JOIN g.participations p
JOIN p.user user
WHERE user.id = :idUser
You can receive this information as List<Object[]> (Object[0] is group id and Object[1] is participation id) or use SELECT NEW.
Without map the Group/Participation relationship, you can do:
SELECT g.id, p.id FROM Group g, Participation p
JOIN p.user user
JOIN p.group groupp
WHERE g.id = groupp.id
AND user.id = :idUser
But you can't do a LEFT JOIN using this strategy. The query above behaviours like a JOIN.
It's normal with Hibernate you map the side of a relationship that you would like to make a query. So, I recommend the first approach, mapping the relationship.

Related

How to get batching using the old hibernate criteria?

I'm still using the old org.hibernate.Criteria and get more and more confused about fetch modes. In various queries, I need all of the following variants, so I can't control it via annotations. I'm just switching everything to #ManyToOne(fetch=FetchType.LAZY), as otherwise, there's no change to change anything in the query.
What I could find so far either concerns HQL or JPA2 or offers just two choices, but I need it for the old criteria and for (at least) the following three cases:
Do a JOIN, and fetch from both tables. This is OK unless the data is too redundant (e.g., the master data is big or repeated many times in the result). In SQL, I'd write
SELECT * FROM item JOIN order on item.order_id = order.id
WHERE ...;
Do a JOIN, fetch from the first table, and the separation from the other. This is usually the more efficient variant of the previous query. In SQL, I'd write
SELECT item.* FROM item JOIN order on item.order_id = order.id
WHERE ...;
SELECT order.* FROM order WHERE ...;
Do a JOIN, but do not fetch the joined table. This is useful e.g., for sorting based on data the other table. In SQL, I'd write
SELECT item.* FROM item JOIN order on item.order_id = order.id
WHERE ...
ORDER BY order.name, item.name;
It looks like without explicitly specifying fetch=FetchType.LAZY, everything gets fetched eagerly as in the first case, which is sometimes too bad. I guess, using Criteria#setFetchMode, I can get the third case. I haven't tried it out yet, as I'm still missing the second case. I know that it's somehow possible, as there's the #BatchSize annotation.
Am I right with the above?
Is there a way how to get the second case with the old criteria?
Update
It looks like using createAlias() leads to fetching everything eagerly. There are some overloads allowing to specify the JoinType, but I'd need to specify the fetch type. Now, I'm confused even more.
Yes you can satisfy all three cases using FetchType.LAZY, BatchSize, the different fetch modes, and projections (note I just made up a 'where' clause with Restrictions.like("name", "%s%") to ensure that I retrieved many rows):
Do a JOIN, and fetch from both tables.
Because the order of an item is FetchType.LAZY, the default fetch mode will be 'SELECT' so it just needs to be set as 'JOIN' to fetch the related entity data from a join rather than separate query:
Session session = entityManager.unwrap(org.hibernate.Session.class);
Criteria cr = session.createCriteria(Item.class);
cr.add(Restrictions.like("name", "%s%"));
cr.setFetchMode("order", FetchMode.JOIN);
List results = cr.list();
results.forEach(r -> System.out.println(((Item)r).getOrder().getName()));
The resulting single SQL query:
select
this_.id as id1_0_1_,
this_.name as name2_0_1_,
this_.order_id as order_id3_0_1_,
order2_.id as id1_1_0_,
order2_.name as name2_1_0_
from
item_table this_
left outer join
order_table order2_
on this_.order_id=order2_.id
where
this_.name like ?
Do a JOIN, fetch from the first table and the separately from the other.
Leave the fetch mode as the default 'SELECT', create an alias for the order to use it's columns in sorting, and use a projection to select the desired subset of columns including the foreign key:
Session session = entityManager.unwrap(org.hibernate.Session.class);
Criteria cr = session.createCriteria(Item.class);
cr.add(Restrictions.like("name", "%s%"));
cr.createAlias("order", "o");
cr.addOrder(org.hibernate.criterion.Order.asc("o.id"));
cr.setProjection(Projections.projectionList()
.add(Projections.property("id"), "id")
.add(Projections.property("name"), "name")
.add(Projections.property("order"), "order"))
.setResultTransformer(org.hibernate.transform.Transformers.aliasToBean(Item.class));
List results = cr.list();
results.forEach(r -> System.out.println(((Item)r).getOrder().getName()));
The resulting first SQL query:
select
this_.id as y0_,
this_.name as y1_,
this_.order_id as y2_
from
item_table this_
inner join
order_table o1_
on this_.order_id=o1_.id
where
this_.name like ?
order by
o1_.id asc
and subsequent batches (note I used #BatchSize(value=5) on the Order class):
select
order0_.id as id1_1_0_,
order0_.name as name2_1_0_
from
order_table order0_
where
order0_.id in (
?, ?, ?, ?, ?
)
Do a JOIN, but do not fetch the joined table.
Same as the previous case, but don't do anything to prompt the loading of the lazy-loaded orders:
Session session = entityManager.unwrap(org.hibernate.Session.class);
Criteria cr = session.createCriteria(Item.class);
cr.add(Restrictions.like("name", "%s%"));
cr.createAlias("order", "o");
cr.addOrder(Order.asc("o.id"));
cr.setProjection(Projections.projectionList()
.add(Projections.property("id"), "id")
.add(Projections.property("name"), "name")
.add(Projections.property("order"), "order"))
.setResultTransformer(org.hibernate.transform.Transformers.aliasToBean(Item.class));
List results = cr.list();
results.forEach(r -> System.out.println(((Item)r).getName()));
The resulting single SQL query:
select
this_.id as y0_,
this_.name as y1_,
this_.order_id as y2_
from
item_table this_
inner join
order_table o1_
on this_.order_id=o1_.id
where
this_.name like ?
order by
o1_.id asc
My entities for all cases remained the same:
#Entity
#Table(name = "item_table")
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToOne(fetch = FetchType.LAZY)
private Order order;
// getters and setters omitted
}
#Entity
#Table(name = "order_table")
#BatchSize(size = 5)
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// getters and setters omitted
}

Map the latest child of a collection with JPA and Hibernate (NotNull)

I need get last entity element from collection. I am using #JoinFormula:
#Entity
public class Book {
#ManyToOne
#JoinFormula("(select * from
(SELECT r.id FROM review r WHERE r.book_id = id ORDER BY r.postedAt DESC)
where rownum = 1)")
private Review
...
}
And it works fantastic, but only if Book has some Review. Otherwise book isn't found. Because hibernate convert this to cross join and use condition in WHERE statement:
review_entity.id =
(select * from (SELECT r.id FROM review r WHERE r.book_id = id ORDER BY r.postedAt DESC) where rownum = 1)
Is any option here to convert JoinFormula to left join or something like this?
select
book0_.id as id1_0_0_,
book0_.title as title2_0_0_,
book0_.version as version3_0_0_,
(SELECT
r.id
FROM
review r
where
r.book_id = book0_.id
ORDER BY
r.postedAt DESC LIMIT 1) as formula1_0_,
review1_.id as id1_1_1_,
review1_.book_id as book_id4_1_1_,
review1_.comment as comment2_1_1_,
review1_.postedAt as postedAt3_1_1_
from
Book book0_
left outer join
Review review1_
on (
SELECT
r.id
FROM
review r
where
r.book_id = book0_.id
ORDER BY
r.postedAt DESC LIMIT 1
)=review1_.id
where
book0_.id=?
I am not sure what you are going is a good idea. Your domain model does not match the reality that a book has many reviews. I understand that you want to only access the latest review but that is probably better done as an individual query. Otherwise, you could update your domain model to reflect the reality but still fetch the last review in a performant manner by means of Hibernate's extra-lazy property.
"Extra-lazy" collection fetching - individual elements of the
collection are accessed from the database as needed. Hibernate tries
not to fetch the whole collection into memory unless absolutely needed
(suitable for very large collections)
#Entity
public class Book {
#OneToMany
#LazyCollection(LazyCollectionOption.EXTRA)
#OrderBy("...")
private List<Review> reviews; //needn't be exposed via public API
public Review getLatestReview(){
return reviews.get(reviews.size() - 1); //or first if ordered desc
}
}
Hibernate's #Where clause could also be used as an alternative to limit collection to only one element.

Hibernate doesn't return proper objects in inner join

I'm working on Spring MVC application that uses Hibernate. One of my requests in SQL looks like this:
SELECT work_order.* FROM work_order
inner join user
on
work_order.user_id = user.id
and
user.user_name = 'Jenna'
and I do get a result as a row of workorders. When I'm trying to do the same with Hibernate I get five objects(which is correct) but can't convert into WorkoOrder objects.
Here's my Hibernate request:
List<WorkOrder> workorders = (List<WorkOrder>)currentSession.createQuery(
"from WorkOrder w inner join w.user as u where user_name=:tempName")
.setParameter("tempName", tempName).getResultList();
Where tempName is a parameter. I do get objects, but can't cast them to Workorder, probably because Hibernate returns Workorders and Users combined. How to fix this so only Workorders will be returned?
Update: User is mapped in WorkOrder
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="user_id", nullable = true)
private User user;
Your HQL does not have a select clause. If you want only the WorkOrder entities from the join you form, then you'll need to tell Hibernate so:
currentSession.createQuery(
"select w from WorkOrder w inner join w.user as u where u.user_name=:tempName")

HQL merge two typedqueries

I have written two hibernate queries:
TypedQuery q = em.createQuery("SELECT user.id FROM TableOne AS user WHERE ...", Long.class);
TypedQuery q = em.createQuery("SELECT link.user_id FROM TableTwo AS link WHERE ...", Long.class);
Now, how do I merge these two queries? My return type has to be TypedQuery
the UNION statement not work on Hibernate.
So you can:
Execute first query and put in a list;
Execute second query and put in a list;
Put the result of first and second list in a unique list.
If you want to delete the duplicated value, you must do programmatically.
Completely copying answer from https://stackoverflow.com/a/3940445/929701:
You could use id in (select id from ...) or id in (select id from ...)
e.g. instead of non-working
from Person p where p.name="Joe"
union
from Person p join p.children c where c.name="Joe"
you could do
from Person p
where p.id in (select p1.id from Person p1 where p1.name="Joe")
or p.id in (select p2.id from Person p2 join p2.children c where c.name="Joe");
At least using MySQL, you will run into performance problems with the later though. It's sometimes easier to do a poor man's join on two queries instead:
// use set for uniqueness
Set<Person> people = new HashSet<Person>((List<Person>) query1.list());
people.addAll((List<Person>) query2.list());
return new ArrayList<Person>(people);
It's often better to do two simple queries than one complex one.

Hibernate left join fetch - get only an ID list of the first table

I have the following HQL query which works fine, however it returns a list of full FooD objects. I only need the ID of the FooD objects as I need to have faster query. Please not that in Hibernate mappings, FooD has a many-to-one relationship with FooB.
hqlQuery = "from FooD d left join fetch d.bill where d.ts < :ts"
I have then tried to get only the ID using the same kind of HQL query:
hqlQuery = "SELECT d.id from FooD d left join fetch d.bill where d.ts < :ts"
I got a "query specified join fetching, but the owner of the fetched association was not present in the select list".
I have then converted the query to regular Oracle SQL to get only FooD.ID:
sqlQuery = "SELECT d.id from FooD d LEFT OUTER JOIN FooB b on d.foodId=b.id where d.ts < :ts"
I have then mapped FooD and FooB objects like this:
sqlQuery.addEntity(FooD.class);
sqlQuery.addEntity(FooB.class);
and then get the resulting list by calling:
hSession.createSQLQuery(sql).setTimestamp("ts", ts).list();
But got the following error: "unexpected token: on near line 1".
Does someone know how to do get only the ID of FooD when doing a left outer join on FooB using Hibernate?
Update:
I didn't test it, but this should do the trick
SELECT d.id from FooD d inner join d.bill where d.ts < :ts
when you add LEFT you make it an outer join implicitly, and there is no need to initialize bill if all you need is to join by keys
Hibernate requires the object to be in the select clause to do any eager join fetches on it
But since you have no select or where clauses on d.bill, why do you need to fetch it anyway?
If all you need is the id, why not do this, there is no reason for the redundant join:
hqlQuery = "SELECT d.id from FooD d where d.ts < :ts"

Categories