jsf hibernate criteria api - java

I have query in Criteria API, that goes like this:
CriteriaQuery<XXX> query = builder.get().createQuery(XXX.class).distinct(true);
Root<XXX> root = query.from(XXX.class);
Fetch<XXX, YYY> fetch = root.fetch(XXX_.yyy, JoinType.LEFT);
now the part I'm stuck on:
when I got query results, I got list of XXX records, with attached list of YYY records, when they exist. I want to sort my result by NUMBER OF YYY records atatched to XXX object. And so far, I'm closer to paranoia then to that goal. Anyone?

To solve you problem you need to get in result SQL Query(which is generated from hibernate criteria) that will look like this:
SELECT xxx.*,count(yyy.FOREIGN_KEY_FIELD) as count FROM XXX xxx left join YYY yyy on (yyy.FOREIGN_KEY_FIELD=xxx.PRIMARY_KEY_FIELD) group by xxx.PRIMARY_KEY_FIELD order by count
Probably for this case you need to use hibernate query api. Criteria is created for Entities and return only list of entities and some projections. In hql it will look very similar to:
select xxx,count(yyy.FOREIGN_KEY_FIELD) as count from XXX xxx left join xxx.yyy yyy group by xxx.PRIMARY_KEY_FIELD order by count
HQL is more flexible for such queries.

Related

QueryDsl - How create inner join with sorted and grouped table

I have to find Salesmen that have sold some itemType. I created method (see below).
But client told me that he wants to find Salesmen by LAST sold itemType.
DB schema:
My attempts: we have in table ORDERS date column, so in normal SQL query I can do double subquery and it should work.
Double, because first I'm sorting by date, then group by salesman - that returns list with only last sold items.
SELECT *
FROM SALESMEN
JOIN
(SELECT *
FROM
(SELECT *
FROM ORDERS
ORDER BY ORDERS.date)
GROUP BY ORDERS.salesman_id) ON SALESMEN.id = ORDERS.salesman_id
WHERE ORDERS.item_type = "CAR"
Unfortunately, queryDSL can do subquery only in IN clause not in FROM.
I spend many hours to find a solution, and in my opinion, it's simply impossible using queryDSL to get sorted and grouped list and join it with another table in one query.
But maybe someone with grater experience has any idea, maybe a solution is simpler than I think :D
public List<SalesmanEntity> findSalesman(SalesmanSearchCriteriaTo criteria) {
SalesmanEntity salesmanEntity = Alias.alias(SalesmanEntity.class);
EntityPathBase<SalesmanEntity> alias = Alias.$(salesman);
JPAQuery<SalesmanEntity> query = new JPAQuery<SalesmanEntity>(getEntityManager()).from(alias);
... a lot of wird IF's....
if (criteria.getLastSoldItemTyp() != null) {
OrderEntity order = Alias.alias(OrderEntity.class);
EntityPathBase<OrderEntity> aliasOrder = Alias.$(order);
query.join(aliasOrder)
.on(Alias.$(salesman.getId()).eq(Alias.$(order.getSalesmanId())))
.where(Alias.$(order.getItemTyp()).eq(criteria.getLastSoldItemTyp()));
}
return query.fetch();
}
Environment:
Java 1.8
SpringBoot 2.0.9
QueryDSL 4.1.4
This is not a limitation of QueryDSL, rather it is a limitation of JPQL - the query language of JPA. For example, SQL does allow subqueries in the FROM clause, and as such querydsl-sql also allows it. With plain plain JPA, or even Hibernate's proprietary HQL it cannot be done. You would have to write a native SQL query then. For this you can have a look at #NamedNativeQuery.
It is possible to add subqueries on top of JPA using Common Table Expressions (CTE) using Blaze-Persistence. Blaze-Persistence ships with an optional QueryDSL integration as well.
Using that extension library, you can just write the following:
QRecursiveEntity recursiveEntity = new QRecursiveEntity("t");
List<RecursiveEntity> fetch = new BlazeJPAQuery<>(entityManager, cbf)
.select(recursiveEntity)
.from(select(recursiveEntity)
.from(recursiveEntity)
.where(recursiveEntity.parent.name.eq("root1"))
.orderBy(recursiveEntity.name.asc())
.limit(1L), recursiveEntity)
.fetch();
Alternatively, when using Hibernate, you can map a Subquery as an Entity, and then correlate that in your query. Using this you can achieve the same result, but you won't be able to reference any outer variables in the subquery, nor will you be able to parameterize the subquery. Both of these features will however be available with the above approach!
#Entity
#Subselect("SELECT salesman_id, sum(amount) FROM ( SELECT * FROM ORDERS ORDER BY ORDERS.date ) GROUP BY ORDERS.salesman_id")
class OrdersBySalesMan {
#Id #Column(name = "salesman_id") Long salesmanId;
#Basic BigDecimal amount; // or something similarly
}
And then in your query:
.from(QSalesman.salesman)
.innerJoin(QOrdersBySalesMan.ordersBySalesMan)
.on(ordersBySalesMan.salesmanId.eq(salesman.id))

