CriteriaQuery with One-to-Many and Many-to-One Relationships - java

I have 3 tables in question: PrblFldr --> PrblFldrAtrbtVal --> PrblTmpltAtrbt. The relationships between these are "one-to-many" and "many-to-one", respectively.
I am using CriteriaBuilder to perform a search on PrblFldr objects. I need to search by the values of each PrblFldrAtrbtVal that is associated with the PrblFldr. The query parameters' keys are the unique PKs that associate each PrblFldrAtrbtVal with a PrblTmpltAtrbt; the value of the parameter is what to search for in the PrblFldrAtrbtVal's value.
Here's my code so far (edited):
#GET
#Path("/folders/search")
public Response searchFolders(#Context UriInfo uriInfo) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<PrblFldr> cq = cb.createQuery(PrblFldr.class);
Root<PrblFldr> folder = cq.from(PrblFldr.class);
Join<PrblFldr, PrblFldrAtrbtVal> attributes = folder.join("prblFldrAtrbtVals");
Join<PrblFldrAtrbtVal, PrblTmpltAtrbt> attributeTemplates = attributes.join("prblTmpltAtrbt");
List<Predicate> predicates = new ArrayList<Predicate>();
MultivaluedMap<String, String> queryParams = uriInfo.getQueryParameters();
for (String key: queryParams.keySet()) {
String value = queryParams.getFirst(key).replaceAll("_", "\\\\_");
predicates.add(cb.and(cb.equal(attributeTemplates.<String>get("tmpltAtrbtSeqId"), key),
cb.like(attributes.<String>get("fldrAtrbtVal"), "%" + value + "%", '\\')));
}
cq.distinct(true).select(folder).where(cb.and(predicates.toArray(new Predicate[]{})));
List<PrblFldr> results = em.createQuery(cq).getResultList();
return Response.ok(gson.toJson(results), MediaType.APPLICATION_JSON).build();
}
EDITED: It is currently working if I only pass in one key/value pair to search for. If I pass in more than one PrblFldrAtrbtVal to search for, a blank result set is returned, despite the fact that one or more PrblFldr objects should be matched by the specified PrblFldrAtrbtVal objects.
I think it has something to do with the cb.and() statement in the cq.where() clause. I do want an 'AND', but why is returning back no results?

The query is returning an empty list because the predicates are added with an 'AND'.
i.e., the query being generated is something like;
tmpltAtrbtSeqId = '1' AND fldrAtrbtVal like '%a%'
AND tmpltAtrbtSeqId = '2' AND fldrAtrbtVal like '%b%'
AND tmpltAtrbtSeqId = '3' AND fldrAtrbtVal like '%c%'
when you pass in multiple key/value pairs.
You need to add a 'OR' clause in the for loop.

Related

sql, javax.persistence.criteria: check a database table column with comma separated string values against a list of strings

I have a database table column with comma separated string values like this:
id mycolumn
-- --------
1 A
2 A, B
3 A, B, C
I can't change this, because it's an old customizer database.
I want to check mycolumn against a list of strings:
first query against the list ['A'] should be return the record 1
second query against the list ['A','B'] should be return records 1,2
third query against the list ['A','B','C'] should be return records 1,2,3
I need a sql (postgres) statement and further a java implementation with javax.persistence.criteria.CriteriaBuilder and javax.persistence.criteria.Predicate.
Thank you very much for your hints, Ann
edit:
Here my solution for the javax.persitence part:
public static void arrayContains(final CriteriaBuilder cb, final List<Predicate> where,
final Expression<String> expression, final String values) {
Expression<String> expressionValues = cb.function("string_to_array", String.class, cb.literal(values), cb.literal(", "));
Expression<String> expressionDb = cb.function("string_to_array", String.class, expression, cb.literal(", "));
where.add(cb.isTrue(cb.function("arraycontains", Boolean.class, expressionValues, expressionDb)));
}
In PostgreSQL use string_to_array(mycolumn,',') in the WHERE clause with the containment operator <#, e.g.
SELECT * FROM t
WHERE string_to_array(mycolumn,',') <# ARRAY['A','B','C']
Keep in mind that you have to create an index that correspond to this condition, in case you're dealing with a large table, e.g. a GIN index.
CREATE INDEX idx_t_mycolumn_gin ON t USING gin (string_to_array(mycolumn,','));
Demo: db<>fiddle

How do I retrieve the number of distinct rows with the JPA Criteria API

