Criteria API - OR comperator with unknown number of comparisons - java

I'm trying to implement some kind of data filtering opportunity for my Java web Application.
In my case user should be able to filter clients who - for example where born after 2 December and have clientSate ACTIVE or INCATIVE.
In my case ACTIVE and INACTIVE are enum values.
Since user can choose 2 or 3 or even 5 different statuses I'm doing it like this:
for (EnumValue enumValue : constraint.getValues().getEnumValue()) {
ClientState state = ClientState.valueOf(enumValue.getValue());
predicate = builder.or(builder.equal(client.get(constraint.getField().getValue()), state));
}
but it doesn't work.
Here is my full function code:
public List<Client> getClients(List<FilterConstraint> filters) {
try {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<?> mainQuery = builder.createQuery(Client.class);
Root<Client> client = mainQuery.from(Client.class);
Predicate predicate = builder.conjunction();
for (FilterConstraint constraint : filters) {
switch (constraint.getOperator()) {
case AFTER:
predicate = builder.and(builder.greaterThan(client.get(constraint.getField().getValue()), constraint.getValues().getStartDate()));
break;
case BEFORE:
predicate = builder.and(builder.greaterThan(client.get(constraint.getField().getValue()), constraint.getValues().getStartDate()));
break;
case BETWEEN:
if (constraint.getField().getType() == FieldDataType.DATE) {
predicate = builder.and(builder.between(client.get(constraint.getField().getValue()), constraint.getValues().getStartDate(), constraint.getValues().getEndDate()));
} else {
predicate = builder.and(builder.between(client.get(constraint.getField().getValue()), constraint.getValues().getMinValue(), constraint.getValues().getMaxValue()));
}
break;
case EMPTY:
predicate = builder.and(builder.isEmpty(client.get(constraint.getField().getValue())));
break;
case EQUALS:
if (constraint.getField().getType() == FieldDataType.ENUM) {
if (constraint.getValues().getEnumValue().size() > 1) {
for (EnumValue enumValue : constraint.getValues().getEnumValue()) {
ClientState state = ClientState.valueOf(enumValue.getValue());
predicate = builder.or(builder.equal(client.get(constraint.getField().getValue()), state));
}
break;
}
ClientState state = ClientState.valueOf(constraint.getValues().getEnumValue().get(0).getValue());
predicate = builder.or(builder.equal(client.get(constraint.getField().getValue()), state));
break;
}
predicate = builder.and(builder.equal(client.get(constraint.getField().getValue()), constraint.getValues().getValue()));
break;
case LESS_THAN:
case MORE_THAN:
case NOT_EMPTY:
case ON:
case STARTS_WITH:
case TODAY:
}
}
CriteriaQuery<Long> cq = builder.createQuery(Long.class);
cq.select(builder.count(cq.from(Client.class)));
em.createQuery(cq);
cq.where(predicate);
Long count = em.createQuery(cq).getSingleResult();
mainQuery.where(predicate);
//TODO Pagination Result should be returned
TypedQuery<?> q = em.createQuery(mainQuery);
List<Client> allClients = (List<Client>) q.getResultList();
return allClients;
} catch (Exception e) {
System.out.println(e.getMessage());
return null;
}
}
I have searched a lot and wasn't able to fund the example where was no exact value for OR operator - actually I found comparing 2 values or 3 - not more.
Can someone tell me how can I fix my code to be able to support any number of values in OR expression ?

CriteriaBuilder.or accepts an array of predicates. So just create an array of "equals enum value" predicates and call or of that array once.
Alternatively, you may want to consider in <enum values> instead.

lexicore is right. You can give it an array like this:
if (constraint.getValues().getEnumValue().size() > 1) {
List<Predicate> predicates = new ArrayList<>();
for (EnumValue enumValue : constraint.getValues().getEnumValue()) {
ClientState state = ClientState.valueOf(enumValue.getValue()); predicates.add(builder.equal(client.get(constraint.getField().getValue()), state));
}
predicate = builder.or(predicates.toArray(new Predicate[]{}));
break;
}
This code should work for you

Related

Return list in alphabetical order using Hibernate