JPA CriteriaQuery with ListJoin

I'm working with JPA CriteriaBuilder, CriteriaQuery etc. and static metamodels for typesafe queries, like here for example: click.
I have 3 tables: Client, Package, Vegetable.
Every client has 1 or more packages, and those packages contain multiple vegetables.
What I have now:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Client> query = builder.createQuery(Client.class);
Root<Client> root = query.from(Client.class);
ListJoin<Package, Vegetable> join = root.join(Client_.packages).join(Package_.vegetables);
TypedQuery<Client> typedQuery = entityManager.createQuery(query);
return typedQuery.getResultList();
The ListJoin I added recently. Point is what Hibernate does: generates the whole select for all fields in Client class from the Client table inner joined with Package and Vegetable, but it doesn't actually selects those fields from joined tables. It gets every package by ID and then every vegetable by ID, thus doing n+1 selects.
Without the ListJoin it doesn't inner join those tables, but I'm working on it right now so I added those joins. Now I want to select all the fields from those classes so I get whole object hierarchy with 1 select. I tried doing something with projections like in the link I gave in Projecting the result chaper, but I have no idea how to connect that with ListJoin.
Is that even possible in this situation? When I run this query on database (with manually added all the fields from joined tables) it works fine, but would Hibernate handle that? And if so - how to project the result so it selects all the fields from 3 tables joined together and constructs whole object hierarchy, all with 1 select?
//Edit: managed to retrieve all packages with a single query, but going further raises exception:
Root<Client> root = query.from(Root.class);
ListJoin<Client, Package> join = root.join(Client_.packages);
ListJoin<Package, Vegetable> secondJoin = join.join(Package_.vegetables);
root.fetch(Client_.packages);
Naturally, I tried to add: join.fetch(Package_.vegetables); but it raises the org.hibernate.QueryException: query specified join fetching, but the owner of the fetched association was not present in the select list, no idea what is that.
As to the latest comment: gonna try that now.
//Edit2: I added 2 fetches (couldn't cast them to Join like in that answer, compiler errors):
Fetch<Client, Package> join = root.fetch(Client_.packages);
Fetch<Package, Vegetable> secondJoin = join.fetch(Package_.vegetables);
It raises org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags, known error I guess so at least got something to search for.
//Edit3: Changed it both to Sets and it works, thanks, couldn't have done it without the Fetch instead of Join suggestion, seems kinda unintuitive to me.

QueryDSL - subquery alias

