Need help creating JPA criteria query - java

I'm building my first Java EE web application using Glassfish and JSF. I'm fairly new to the criteria query and I have a query I need to perform but the javaee6 tutorial seems a little thin on examples. Anyway, I'm having a hard time creating the query.
Goal: I want to pull the company with the most documents stored.
Companies have a OneToMany relationship with Documents.
Documents has a ManyToOne relationship with several tables, the "usertype" column distinguishes them.
MySQL query:
SELECT USERID, COUNT(USERID) AS CNT
FROM DOCUMENTS
WHERE USERTYPE="COMPANY"
GROUP BY USERID
ORDER BY CNT DESC
Thanks
--update--
Based on user feedback, here is what I have so far:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Documents> cqry = cb.createQuery(Documents.class);
//Intersting Stuff
Root<Documents> root = cqry.from(Documents.class);
Expression userid = root.get("userID");
Expression usertype = root.get("userType");
Expression count = cb.count(userid);
cqry.multiselect(userid, count);
Predicate userType = cb.equal(usertype, "COMPANY");
cqry.where(userType);
cqry.groupBy(userid);
cqry.orderBy(cb.desc(count));
//more boilerplate
Query qry = em.createQuery(cqry);
List<Documents> results = qry.getResultList();
The error I get is:
Exception Description: Partial object queries are not allowed to maintain the cache or be edited. You must use dontMaintainCache().
Typical error, means nothing to me!