I have a function that returns a list of templates(unsorted); can someone tell me how I can get the list in a sorted format?
public List<FormTemplate> listDomainTemplates(Integer id) {
Domain domain = domainService.getDomain(id);
if (domain == null) {
return new ArrayList<>();
}
CriteriaBuilder cb = sessionFactory.getCurrentSession().getCriteriaBuilder();
CriteriaQuery<FormTemplate> query = cb.createQuery(FormTemplate.class);
Root<FormTemplate> application = query.from(FormTemplate.class);
query.select(application);
Predicate predicate = cb.equal(application.get("domain"), domain);
query.where(predicate);
Query<FormTemplate> q = sessionFactory.getCurrentSession().createQuery(query);
return q.getResultList();
}
Please experiment with following:
query.orderBy(cb.asc(application.get(...));
... - should point to FormTemplate field to sort, I suppose.

Spring boot Is it possible to group predicates or use 'in' conditionally

I'm having issues properly grouping my query using CriteriaBuilder and Predicates.
I want to create a query that can generate something like:
SELECT * from tableName where columnA = '1234' and (columnB Like '%33%' or columnB Like '%44%')
But what I'm getting (Obviously expected looking at the code) is:
SELECT * from tableName where columnA = '1234' and columnB Like '%33%' or columnB Like '%44%'
Which does not produce the same result as the first query.
Been trying to work around it, but this is my first time working with criteriaBuilder and predicates.
Here's the code:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<Foo> criteriaQuery = builder.createQuery(Foo.class);
Root r = criteriaQuery.from(Foo.class);
Predicate predicate = builder.conjunction();
for(Map.Entry<String, String> entry : searchParams.entrySet()){
if((entry.getKey().equalsIgnoreCase("columnK") || entry.getKey().equalsIgnoreCase("columnY") ||
entry.getKey().equalsIgnoreCase("columnZ") || entry.getKey().equalsIgnoreCase("columnJ")) && !Strings.isNullOrEmpty(entry.getValue())){
predicate = builder.and(predicate,
builder.like(r.get(entry.getKey()),
String.format("%%%s%%", entry.getValue().toString())));
}
else if(entry.getKey().equalsIgnoreCase("theDate") && !Strings.isNullOrEmpty(entry.getValue())){
predicate = builder.and(predicate, builder.equal(r.get(entry.getKey()), entry.getValue()));
}
}
//here's where the problem is ... I realize I can use IN, but I also didn't get that to work
boolean isFirstDone = false;
for(String oneId: idStringList){
if(!isFirstDone) {
predicate = builder.and(predicate, builder.equal(r.get("acceptorId"), oneId));
isFirstDone = true;
}
else{
predicate = builder.or(predicate, builder.equal(r.get("acceptorId"), oneId));
}
}
criteriaQuery.where(predicate);
Thank you.
Okay, so I just cracked this after carefully reading this answer https://stackoverflow.com/a/9323183/5038073 The solution is not exactly what I needed but it helped... a lot
I created a path and combined it with my predicate.
Here's what changed:
This:
boolean isFirstDone = false;
for(String oneId: idStringList){
if(!isFirstDone) {
predicate = builder.and(predicate, builder.equal(r.get("acceptorId"), oneId));
isFirstDone = true;
}
else{
predicate = builder.or(predicate, builder.equal(r.get("acceptorId"), oneId));
}
}
became:
Path<Object> path = r.get("acceptorId");
CriteriaBuilder.In<Object> in = builder.in(path);
for (String oneId: idStringList) {
in.value(oneId);
}
predicate = builder.and(predicate, in);
Pretty simple for all the trouble it took me to solve.
Hope this helps someone else too!

A way to query Neo4j by property sorted by highest or lowest values

In Neo4j if I want to find nodes with the highest or lowest property values I can use the Cypher keywords Order By and Limit. I'd like to accomplish the same using the embedded API. I know Lucene can support this but can't see how to do this with Neo's API.
public TopFieldDocs search(Query query, int n, Sort sort)
https://neo4j.com/docs/java-reference/current/javadocs/org/neo4j/graphdb/index/ReadableIndex.html
I looked into this and the answer is Yes, this can be done. The second argument in IndexHits<T> query( String key, Object queryOrQueryObject ) can be a parseable (by org.apache.lucene.queryParser.QueryParser) String, a org.apache.lucene.search.Query, or a org.neo4j.index.lucene.QueryContext
Here's a code snippet that allows for requiring an "EntityType" property to have a specific value, requires a numeric property to be non-blank, and returns the top n hits sorted by the numeric property.
...
List<TopNValue> values = new ArrayList<>();
QueryContext queryContext = getQueryContext(entityType, property, propertyType, n, true);
try (IndexHits<Node> indexHits = nodeIndex.query(property, queryContext))
{
for (Node node : indexHits)
{
values.add(node);
}
}
...
private static QueryContext getQueryContext(String entityType, String property, String type, int n, boolean reversed)
{
NumericRangeQuery propertyQuery;
int fieldType;
switch (type)
{
case GraphTypes.LONG:
propertyQuery = NumericRangeQuery.newLongRange(property, Long.MIN_VALUE, Long.MAX_VALUE, true, true);
fieldType = SortField.LONG;
break;
case GraphTypes.DOUBLE:
propertyQuery = NumericRangeQuery.newDoubleRange(property, Double.MIN_VALUE, Double.MAX_VALUE, true, true);
fieldType = SortField.DOUBLE;
break;
case GraphTypes.FLOAT:
propertyQuery = NumericRangeQuery.newFloatRange(property, Float.MIN_VALUE, Float.MAX_VALUE, true, true);
fieldType = SortField.FLOAT;
break;
case GraphTypes.INT:
propertyQuery = NumericRangeQuery.newIntRange(property, Integer.MIN_VALUE, Integer.MAX_VALUE, true, true);
fieldType = SortField.INT;
break;
default:
throw new IllegalArgumentException("Cannot create NumericRangeQuery for type: " + type);
}
Query query;
if (entityType == null)
{
query = propertyQuery;
}
else
{
TermQuery entityTypeQuery = new TermQuery(new Term("EntityType", entityType));
BooleanQuery booleanQuery = new BooleanQuery();
booleanQuery.add(entityTypeQuery, BooleanClause.Occur.MUST);
booleanQuery.add(propertyQuery, BooleanClause.Occur.MUST);
query = booleanQuery;
}
Sort sorting = new Sort(new SortField(property, fieldType, reversed));
return new QueryContext(query).sort(sorting).top(n);
}

Filtering data with CriteriaBuilder to compare enum values with literals not working

I have a java class with a enum field,
org.example.Importacion {
...
#Enumerated(EnumType.STRING)
private EstadoImportacion estadoImportacion;
public static enum EstadoImportacion {
NOT_VALID, IMPORTED, ERROR, VALID
}
}
When I create a Query with CriteriaBuilder and I try to compare the enum values, one from a filter to the criteriabuilder using literals, the final result of the query does not filter the enum values, so if I send org.example.Importacion.EstadoImportacion.ERROR to the iterator method, the rersult will not filter ERROR on the filnal result list.
The companyCod filters ok, so If I send "COMPANY001" as a companyCode, the querybuilder filters the final result.
I would like to know how to compare enums in the query:
public Iterator<Importacion> iterator (
long first,
long count,
String companyCod,
org.example.Importacion.EstadoImportacion estado) {
CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery<Importacion> criteria = cb.createQuery(Importacion.class);
Root<Importacion> desembolso = criteria.from(Importacion.class);
criteria.select(desembolso);
Predicate p = cb.conjunction();
if(companyCod != null) {
p = cb.and(p, cb.equal(desembolso.get("codigo"), companyCod));
//This part works fine!
}
if (estado != null) {
Expression<org.example.Importacion.EstadoImportacion> estadoImportacion = null;
if (estado.equals(org.example.Importacion.EstadoImportacion.ERROR)) {
estadoImportacion = cb.literal(org.example.Importacion.EstadoImportacion.ERROR);
}
if (estado.equals(org.example.Importacion.EstadoImportacion.IMPORTED)) {
estadoImportacion = cb.literal(org.example.Importacion.EstadoImportacion.IMPORTED);
}
if (estado.equals(org.example.Importacion.EstadoImportacion.NOT_VALID)) {
estadoImportacion = cb.literal(org.example.Importacion.EstadoImportacion.NOT_VALID);
}
if (estado.equals(org.example.Importacion.EstadoImportacion.VALID)) {
estadoImportacion = cb.literal(org.example.Importacion.EstadoImportacion.VALID);
}
p = cb.and(p, cb.equal(estadoImportacion, cb.literal(estado)));
//Doesn't seems to compare enum values
}
criteria.where(p);
javax.persistence.Query query = em.createQuery(criteria);
query.setMaxResults((int)count + (int)first + 1);
query.setFirstResult((int)first);
List resultList = query.getResultList();
Iterator iterator = (Iterator) resultList.iterator();
LOGGER.info("desembolso size: {}", resultList.size());
return iterator;
}
Your criteria compares a literal with the enum. That's not what you want. You want to compare the Importacion's estadoImportacion with the given estado:
Predicate p = cb.conjunction();
if(companyCod != null) {
p = cb.and(p, cb.equal(desembolso.get("codigo"), companyCod));
}
if (estado != null) {
p = cb.and(p, cb.equal(desembolso.get("estadoImportacion"), estado));
}

Ormlite Where issue with parenthesis, building meta-layer query library

I've read the other posts and the docs about how to use the "Where" clause to "create" parenthesis statements.
My requirement is simple:
... WHERE companyID=1 AND (director=true OR officer=true) ;
I'm writing a routine that takes an array of Object, which are then parsed into an Ormlite Where call. a typical call looks like this:
.., "companyID", 1, Q.AND, Q.Bracket, "director", true, Q.OR, "officer", true, Q.Bracket)
The intent is to speed up simple queries. There is no desire to replace Ormlite's querying tools. This is a simple meta-layer on top.
Everything works fine for simple queries, since the parameters are processed sequentially and the where clause is built incrementally.
For parenthesis I am postponing the processing until the bracket is closed.
This is where I am having a problem. The example from the docs I am using shows this:
-- From the OrmLite docs...
Where<Account, String> where = queryBuilder.where();
where.or(
where.and(
where.eq(Account.NAME_FIELD_NAME, "foo"),
where.eq(Account.PASSWORD_FIELD_NAME, "_secret")),
where.and(
where.eq(Account.NAME_FIELD_NAME, "bar"),
where.eq(Account.PASSWORD_FIELD_NAME, "qwerty")));
This produces the following approximate SQL:
SELECT * FROM account
WHERE ((name = 'foo' AND password = '_secret')
OR (name = 'bar' AND password = 'qwerty'))
The key thing I understand from the docs example, is that the same where instance is used in the nested and(...) call. This is precisely what I'm doing but I'm still getting a "Did you forget an AND or an OR" message.
The code implementing the "delayed" processing looks like this:
#SuppressWarnings("unchecked")
private void processWhere(Where<?, ?> where, Q q, List<QValue> list)
{
if (null == list || list.size() < 2)
{
System.err.println("Invalid where passed: " + list);
return;
}
if (q.equals(Q.AND))
where.and(getCondition(where, list.get(0)), getCondition(where, list.get(1)));
else
where.or(getCondition(where, list.get(0)), getCondition(where, list.get(1)));
}
The "QValue" item is just a "holder" for column, condition and value data.
The "getCondition" method is as follows:
#SuppressWarnings("rawtypes")
protected Where getCondition(Where<?, ?> where, QValue qv)
{
if (null != where && null != qv)
return getCondition(where, qv.getType(), qv.getColumn(), qv.getValue(), qv.getValue2());
else
return null;
}
#SuppressWarnings("rawtypes")
protected Where getCondition(Where<?, ?> where, Q cond, String key, Object val, Object val2)
{
if (null == where || null == cond || null == key || null == val)
return null;
SelectArg arg = new SelectArg();
arg.setValue(val);
try
{
switch (cond)
{
case NotNull:
where.isNotNull(key);
break;
case IsNull:
where.isNull(key);
break;
case Equals:
where.eq(key, arg);
break;
case NotEqual:
where.ne(key, arg);
break;
case GreaterThan:
where.gt(key, arg);
break;
case LessThan:
where.lt(key, arg);
break;
case Like:
arg.setValue("%" + val + "%");
where.like(key, arg);
break;
case LikeStart:
arg.setValue("" + val + "%");
where.like(key, arg);
break;
case LikeEnd:
arg.setValue("%" + val);
where.like(key, arg);
break;
case Between:
if (null != val && null != val2)
where.between(key, val, val2);
break;
default:
where.eq(key, arg);
break;
}
}
catch (SQLException e)
{
GlobalConfig.log(e, true);
return null;
}
return where;
}
As far as I can tell, I'm using the Where object correctly, but I am still getting a:
"Did you forget an AND or OR?" message.
I've tried creating "new" Where clauses with QueryBuilder:
Where w1 = qb.where() ;
//process w1 conditions...
return where.query() ;
Which also fails or generates incorrect SQL in the various combinations I've tried. Any suggestions on how to get the and(...) and or(...) methods working properly will be greatly appreciated.
BTW once the library is working properly, I'll put it up as Open Source or donate it to Gray, or both.
Thanks in advance.
Anthony
I faced the same issue and solved it like this:
where.eq("companyID", 1);
where.and(where, where.or(where.eq("director", true), where.eq("officer", true)));
or
where.and(where.eq("companyID", 1), where.or(where.eq("director", true), where.eq("officer", true)));
which in SQL gives us:
((`companyID` = 1 AND `director` = 1 ) OR (`companyID` = 1 AND `officer` = 1 ))
It's not identical to your example clause WHERE companyID=1 AND (director=true OR officer=true) but has the same meaning.

Categories

Resources