Spring/Hibernate/MySQL/JPA here. I have the following code:
public void setOrdering(
SearchRequest searchRequest,
CriteriaQuery query,
CriteriaBuilder builder,
Root<? extends MyEntity> root) {
String sortParam = "reportedOn";
Expression expression = builder.selectCase()
.when(builder.isNull(root.get(sortParam)), root.get(sortParam))
.otherwise(root.get(sortParam));
Order order = (searchRequest.isAscending())
? builder.asc(expression)
: builder.desc(expression);
query.orderBy(order);
}
Basically, I'm trying to implement the CriteriaBuilder/JPA equivalent of:
SELECT
*
FROM
mytable
WHERE
<lots of predicates here>
ORDER BY reported_on IS NULL, reported_on <ASC/DESC>
I already have the WHERE predicates added, I'm just struggling with the query.orderBy(...).
At runtime, when searchRequest.isAscending() is false, the results come back working just fine, with the records that contain a null reported_on value ordered at the end of the results.
But if searchRequest.isAscending() is true, the NULLS LAST attempt does not appear to work at all.
You're mixing up the Spring and JPA APIs, here query is from the JPA API so you need to sort using something like:
CriteriaBuilder cb = ...
Root root = ...
query.orderBy(cb.asc(root.get("reportedOn")));
It does not look like JPA's CriteriaBuilder supports NULLS LAST. I actually got this working using a SQL "hack":
String sortParam = "reportedOn";
Order order = (searchRequest.isAscending())
? builder.desc(builder.neg(root.get(sortParam)))
: builder.desc(root.get(sortParam));
query.orderBy(order);
Basically ORDER BY -reported_on DESC does the same thing as ORDER BY reported_on ASC but it sorts records with NULL reported_on values all the way to the bottom of the search results, which is what NULLS LAST is supposed to do.
Related
I have a problem when i want to search in entities that have specific ids. I have fullTextQuery that i execute, it works all fine, bud when i want to say
ONLY SEARCH IN THESE ENTITIES (List of ids provided) :
+(title:slovakia~2 leadText:slovakia~2 body:slovakia~2 software:slovakia~2) +verified:true +eid:(113 | 112 | 3)
Then i get 0 results, these entities are indexed and persisted, all should be working fine, yet it doesnt return any results.
Here is The entity property defined :
#Id
#GeneratedValue
#Field(name = "eid")
#FieldBridge(impl = LongBridge.class)
private long id;
I have tried, Without field bridge, with TermVector.YES and also without any additional #Field.. annotation. All results either exception or just no results.
What is a proper way of searching in specific IDs?
For instance here is the working query:
Creation of query looks like this :
return Optional.of(getQueryBuilder()
.keyword()
.onField("eid")
.matching(stringBuilder.toString())
.createQuery());
The syntax you tried to use, (113 | 112 | 3), is not correct in this context. Parameters to the keyword query are not interpreted, in particular operators are not supported.
Use a boolean junction that matches any of the provided IDs instead:
List<String> eids = ...;
QueryBuilder qb = getQueryBuilder();
BooleanJunction<?> idJunction = qb.bool();
for (String eid : eids) {
idJunction.should(
qb.keyword()
.onField("eid")
.matching(eid)
.createQuery()
);
}
return idJunction.createQuery();
Note that, if you want to add other queries, you should not use the same junction. Use another junction that includes idJunction.createQuery() as one of its clauses.
From the little experience i have had with hibernate-search, only Ranges seem to work well with intenger and long fields. In your example here, i expect the following query should work just fine:
QueryBuilder qb = getQueryBuilder();
BooleanJunction<?> idJunction = qb.bool();
bool.must(NumericRangeQuery.newLongRange("eid", Long.valueOf(eid), Long.valueOf(eid), true, true).createQuery();
In this case, the Boxed Long.valueOf() is optional if the values being supplied are Long values already.
I've a class Lawsuit, that contains a List<Hearing>, each one with a Date attribute.
I need to select all the Lawsuits ordered by the date of their Hearings
I've a CriteriaQuery like
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Lawsuit> cq = cb.createQuery(Lawsuit.class);
Root<Lawsuit> root = cq.from(Lawsuit.class);
I use distinct to flatten the results:
cq.select(root).distinct(true);
I then join Lawsuit with Hearing
Join<Lawsuit, Hearing> hearing = root.join("hearings", JoinType.INNER);
to create Predicates
predicateList.add(cb.isNotNull(hearing.<Date>get("date")));
and Orders:
orderList.add(cb.asc(hearing.<Date>get("date")));
Everything works fine if I avoid distinct, but if I use it, it complains about not being able to order based on fields that are not in the SELECT:
Caused by: org.postgresql.util.PSQLException: ERROR: for SELECT DISTINCT, ORDER BY expressions must appear in select list
The List<Hearing> is already accessible through the Lawsuit classes returned, so I'm confused: how should I add them to the select list ?
I've discovered the source of the problem somewhere else, and solving it has made unnecessary to do what asked in the question;
as described in other answers, it should be unnecessary to perform the distinct here.
The duplicate rows were originated by erroneous left joins that were performed on collections (attributes of the root object) even if the predicates were not been used:
Join<Lawsuit, Witness> witnesses = root.join("witnesses", JoinType.LEFT);
if (witnessToFilterWith!=null) {
predicateList.add(cb.equal(witnesses.<Long>get("id"),witnessToFilterWith.getId()));
}
The join should obviously be performed as inner and only if needed:
if (witnessToFilterWith!=null) {
Join<Lawsuit, Witness> witnesses = root.join("witnesses", JoinType.INNER);
predicateList.add(cb.equal(witnesses.<Long>get("id"),witnessToFilterWith.getId()));
}
So, if you're here because you're getting the same problem, search the problem in the joins.
You can also de-duplicate via group by based on primary key column of root table:
cq.groupBy(root.get("id")); // Assuming that Lawsuite.id is primary key column
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)));
I am trying to write a distinct criteria query, using:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<CorporateNews> query = criteriaBuilder.createQuery(CorporateNews.class);
Root<CorporateNews> root = query.from(CorporateNews.class);
query.select(root).distinct(true);
return (List<CorporateNews>) entityManager.createQuery(query).getResultList();
However, this method is not working. The data further contain duplicates.
Anyone know how to fix it?
I call out this method later in the REST API. I get the result:
[{"category":"Cinema"},{"category":"Cinema"},{"category":"Music"}]
However, I would get without repetition of data and so something like this:
[{"category":"Cinema"},{"category":"Music"}]
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);