I came accross an interesting behaviour: probably criteria API puts single quotes around parameters in query.
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<SomeClass> criteriaQuery = criteriaBuilder.createQuery(SomeClass.class);
Metamodel metamodel = em.getMetamodel();
EntityType<SomeClass> entityType_ = metamodel.entity(SomeClass.class);
Root<SomeClass> root = criteriaQuery.from( SomeClass );
criteriaQuery.select(root.get(entityType_.getSingularAttribute( "someField" ));
TypedQuery<SomeClass> q = em.createQuery(criteriaQuery);
List<SomeClass> result = (List<SomeClass>) q.getResultList();
this snippet results in a list with one column which is full of with "someField" in every single cells of the column. (select 'someField' from SomeClass; <--really works )
The select accepts this behaviour by criteria, however the group by fails, says: ORA-00979: not a GROUP BY expression.
I only think about criteria does the same substitution, like in case of select.
criteriaQuery.groupBy(root.get(entityType_.getSingularAttribute("someField") ));
How can I avoid those single qoutes in my queries?
Any suggests appreciated, thank You in advance.
Related
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
I have a query that I want to make Criteria Query
select u.email, st.total_amount, st.company_total from users u
join (select user_id, SUM(balance) as total_amount,SUM(company_count) as company_total from subscription s
where s.is_active = 0
group by user_id) st on u.id = st.user_id
where u.is_active = 0
order by st.company_total
I have already made 1 criteria Query
CriteriaQuery<UserImpl> innerQuery = builder.createQuery(UserImpl.class);
Root<Subscription> subscriptionRoot = innerQuery.from(Subscription.class);
innerQuery.multiselect(subscriptionRoot.get("user").get("id"), builder.sum(subscriptionRoot.get("balance")),
builder.sum(subscriptionRoot.get("companyCount")));
I don't know how to make the outer query in spring hibernate JPA. Can some help.
Defining a JOIN clause is pretty simple. You first call the from method on your CriteriaQuery object to get a Root object. In the next step, you can call the join method to define your JOIN clause.
Following is example of Docs, you can manipulate it as per your example.
You can refer Hibernate docs for the join examples. following is sample code.
CriteriaQuery<String> q = cb.createQuery(String.class);
Root<Order> order = q.from(Order.class);
q.select(order.get("shippingAddress").<String>get("state"));
CriteriaQuery<Product> q2 = cb.createQuery(Product.class);
q2.select(q2.from(Order.class)
.join("items")
.<Item,Product>join("product"));
Docs to refer
Another quick example I found is listed below:
<Y> ListJoin<X, Y> join(ListAttribute<? super X, Y> list);
Quick example (assuming Employee has list of Tasks with many-to-many relation):
CriteriaQuery<Employee> query = criteriaBuilder.createQuery(Employee.class);
Root<Employee> employee = query.from(Employee.class);
ListJoin<Employee, Task> tasks = employee.join(Employee_.tasks);
query.select(employee)
.where(criteriaBuilder.equal(tasks.get(Task_.supervisor),
employee.get(Employee_.name)));
TypedQuery<Employee> typedQuery = entityManager.createQuery(query);
List<Employee> employees = typedQuery.getResultList();
Reference: here
I have a Parent with a OneToMany associations with a Child Table.
I'm trying to write a query with CriteriaBuilder to restrict the results returned from the Child table.
I'm adding a Predicate, something like
cb.equal(parent.get("children").get("sex"), "MALE")
If the Parent has a son or SON and Daughter it's returning that parent but also returning all the children they have.
Hibernate fires off the first query with my predicates but the second query to get the children only uses the JoinColumn in the where clause it doesn't include
cb.equal(parent.get("children").get("sex"), "MALE").
Thoughts?
I am using a SetJoin
children = parent.joinSet("children", JoinType.LEFT)
CLARIFICATION:
public static Specification<Parent> findPlanBenefits(Integer parentId) {
return (parent, query, cb) -> {
Predicate predicates = cb.conjunction();
List<Expression<Boolean>> expressions = predicates.getExpressions();
//Parent Criteria
expressions.add(cb.equal(parent.get("parentId"), parentId));
//Children Criteria
SetJoin<Parent, Children> children = parent.joinSet("children", JoinType.LEFT);
Predicate sex = cb.equal(children.get("sex"), "MALE");
children.on(sex);
return predicates;
};
}
I am afraid, the JOIN ON does not work as you expect in your answer. JOIN ON only tells how to join, and NOT how relationships are loaded.
So, in order to solve your problem you will have to filter the children after they are loaded, or fetch manually all male children with a separate query.
In order to check how JOIN ON works, you could try also the corresponding JPQL query.
UPDATE
OP told that the JPQL queryselect p from parent p join fetch children c where p.parentId = :parentId and c.sex = "MALE" works.
The corresponding CriteriaQuery would look like:
CriteriaQuery<Parent> criteria = cb.createQuery((Class<Parent>) Parent.class);
Root<Parent> parent = criteria.from(Parent.class);
criteria.select((Selection<T>) parent);
SetJoin<Parent, Children> children = parent.joinSet("children", JoinType.LEFT);
Predicate sexPredicate = cb.equal(children.get("sex"), "MALE");
parent.fetch(children);
//parent.fetch("children");//try also this
criteria.where(sexPredicate);
When you create a JOIN (especially when property is collection type, not SingularAttribute, you have to use it to build the criteria, so use
cb.equal(children.get("sex"), "MALE").
instead of
cb.equal(parent.get("children").get("sex"), "MALE").
For the future referrence, this is from another post, that helped me (link):
Instead of parent.joinSet use fetch and then cast it to join:
Join<Parent, Children> join = (Join<Parent, Children>)parent.fetch(Parent_.children);
As mentioned in the post linked above it is not perfect solution but it saved me a lot of headaches.
I'm using Eclipselink and have a tricky problem regarding JPA NamedQueries.
My database table contains a column which is from type VARCHAR and stores a comma separated list of keywords as one String.
How can I create a NamedQuery in JPA to search theese keywords?
I'd like to give a list of Strings as a parameter and as a result I'd like to have a list of objects which keyword list contain one of the Strings from the parameter list.
Maybe like the following:
List<String> keywordList = new ArrayList<String>();
keywordList.add("test");
keywordList.add("car");
List<Object> result = em.createNamedQuery("findObjectByKeywords", Object.class)
.setParameter("keywords", keywordList)
.getResultList();
Unfortunately I'm not such a big database/SQL expert. Maybe someone of you can help me?
I hope you understand my problem.
Edit:
I am developing on Weblogic 10.3.6, which means I am not able to use JPA 2.0 features.
Edit2:
I managed to activate JPA 2.0 in my Weblogic Server with the help of Oracle Enterprise Pack for Eclipse. Problem solved, I think.
VALID FOR JPA2.0
As Bhesh commented a simple JPQL won't make it. The resulting SQL has to contain a where clause similar to following:
where keywords like '%keyword1%' or keywords like '%keyword2%' or ... or keywords like '%keywordN%'
This means: We need a loop here!
You could try to build a JPQL by yourself like Bhesh suggested in his first comment, though as he also stated it is not a brilliant idea. But don't worry - JPA provides also a Criteria API which comes handy in such situations. So, although you're not going to have a named query, you can still make it with JPA this way:
public List<YourEntity> findAllByKeywords(List<String> keywords){
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<YourEntity> query = builder.createQuery(YourEntity.class);
Root<YourEntity> root = query.from(YourEntity.class);
List<Predicate> predicates = new LinkedList<>();
for (String keyword : keywords) {
predicates.add(builder.like(root.<String>get("keywords"), "%" + keyword + "%"));
}
return entityManager.createQuery(
query.select(root).where(
builder.or(
predicates.toArray(new Predicate[predicates.size()])
)
))
.getResultList();
}
or (always slightly better with Guava)
public List<YourEntity> findAllByKeywords(List<String> keywords){
final CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<YourEntity> query = builder.createQuery(YourEntity.class);
final Root<YourEntity> root = query.from(YourEntity.class);
return entityManager.createQuery(
query.select(root).where(
builder.or(
transform(keywords, toPredicateFunction(builder, root)).toArray(new Predicate[]{})
)
))
.getResultList();
}
private Function<String, Predicate> toPredicateFunction(final CriteriaBuilder builder, final Root<YourEntity> root) {
return new Function<String, Predicate>() {
#Override
public Predicate apply(String input) {
return builder.like(root.<String>get("keywords"), "%" + input + "%");
}
};
}
I'm building a SQL query with QueryDSL that contains several subqueries joined in a union. This is the base of my query:
QTransaction t = QTransaction.transaction;
query = query.from(t).where(t.total.gt(BigDecimal.ZERO));
I then have several subqueries to obtain client names associated with a transaction. I've cut down to two for the example:
SQLSubQuery subQuery = new SQLSubQuery();
subQuery = subQuery.from(t).join(t.fk462bdfe3e03a52d4, QClient.client);
ListSubQuery clientByPaid = subQuery.list(t.id, bt.paidId, QClient.client.name.as("clientname"));
subQuery = new SQLSubQuery();
subQuery = subQuery.from(t).where(t.paidId.isNull(), t.clientname.isNotNull());
ListSubQuery clientByName = subQuery.list(t.id, Expressions.constant(-1L), t.clientname.as("clientname"));
How do I union these together, and join the union with my main query? This is my current attempt:
subQuery = new SQLSubQuery();
subQuery = subQuery.from(subQuery.unionAll(clientByPaid,clientByName).as("namequery"));
query = query.leftJoin(subQuery.list(
t.id, Expressions.path(Long.class, "clientid"),
Expressions.stringPath("clientname")),
Expressions.path(List.class, "namequery"));
This compiles, but generates invalid SQL at runtime when I attempt query.count(). Likely mistakes:
The syntax for the union of subqueries.
The connection between the .as(...) expression that names the subquery result columns and the path expression used in the leftJoin.
Fixed it. The main bug was that I'd missed out the on clause in the left join, but in order to express the on condition I had to be much more careful about naming the subqueries. The documentation is a little light on constructing paths to access subquery results, so here's the example.
The first query in the union sets the column names:
SQLSubQuery subQuery = new SQLSubQuery();
subQuery = subQuery.from(t).join(t.fk462bdfe3e03a52d4, QClient.client);
ListSubQuery clientByPaid = subQuery.list(t.id.as("id"), t.paidId.as("clientid"),
QClient.client.name.as("clientname"));
subQuery = new SQLSubQuery();
subQuery = subQuery.from(t).where(t.paidId.isNull(), t.clientname.isNotNull());
ListSubQuery clientByName = subQuery.list(t.id, Expressions.constant(-1L),
t.clientname);
I now need to build a path expressions to refer back to my inner query. It doesn't seem to matter which class I use for the path, so I've picked Void to emphasize this.
subQuery = new SQLSubQuery();
Path innerUnion = Expressions.path(Void.class, "innernamequery");
subQuery = subQuery.from(subQuery.union(clientByPaid,clientByName).as(innerUnion));
And a further path expression to express the on clause. Note that I join to a list() of the union query, with each column selected using the innerUnion path defined earlier.
Path namequery = Expressions.path(Void.class, "namequery");
query = query.leftJoin(subQuery.list(
Expressions.path(Long.class, innerUnion, "id"),
Expressions.path(Long.class, innerUnion, "clientid"),
Expressions.stringPath(innerUnion, "clientname")),
namequery)
.on(t.id.eq(Expressions.path(Long.class, namequery, "id")));