Search code examples
spring-bootspring-data-jpacriteriacriteria-api

Generic criteriaUpdate set boolean = !boolean


I want to use criteriaUpdate to create an update query like this:

UPDATE <SOME TABLE>
SET SELECTED = !SELECTED
WHERE
[DYNAMIC QUERY HERE]

The closest I could get was with the code:

public <T> Query createRevertSelectionQuery(Class<T> clazz, EntityManager em, Specification<T> s) {
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaUpdate<T> criteriaUpdate = cb.createCriteriaUpdate(clazz);
    Root<T> root = criteriaUpdate.from(clazz);
    Predicate p = cb.and(new Predicate[] {s.toPredicate(root, null, cb)});
    Expression<Boolean> e =cb.not((root.get("selected").as(Boolean.class)));
    Path<Boolean> selected = root.get("selected");
    criteriaUpdate.set(selected, e);
    criteriaUpdate.where(p);
    Query q = em.createQuery(criteriaUpdate);       
    return q;       
}

but it fails because I get the following query:

update com.redknee.suspense.mgt.model.Moc as generatedAlias0 
set generatedAlias0.selected = generatedAlias0.selected <> true 
where 
[dynamic query]

giving me the error

org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected token: <> near line 1, column 118

Anyone can help please?


Solution

  • I am not sure if this is a bug or if it is just not meant to be used this way .

    In where-clause NOT and any other operands work like a charm. But, no matter what you try Hibernate query builder seems always to optimize those parenthesis away (in my opinion it might still be a good habit to always use parenthesis but its only an opinion).

    One way to force parenthesis is to use JPA Subquery. See below example. Note that i have slightly altered the JPA object names by my own taste and not included the Specification because it is not relevant to this solution:

    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaUpdate<T> update = cb.createCriteriaUpdate(clazz);
    Root<T> from = update.from(clazz);
    Path<Boolean> selected = from.get("selected");
    
    // Subquery just "joins back" to the same row and
    // returns a negated boolean value of "selected" from the original row
    Subquery<Boolean> subSelect = update.subquery(Boolean.class);
    Root<T> subFrom = subSelect.from(clazz);
    subSelect.select(cb.not(selected));
    subSelect.where(cb.equal(from.get("id"), subFrom.get("id")));
    
    update.set(selected, subSelect);