Your query doesn't return a complete entity object as you're only selecting two fields of the given table (this is why you're getting an error that says yadayadapartialyadayada).
Your solution is almost right, here's what you need to change to make it work—making it partial.
Instead of a plain CriteriaQuery<...> you have to create a tuple CriteriaQuery<..> by calling CriteriaBuilder.createTupleQuery(). (Basically, you can call CriteriaBuilder.createQuery(...) and pass Tuple.class to it as an argument. Tuple is a sort of wildcard entity class.)
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq= cb.createTupleQuery();
Root<Documents> root = cq.from(Documents.class);
Expression<Integer> userId = root.get("USERID");
Expression<String> userType = root.get("USERTYPE");
Expression<Long> count = cb.count(userId);
cq.multiselect(userId.alias("USERID"), count.alias("CNT"));
cq.where(cb.equal(userType, "COMPANY");
cq.groupBy(userId);
cq.orderBy(cb.desc(count));
TypedQuery<Tuple> tq = em.createQuery(cq);
for (Tuple t : tq.getResultsList()) {
System.out.println(t.get("USERID"));
System.out.println(t.get("CNT"));
}
(Accessing fields of a Tuple gave me an error if I didn't use aliases for them (in multiselect(...)). This is why I've used aliases, but this can be tackled more cleanly by using JPA 2's Metamodel API, which is described in the specification quite thoroughly. )
The documentation for CriteriaQuery.multiselect(...) describes the behaviour of queries using Tuple objects more deeply.

If you are using Hibernate, this should work:
ProjectionList pl = Projections.projectionList()
.add(Projections.groupProperty("userid"))
.add(Projections.property("userid"))
.add(Projections.count("userid"));
Criteria criteria = session.createCriteria(Document.class)
.add(Restrictions.eq("usertype",usertype))
.setProjection(pl)
.addOrder(Order.desc("cnt"));
Hope it helps!

Take a look into this easy tutorial. It uses JPA2 and Criteria
http://www.jumpingbean.co.za/blogs/jpa2-criteria-api
Regards!

You need to add a constructor to Documents with only userid and count because you will need it on:
cqry.multiselect(userid, count);

Related

Is this query possible using Criteria or DetachedCriteria Hibernate

The question is simple can this query be done in Hibernate using Criteria or DetachedCriteria? i guess not but i wanted to do this question maybe exist a workaround.
SELECT
COLUMNS
FROM table
WHERE id not in (
SELECT * FROM (
SELECT id
FROM table
WHERE
SOMECONDITIONS
ORDER BY timestamp desc limit 0, 15
)
as t);
I will mark the answer by #Dean Clark as correct but another question arises is the following
where can i find the findByCriteria from SessionFactory we are not using Spring
To exactly match you query, you really need to do it with 2 steps, but I'd avoid this if possible:
final Criteria innerCriteria = getSession().createCriteria(YourEntity.class);
// SOME CONDITIONS
innerCriteria.add(Restrictions.eq("someColumn", "someValue"));
innerCriteria.addOrder(Order.desc("timestamp"));
innerCriteria.setMaxResults(15);
innerCriteria.setProjection(Projections.id());
List<YourIdClass> ids = innerCriteria.list();
final Criteria criteria = getSession().createCriteria(YourEntity.class);
criteria.add(Restrictions.not(Restrictions.in("id", ids)));
List<YourEntity> results = criteria.list();
Would the objects you're trying to identify have the same "SOMECONDITIONS"? If so, this would functionally accomplish what you're looking for:
final DetachedCriteria criteria = DetachedCriteria.forClass(YourEntity.class);
// SOME CONDITIONS
criteria.add(Restrictions.eq("someColumn", "someValue"));
criteria.addOrder(Order.desc("timestamp"));
getHibernateTemplate().findByCriteria(criteria, 16, 9999999);

JPA CriteriaQuery with LEAST and GREATEST functions

I am have a problem where i need to join two tables using the LEAST and GREATEST functions, but using JPA CriteriaQuery. Here is the SQL that i am trying to duplicate...
select * from TABLE_A a
inner join TABLE_X x on
(
a.COL_1 = least(x.COL_Y, x.COL_Z)
and
a.COL_2 = greatest(x.COL_Y, x.COL_Z)
);
I have looked at CriteriaBuilder.least(..) and greatest(..), but am having a difficult time trying to understand how to create the Expression<T> to pass to either function.
The simplest way to compare two columns and get the least/greatest value is to use the CASE statement.
In JPQL, the query would look like
select a from EntityA a join a.entityXList x
where a.numValueA=CASE WHEN x.numValueY <= x.numValueZ THEN x.numValueY ELSE x.numValueZ END
and a.numValueB=CASE WHEN x.numValueY >= x.numValueZ THEN x.numValueY ELSE x.numValueZ END
You can code the equivalent using CriteriaBuilder.selectCase() but I've never been a big fan of CriteriaBuilder. If requirements forces you to use CriteriaBuilder then please let me know and I can try to code the equivalent.
CriteriaBuilder least/greatest is meant to get the min/max value of all the entries in one column. Let's say you want to get the Entity that had the alphabetically greatest String name. The code would look like
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery query = cb.createQuery(EntityX.class);
Root<EntityX> root = query.from(EntityX.class);
Subquery<String> maxSubQuery = query.subquery(String.class);
Root<EntityX> fromEntityX = maxSubQuery.from(EntityX.class);
maxSubQuery.select(cb.greatest(fromEntityX.get(EntityX_.nameX)));
query.where(cb.equal(root.get(EntityX_.nameX), maxSubQuery));
I created a sample Spring Data JPA app that demonstrates these JPA examples at
https://github.com/juttayaya/stackoverflow/tree/master/JpaQueryTest
It turns out that CriteriaBuilder does support calling LEAST and GREATEST as non-aggregate functions, and can be accessed by using the CriteriaBuilder.function(..), as shown here:
Predicate greatestPred = cb.equal(pathA.get(TableA_.col2),
cb.function("greatest", String.class,
pathX.get(TableX_.colY), pathX.get(TableX_.colZ)));

JPA based Criteria query for finding entity by field name

I dont know if its me who finds criteria queries with JPA difficult.
What to do next in order to get Tag(s) with name = :tagNam
Tag getTagByName(String tagName){
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tag> cq = cb.createQuery(Tag.class);
Root<Tag> root = cq.from(Tag.class);
**//what to do next in order to get Tag(s) with name = :tagNam**
}
Tag is an entity with field name
I have viewed:
http://docs.oracle.com/javaee/6/tutorial/doc/gjivm.html
http://www.ibm.com/developerworks/library/j-typesafejpa/
http://www.programcreek.com/java-api-examples/javax.persistence.criteria.CriteriaQuery
But was unable to grasp concepts. Is there an easy step by step or comprehensive tutorial too?
I am also frustrated as I was required (according to tutorials) to do use Tag_ which does not resolves of course.
Try this:
CriteriaQuery<Tag> cq = cb.createQuery(Tag.class);
Root<Tag> tag = cq.from(Tag.class);
cq.where(cb.equal(tag.get(Tag_.name), tagName));
This is adapted from oracle documentation(your first link).

Join cartesian product

Context
I send some files to some enterprises every week. I need to restitute for each week and each enterprise whether the file is sent or not.
Tables
ent (enterprise)
wek (week)
fil (file : references wek and ent)
Solution with pure SQL
Make a cartesian product between ent and wek then left outer join fil. This works :
select * from
(
select * from wek, ent e
) as t1
left join fil f
on f.ent_id = t1.ent_id
and f.wek_id = t1.wek_id
Problem :
How to translate this into JPA (in the CriteriaBuilder way)?
For example, if I try :
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<ResultClass> query = cb.createQuery(ResultClass.class);
Root<Week> week = query.from(Week.class);
Root<Enterprise> query.from(Enterprise.class);
Expression<???> cartesianProduct = ??? //How?
cartesianProduct.leftJoin(???_.file);
query.where(
cb.equal(file.get(File_.wek_id), week.get(Week.week_id));
)
Using 2 "from" clauses gives me the cartesian product but how do I left join this result?
Unstatisfaying solution :
Create a view :
CREATE VIEW view_ent_wek AS
SELECT ent_id as ent_id, wek_id as wek_id, ent_id || '-' || ent_id as id
FROM ent, wek;
Map it to an entity :
#Entity
#Table(name = "view_ent_wek")
public class WeekEnterprise {
#Id
#Column(name = "id")
private String id;
#ManyToOne
#JoinColumn(name = "ent_id")
private Enterprise enterprise;
#ManyToOne
#JoinColumn(name = "wek_id")
private Week week;
[...]
Then I can use it in a query :
Root<WeekEnterprise> weekEnterprise = query.from(WeekEnterprise.class);
weekEnterprise.join(...)
I don't like this solution because it makes me create a view that is obviously not necessary. Any idea?
I don't see the point of doing the reporting-style SQL query with JPA, let alone with Criteria Query.
Of course, JPA (and its implementations) do support tuple queries (as opposed to entity queries, see also: Hibernate tuple criteria queries), but tuple queries is not what JPA is good at. Imagine you want to change that report and add more calculations. Wouldn't it be much easier to do that with SQL?
Using JPA for mapping to Java objects and for transaction modelling still makes sense, and thus, your SQL view solution is already quite good, or if you prefer not to store the SQL in a view, use JPA's native query API:
List<Object[]> list = em.createNativeQuery(sql).getResultList();
Or:
List<WeekEnterprise> list = em.createNativeQuery(sql, WeekEnterprise.class)
.getResultList();
I have some ideas:
With 2 JPA queries. First: fetch the cartesian product of wek and enterprises. Second: take an inner join between wek, enterprises and files. Uses a map (wek_id+ent_id => Tuple(Wek, Ent, File)) to quickly identify where to put the file.
Write plain SQL queries and execute them with the JPA API.
(Didn't think much about this one) Create a back reference from Wek and Ent to File (lazy loaded) and then you should be able to continue your first idea.
Don't know much about JPA, but about Postgres. And I would suggest this superior query:
SELECT *
FROM wek CROSS JOIN ent
LEFT JOIN fil USING (ent_id, wek_id);
You don't need the subselect.
The only difference in the result (besides being shorter and faster): You get the columns ent_id and wek_id once in the output (which might solve a problem with duplicated column names).
Should be easy to translate to JPA now.

Referring to an earlier aliased field in a criteria query

In this query:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> q = cb.createTupleQuery();
// FROM GamePlayedEvent gpe
Root<GamePlayedEvent> gpe = q.from(GamePlayedEvent.class);
// SELECT gameId, COUNT(*) AS count, AVG(duration)
// AS avDur, AVG(rewardCurrency) AS avCur, AVG(rewardXP) avXp
q.select(cb.tuple(
gpe.<String>get("gameId"),
cb.count(gpe).alias("count"),
cb.avg(gpe.<Double>get("duration")).alias("avDur"),
cb.avg(gpe.<Integer>get("rewardCurrency")).alias("avCur"),
cb.avg(gpe.<Integer>get("rewardXp")).alias("avXp")
));
// WHERE loginTime BETWEEN ...
q.where(cb.between(gpe.<Date>get("time"), fromTime, toTime));
// GROUP BY gameId
q.groupBy(gpe.<String>get("gameId"));
// ORDER BY count DESC
q.orderBy(cb.desc(???));
How can I add the ORDER BY count DESC, referring to the "count" defined in the SELECT clause?
What if you just captured the count expression, and used it directly?
Expression event_count = cb.count(gpe);
q.select(cb.tuple(
gpe.<String>get("gameId"),
event_count,
...
));
q.orderBy(cb.desc(event_count));
I came across the same problem today but none of the suggested solutions worked for me because I needed to reuse the expression not only in the order by clause but also in the group by clause.
One obvious solution would be to create a view on the database level but this is a bit clumsy, creates an unnecessary subquery and even not possible if the db user isn't granted enough privileges. A better option which I ended up implementing is to write something like this
q.select(cb.tuple(
gpe.<String>get("gameId"),
cb.count(gpe),
...
)).groupBy(cb.literal(2)).orderBy(cb.literal(2));
The first downside of this approach is that the code is errorprone. The other drawback is that the generated sql query contains ordinal position notation, which works on some databases (like postgresql or mariadb) but doesn't work on others (like sql server). In my case, however, I found this to be the best option.
Tested on jpa 2.1 with hibernate 5.2.3 as a provider and postgresql 9.6.
Even though the Pro JPA 2 book describes that the alias method can be used to generate a sql query alias (on page 251) I have had no success with making it work with neither EclipseLink or Hibernate. For your question I would say that your orderBy line should read:
q.orderBy(cb.desc(cb.count(gpe));
if it was supported by the different vendors.
As far as my research goes it seams that the alias method is only used for naming elements in the tuble used in the select (so only for projection).
I have one question though. Why would you want to use the JPA Criteria API for this query. It (the query) seams to be static in nature so why not use JPQL where you can define your query aliases directly.
Have you tried setting up a projection with an alias?
criteria.setProjection(Projections.projectionList()
.add(Projections.count("item.id"), "countItems"));
criteria.addOrder(Order.desc("countItems"));
For a sum aggregation field I have the following code which worked for me:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<T> cq = cb.createQuery(entity);
Root<T> root = cq.from(entity);
cq.orderBy(cb.desc(cb.sum(root.get(orderByString))));
// orderByString is string entity field that is being aggregated and which we want to put in orderby clause as well.

Categories