I need to count the number of rows returned by a CriteriaQuery. The relevant piece of information here is that those need to be distinct rows based on a dynamically selected set of columns.
This means, that I cannot count over the entire table, since that might consider results, that would be redundant if you strip a certain number of columns.
I have a List of Predicates and Selections to conform to:
private final List<Selection<?>> projection = new ArrayList<>();
private final List<Predicate> predicates = new ArrayList<>();
and I want to count the number of rows that would be returned, if this query was executed non-paginated:
criteriaQuery.multiselect(projection)
.distinct(true)
.where(cb.and(predicates.toArray(new Predicate[0])));
The usual approach of transforming this into a Subquery will not work, since you cant multiselect on a Subquery and also can't select from the Subquery.
Can this be done with the Criteria API?
can you try this ?
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery query = builder.createQuery(String.class);
Root ruleVariableRoot = query.from(RuleVar.class);
query.select(ruleVariableRoot.get(RuleVar_.varType)).distinct(true);
More infos: https://www.objectdb.com/java/jpa/query/criteria

How to use OR queries in greenDAO?

I have a query like:
Select * from table where user = 'user1' or city = 'delhi';
I know how to do it for single user but I am not getting how can I use or in following query.
dao.queryBuilder()
.where(UserDao.Properties.UserId.eq(userId1))
.list();
For Version 3.2.+, here is an example to use a combination of Where() and WhereOr() conditions. The below is a hypothetical query to select all items:
That have the tags 'paint', 'emulsion'
That belong to a specific Category
Excluding a particular Sub Category
The Where() method takes exactly one query condition and the WhereOr() can take multiple query conditions (as many as the number of properties in the Dao Class), separated by comma
String catgId = "AB12545";
String excludeSubCatgId = "SAB09990";
DaoSession daoSession = ((App) getApplication()).getDaoSession();
List<Item> = daoSession.getItemDao().queryBuilder()
.where(ItemDao.Properties.CategoryId.eq(catgId))
.where(ItemDao.Properties.SubCategory.notEq(excludeSubCatgId))
.whereOr(ItemDao.Properties.ItemTagCloud.like("%paint%"),
ItemDao.Properties.ItemTagCloud.like("%emulsion%"))
.orderDesc(ItemDao.Properties.ItemPrice)
.list();
In order to use or conditions in greenDAO, you have to use or method in QueryBuilder object.
Example:
QueryBuilder<User> qb = dao.queryBuilder();
qb.where(UserDao.Properties.UserId.eq(userId1), qb.or(UserDao.Properties.City.eq("delhi")));
List<User> users = qb.list();
For more details, see the "Queries" section in greenDAO documentation.
Try this:
QueryBuilder<User> qb = dao.queryBuilder();
qb.whereOr(UserDao.Properties.UserId.eq(userId1),
UserDao.Properties.City.eq("delhi"));
List<User> users = qb.list();

Hibernate - sqlQuery map redundant records while using JOIN on OneToMany

