Como definir parâmetros posicionais / nomeados dinamicamente para consulta de critérios JPA?

Provedor de hibernação não gera declaração preparada paranão-string digite os parâmetros, a menos que estejam configurados paraentityManager.createQuery(criteriaQuery).setParameter(Parameter p, T t); como feito pelo EclipseLink, por padrão.

Qual é a maneira de definir esses parâmetros, se eles são fornecidos dinamicamente em tempo de execução. Por exemplo,

CriteriaBuilder criteriaBuilder=entityManager.getCriteriaBuilder();
CriteriaQuery<Long>criteriaQuery=criteriaBuilder.createQuery(Long.class);
Metamodel metamodel=entityManager.getMetamodel();
EntityType<Product> entityType = metamodel.entity(Product.class);
Root<Product> root = criteriaQuery.from(entityType);
criteriaQuery.select(criteriaBuilder.count(root));

List<Predicate>predicates=new ArrayList<Predicate>();

for (Map.Entry<String, String> entry : filters.entrySet()) {
    if (entry.getKey().equalsIgnoreCase("prodId")) {
        predicates.add(criteriaBuilder.equal(root.get(Product_.prodId), Long.parseLong(entry.getValue().trim())));
    } else if (entry.getKey().equalsIgnoreCase("prodName")) {
        predicates.add(criteriaBuilder.like(root.get(Product_.prodName), "%" + entry.getValue().trim() + "%"));
    } else if (entry.getKey().equalsIgnoreCase("marketPrice")) {
        predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get(Product_.marketPrice), new BigDecimal(entry.getValue().trim())));
    } else if (entry.getKey().equalsIgnoreCase("salePrice")) {
        predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get(Product_.salePrice), new BigDecimal(entry.getValue().trim())));
    } else if (entry.getKey().equalsIgnoreCase("quantity")) {
        predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get(Product_.quantity), Integer.valueOf(entry.getValue().trim())));
    }
}

if (!predicates.isEmpty()) {
    criteriaQuery.where(predicates.toArray(new Predicate[0]));
}

Long count = entityManager.createQuery(criteriaQuery).getSingleResult();

Neste caso, o parâmetro paraprodName é um parâmetro do tipo String. Assim, é automaticamente ligado a um parâmetro posicional?. O resto (tipo sem string) não são, no entanto. Todos eles são apenas substituídos por seus valores.

Eles precisam ser configurados paracreateQuery() usando, por exemplo,

ParameterExpression<BigDecimal> exp=criteriaBuilder.parameter(BigDecimal.class);
entityManager.createQuery(criteriaQuery).setParameter(exp, new BigDecimal(1000));

Como definir esses parâmetros dinâmicos para que uma consulta preparada possa ser gerada?

Isso realmente não é necessário no EclipseLink, onde eles são automaticamente mapeados para parâmetros posicionais.

Isso pode ser feito com o provedor de hibernação?

Estou usando o JPA 2.0 fornecido pelo Hibernate 4.2.7 final.

Se todos os parâmetros forem definidos, a instrução gerada pela consulta de critérios acima será semelhante à mostrada abaixo.

SELECT count(product0_.prod_id) AS col_0_0_ 
FROM   projectdb.product product0_ 
WHERE  product0_.market_price >= 1000 
       AND ( product0_.prod_name LIKE ? ) 
       AND product0_.prod_id = 1 
       AND product0_.quantity >= 1 
       AND product0_.sale_price >= 1000 

Acabei de executar a consulta de critérios acima no EclipseLink (2.3.2) que resultou na produção da seguinte instrução SQL.

SELECT count(prod_id) 
FROM   projectdb.product 
WHERE  ( ( ( ( ( market_price >= ? ) 
           AND prod_name LIKE ? ) 
         AND ( prod_id = ? ) ) 
       AND ( quantity >= ? ) ) 
     AND ( sale_price >= ? ) ) 

bind => [1000, %product1%, 1, 1, 1000]

isto é, uma consulta parametrizada.

Fazer como o seguinte não é possível.

//...

TypedQuery<Long> typedQuery = entityManager.createQuery(criteriaQuery);

for (Map.Entry<String, String> entry : filters.entrySet()) {
    if (entry.getKey().equalsIgnoreCase("discountId")) {
        ParameterExpression<Long> parameterExpression = criteriaBuilder.parameter(Long.class);
        predicates.add(criteriaBuilder.equal(root.get(Discount_.discountId), parameterExpression));
        typedQuery.setParameter(parameterExpression, Long.parseLong(entry.getValue().trim()));
    }

    //...The rest of the if-else-if ladder.
}

//...
//...
//...
Long count = typedQuery.setFirstResult(first).setMaxResults(pageSize).getSingleResult();

Isso faria com que ojava.lang.IllegalArgumentException: Name of parameter to locate cannot be null exceção a ser lançada.

questionAnswers(1)

yourAnswerToTheQuestion