So, I want to get list of entities with count of specific data from my db. I use QueryDSL 4.1.3 and postgres. I have subquery, which counts specific data for each entity. I got something like this and it works:
JPAQuery<?> countQuery = from(sideEntity)
.join(..).on(..)
..
.select(sideEntity.countDistinct);
JPAQuery<?> mainQuery = from(mainEntity)
.innerJoin(..).on(..)
..
.where(..);
return mainQuery(mainEntity, countQuery).fetch();
What I need is to order by countQuery result. Following sql works on my db
select distinct
t0.id as a1
..
(select count(distinct (t2.id) from .. where .. ) as countAlias
from .. where ..
group by ..
order by countAlias desc
So I want to know if it's possible in queryDSL to set an alias to subquery result and then order by this alias.

Hibernate Criteria filter Entity where ManyToMany relation contains multiple objects

I need help with Hibernate Criteria API.
I have a class Job that contain a list of Skill in ManyToMany relationship.
I need to select jobs based on a skill list given as parameter.
I've tried with Restriction.in("skill.id",ids) but this gives me wrong list.If i've selected 2 skills in search form i want the jobs that contain BOTH of them,so i need somehow to implement AND clause.
I've tried with Conjunction:
Conjunction and = Restrictions.conjunction();
for(Skill skill:job.getSkills()){
and.add(Restrictions.eq("skill.id",skill.getId()));
}
And this:
Criteria crit = criteria.createCriteria("skills",JoinType.LEFT_OUTER_JOIN);
for(Skill skill:job.getSkills()){
crit.add(Restrictions.eq("id", skill.getId()));
}
but it creates same alias for skill and it gives me no result.
sql is and (skill1_.ID=? and skill1_.ID=?)
Can anyone help me with this ?thanks
UPDATE:
HQL Query will be like:
select a from Job a where exists (select skill1.id from Skill skill1 join skill1.jobs r where r.id=a.id and skill1.id=1) and exists (select skill2.id from Skill skill2 join skill2.jobs r where r.id=a.id and skill2.id=4)
I need Criteria based on this.
for(Skill skill:job.getSkills()){
DetachedCriteria subquery = DetachedCriteria.forClass(Skill.class,"skill");
subquery.add(Restrictions.eq("id",skill.getId()));
subquery.setProjection(Projections.property("id"));
subquery.createAlias("jobs", "job");
subquery.add(Restrictions.eqProperty("job.id", "Job.id"));
criteria.add(Subqueries.exists(subquery));
}
I managed to solve it.now it works perfect.

Automatic generation of java hibernate code from sql code

Is there a tool able to generate java hibernate code starting from the sql query?
(like reverse of what hibernate does, generating selects from java code) It will help me move all my queries to hibernate!
I mean if i have a select with parameters like this:
select ta.id label, ta.nume value
from ar
left outer join ta ta on idp = ta.ID
where ta.status = 1
and (dp = 0 OR ps = idps_)
and status = 1
order by ta.nume;
to obtain in the end something like this:
DetachedCriteria criteria = DetachedCriteria.forEntityName("ar");
criteria.createAlias("ta", "ta", Criteria.LEFT_JOIN);
criteria.add(Restrictions.eq("ta.status", 1));
Criterion eq = Restrictions.eq("ps.id", idps_);
Criterion isZero = Restrictions.eq("dp.id", 0);
Criterion or = Restrictions.or(eq, isZero);
criteria.add(or);
criteria.add(Restrictions.eq("status", 1));
ProjectionList projectionList = Projections.projectionList();
projectionList.add(Projections.property("ta.id"), "value");
projectionList.add(Projections.property("ta.nume"), "label");
criteria.setProjection(Projections.distinct(projectionList));
criteria.setResultTransformer(Transformers.ALIAS_TO_ENTITY_MAP);
criteria.addOrder(Order.asc("ta.nume"));
OR something similar using maps as output...
providing to the tool the path where i store the mappings of the entities/beans with the tables (or the path to the beans, if the beans are annotated)
You have the HQL that is an SQL-like dialect to work with Hibernate. You use field names from entities instead of those from tables. It supports joins etc.
In fact, Criteria API has very limited support of joins (at least it was so last time I've tried to used) and I've a few times finished rewriting everything from Criteria API to HQL, so I now simply tread Criteria API as no option.
In HQL you can also use SQL functions, both in SELECT and in WHERE part, those embedded and those you've written yourself.

Categories

Resources