I have #OneToMany association between 2 entities (Entity1 To Entity2).
My sqlQueryString consists of next steps:
select ent1.*, ent2.differ_field from Entity1 as ent1 left outer join Entity2 as ent2 on ent1.item_id = ent2.item_id
Adding some subqueries and writing results to some_field2, some_field3 etc.
Execute:
Query sqlQuery = getCurrentSession().createSQLQuery(sqlQueryString)
.setResultTransformer(Transformers.aliasToBean(SomeDto.class));
List list = sqlQuery.list();
and
class SomeDto {
item_id;
some_filed1;
...
differ_field;
...
}
So the result is the List<SomeDto>
Fields which are highlighted with grey are the same.
So what I want is to group by, for example, item_id and
the List<Object> differFieldList would be as aggregation result.
class SomeDto {
...fields...
List<Object> differFieldList;
}
or something like that Map<SomeDto, List<Object>>
I can map it manually but there is a trouble:
When I use sqlQuery.setFirstResult(offset).setMaxResults(limit)
I retrieve limit count of records. But there are redundant rows. After merge I have less count actually.
Thanks in advance!
If you would like to store the query results in a collection of this class:
class SomeDto {
...fields...
List<Object> differFieldList;
}
When using sqlQuery.setFirstResult(offset).setMaxResults(n), the number of records being limited is based on the joined result set. After merging the number of records could be less than expected, and the data in List could also be incomplete.
To get the expected data set, the query needs to be broken down into two.
In first query you simply select data from Entity1
select * from Entity1
Query.setFirstResult(offset).setMaxResults(n) can be used here to limit the records you want to return. If fields from Entity2 needs to be used as condition in this query, you may use exists subquery to join to Entity2 and filter by Entity2 fields.
Once data is returned from the query, you can extract item_id and put them into a collection, and use the collection to query Entity 2:
select item_id, differ_field from Entity2 where item_id in (:itemid)
Query.setParameterList() can be used to set the item id collection returned from first query to the second query. Then you will need to manually map data returned from query 2 to data returned from query 1.
This seems verbose. If JPA #OneToMany mapping is configured between the 2 entity objects, and your query can be written in HQL (you said not possible in comment), you may let Hibernate lazy load Entity2 collection for you automatically, in which case the code can be much cleaner, but behind the scenes Hibernate may generate more query requests to DB while lazy loading the entity sitting at Many side.
The duplicated records are natural from a relational database perspective. To group projection according to Object Oriented principles, you can use a utility like this one:
public void visit(T object, EntityContext entityContext) {
Class<T> clazz = (Class<T>) object.getClass();
ClassId<T> objectClassId = new ClassId<T>(clazz, object.getId());
boolean objectVisited = entityContext.isVisited(objectClassId);
if (!objectVisited) {
entityContext.visit(objectClassId, object);
}
P parent = getParent(object);
if (parent != null) {
Class<P> parentClass = (Class<P>) parent.getClass();
ClassId<P> parentClassId = new ClassId<P>(parentClass, parent.getId());
if (!entityContext.isVisited(parentClassId)) {
setChildren(parent);
}
List<T> children = getChildren(parent);
if (!objectVisited) {
children.add(object);
}
}
}
The code is available on GitHub.

How to run an aggregate function like SUM on two columns in JPA and display their results?

I am new to JPA. So my question should be so simple to some.
Below is the Simple Query in SQL which i would like to convert to JPA. I already have an entity class called TimeEnt.
SELECT
SUM(TimeEntryActualHours) as UnBilledHrs,
SUM (TimeEntryAmount) as UnbilledAmount
FROM TimeEnt WHERE MatterID = 200
The JPA Query Language does support aggregates functions in the SELECT clause like AVG, COUNT, MAX, MIN, SUM and does support multiple select_expressions in the SELECT clause, in which case the result is a List of Object array (Object[]). From the JPA specification:
4.8.1 Result Type of the SELECT Clause
...
The result type of the SELECT
clause is defined by the the result
types of the select_expressions
contained in it. When multiple
select_expressions are used in the
SELECT clause, the result of the query
is of type Object[], and the
elements in this result correspond in
order to the order of their
specification in the SELECT clause
and in type to the result types of
each of the select_expressions.
In other words, the kind of query you mentioned in a comment (and since you didn't provide your entity, I'll base my answer on your example) is supported, no problem. Here is a code sample:
String qlString = "SELECT AVG(x.price), SUM(x.stocks) FROM Magazine x WHERE ...";
Query q = em.createQuery(qlString);
Object[] results = (Object[]) q.getSingleResult();
for (Object object : results) {
System.out.println(object);
}
References
JPA 1.0 Specification
4.8.1 Result Type of the SELECT Clause
4.8.4 Aggregate Functions in the SELECT Clause
Lets think we have entity called Product:
final Query sumQuery = entityManager
.createQuery("SELECT SUM(p.price), SUM(p.sale) FROM Product p WHERE p.item=:ITEM AND ....");
sumQuery.setParameter("ITEM","t1");
final Object result= sumQuery.getSingleResult(); // Return an array Object with 2 elements, 1st is sum(price) and 2nd is sum(sale).
//If you have multiple rows;
final Query sumQuery = entityManager
.createQuery("SELECT SUM(p.price), SUM(p.sale) FROM Product p WHERE p.item in (" + itemlist
+ ") AND ....");
// Return a list of arrays, where each array correspond to 1 item (row) in resultset.
final List<IEniqDBEntity> sumEntityList = sumQuery.getResultList();
Take a look at the EJB Query Language specification.
The idiom is very similiar to standard SQL
EntityManager em = ...
Query q = em.createQuery ("SELECT AVG(x.price) FROM Magazine x");
Number result = (Number) q.getSingleResult ();
Regards,

Categories

Resources