Spring-data, Oliver Gierke's excellent library, has something called a Specification (org.springframework.data.jpa.domain.Specification). With it you can generate several predicates to narrow your criteria for searching.
Can someone provide an example of using a Subquery from within a Specification?
I have an object graph and the search criteria can get pretty hairy. I would like to use a Specification to help with the narrowing of the search, but I need to use a Subquery to see if some of the sub-elements (within a collection) in the object graph meet the needs of my search.
Thanks in advance.
String projectName = "project1";
List<Employee> result = employeeRepository.findAll(
new Specification<Employee>() {
#Override
public Predicate toPredicate(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Subquery<Employee> sq = query.subquery(Employee.class);
Root<Project> project = sq.from(Project.class);
Join<Project, Employee> sqEmp = project.join("employees");
sq.select(sqEmp).where(cb.equal(project.get("name"),
cb.parameter(String.class, projectName)));
return cb.in(root).value(sq);
}
}
);
is the equivalent of the following jpql query:
SELECT e FROM Employee e WHERE e IN (
SELECT emp FROM Project p JOIN p.employees emp WHERE p.name = :projectName
)
Related
I wanna know is there a way to do something like this in hibernate using criteriaBuilder
select users.first_name,orders.payable,order_item.product_title
from "order" orders
join users on orders.user_id_fk = users.id_pk
join order_item on orders.id_pk = order_id_fk
I need this specially if I have to use group by. I search and google and also read this article but have no clue how can I do this in hibernate:
Query Selection Other Than Entities
querycriteria
hibernate-facts-multi-level-fetching
also I code this for selecting field in first layer and it worked perfectly for selecting first layer but it need some change to work with join and select field from other table than root:
<R> List<R> reportEntityGroupBy(List<String> groupBy, List<String> selects, Class<T> root, Class<R> output) {
CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<R> criteriaQuery = criteriaBuilder.createQuery(output);
Root<T> rootQuery = criteriaQuery.from(root);
if (selects == null || selects.isEmpty()) {
selects = groupBy;
}
criteriaQuery.multiselect(selects.stream().map(rootQuery::get).collect(Collectors.toList()));
criteriaQuery.groupBy(groupBy.stream().map(rootQuery::get).collect(Collectors.toList()));
I use Hibernate 5.4.22.Final and use entityGraph for my join.
I don't know how your selects look like, but I suppose you are using paths i.e. attributes separated by .. In that case, you have to split the paths and call Path#get for each attribute name.
List<Path> paths = selects.stream()
.map(select -> {
Path<?> path = rootQuery;
for (String part : select.split("\\.")) {
path = path.get(part);
}
return path;
})
.collect(Collectors.toList()));
criteriaQuery.multiselect(paths);
criteriaQuery.groupBy(paths);
I'm trying to create a generic ordering and filtering abstraction on my backend, but some values on the web CRUD can be arbitrarily complex so, in some cases, we resorted to using subqueries. The query I'm trying to generate is in this format:
SELECT f, (SELECT COUNT(r.id) FROM Bar b WHERE b.foo = e) c
FROM Foo f
ORDER BY c
When creating the query I'm unable to reproduce that. The subquery is repeated in the WHERE clause, for some reason. This is the error I'm getting:
org.hibernate.hql.internal.ast.QuerySyntaxException: unexpected AST node: query
[select generatedAlias0, (select count(generatedAlias1) from example.Bar as generatedAlias1
where generatedAlias0=generatedAlias1.foo) from example.Foo as generatedAlias0
order by (select count(generatedAlias1) from example.Bar as generatedAlias1
where generatedAlias0=generatedAlias1.foo) asc]
Excerpt from my query code:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> query = cb.createTupleQuery();
Root<T> root = query.from(Foo.class);
Subquery<Long> subquery = query.subquery(Long.class);
Root<?> subqueryRoot = subquery.from(Bar.class);
Expression<Long> count = cb.count(subqueryRoot);
subquery.select(count);
subquery.where(cb.equal(root, subqueryRoot.get("foo")));
query.multiselect(root, subquery.getSelection());
query.orderBy(cb.asc(subquery));
Even if you use CriteriaAPI you can take advantage of other alternatives for sorting. I have not managed to sort by the result of the subquery but you could do one of the following:
JAVA 8
return entityManager.createQuery(cq).getResultList().stream()
.sorted(Comparator.comparing(YourObject::getCount))
.collect(Collectors.toList());
ALL VERSIONS
List<YourObjet> list = entityManager.createQuery(cq).getResultList();
Collections.sort(list, new Comparator<YourObjet>(){
#Override
public int compare(YourObjet o1, YourObjet o2) {
return o1.getCount().compareToIgnoreCase(o2.getCount());
}
});
Sure there are more options, but Java 8 is really optimal
I am trying to solve the following problem with JPA specifications:
Consider the following classes:
public class Language {
private String name;
...
}
public class Person {
private List<Language> languages;
...
}
How do I select all Persons, that speak a language with name x? I am looking for a solution using the CriteriaBuilder, which should generate a Predicate that I can 'and' together with other predicates.
Thanks in advance.
It is actually pretty easy:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Person> query = cb.createQuery(Person.class);
Root<Person> root = query.from(Person.class);
ListJoin<Person, Language> langJoin = root.join(Person_.langs);
query.where(cb.equal(langJoin.get(Language_.name), lang));
So you basically join the languages via corresponding association and then add an equals predicate matching the required attribute of the joined entity with your criteria.
I put in place the following specification that represents the predicate construction for querying Students based on their age and their ClassRoom's teachers' name (one student can have one or more classroom)
public class StudentSpecification implements Specification<Student> {
private final Integer age;
public StudentSpecification(Integer age){
this.age = age;
}
#Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.<Integert>get(age), Integer.valueOf(v)));
SetJoin<Student, ClassRoom> classRooms = root.join(Student_.classRooms);
predicates.add(criteriaBuilder.equal(classRooms.get(ClassRoom_.teacher), "Marta"));
predicates.add(criteriaBuilder.equal(classRooms.get(ClassRoom_.teacher), "Fowler"));
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
}
}
Here is an example of data :
Student
_____________________________________________
ID CLASSROOM_ID NAME AGE
2 120 Pascal 22
8 120 Bryan 21
ClassRoom
_____________________________________________
ID CLASSROOM_ID TEACHER
1 120 Marta
2 120 McAllister
2 120 Fowler
The specification returns nothing.
When I see the generated statement, I understand why it doesn't work :
where
classRooms.teacher=?
and classRooms.teacher=?
I was expecting something like :
where
students0.classroom_id in (
select classrooms0.classroom_id where
classRooms.teacher=?
)
and students0.classroom_id in (
select classrooms0.classroom_id where
classRooms.teacher=?
)
Question : how can make a query with the Criteria API work in my case ?
You will need Subquery to achieve what you want if you need to stick with Criteria API. Otherwise, HQL can be a better choice for the sake of readability compared to the verbosity of Criteria API.
The idea is to generate individual queries and make a manual join through a predicate. So no need for a Join or SetJoin.
First, note that there are some mistakes in your code. The most obvious one is the path you used to reach the age field. You should use the generated metamodel instead of hard coded strings.
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get(Student_.age), age));
instead of :
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.<Integert>get(age), Integer.valueOf(v)));
Then, here is the complete solution :
public static Specification<Student> withTeacherAndName(){
return new Specification<Student>() {
#Override
public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> criteriaQuery,
CriteriaBuilder criteriaBuilder) {
List<Predicate> predicates = new ArrayList<>();
predicates.add(criteriaBuilder.greaterThanOrEqualTo(root.get(Student_.age), 20));
Subquery<String> sq1 = criteriaQuery.subquery(String.class);
Root<Classroom> classroomRoot = sq1.from(Classroom.class);
sq1.select(classroomRoot.get(Classroom_.classroomId));
sq1.where(criteriaBuilder.equal(classroomRoot.get(Classroom_.teacher), "Marta"));
Subquery<String> sq2 = criteriaQuery.subquery(String.class);
Root<Classroom> classroomRoot2 = sq2.from(Classroom.class);
sq2.select(classroomRoot2.get(Classroom_.classroomId));
sq2.where(criteriaBuilder.equal(classroomRoot2.get(Classroom_.teacher), "Fowler"));
criteriaQuery.where(criteriaBuilder.equal(root.get(Student_.classroomId), sq1));
criteriaQuery.where(criteriaBuilder.equal(root.get(Student_.classroomId), sq2));
return criteriaBuilder.and(predicates.toArray(new Predicate[]{}));
}
};
}
So basically you are creating a subquery for each criteria.
The code needs a refactoring (a loop for example).
If you want an in Clause instead of an equals clause just use it:
predicates.add(criteriaBuilder.in(classRooms.get(ClassRoom_.teacher), "Marta"));
predicates.add(criteriaBuilder.in(classRooms.get(ClassRoom_.teacher), "Fowler"));
See https://docs.oracle.com/javaee/6/api/javax/persistence/criteria/CriteriaBuilder.html#in(javax.persistence.criteria.Expression)
I'm stuck with a simple problem; struggling how to invoke order by on a joined entity. Essentially I am trying to achieve the following with JPA Criteria:
select distinct d from Department d
left join fetch d.children c
left join fetch c.appointments a
where d.parent is null
order by d.name, c.name
I have the following:
CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery<Department> c = cb.createQuery(Department.class);
Root<Department> root = c.from(Department.class);
Fetch<Department, Department> childrenFetch = root.fetch(
Department_.children, JoinType.LEFT);
childrenFetch.fetch(Department_.appointments, JoinType.LEFT);
c.orderBy(cb.asc(root.get(Department_.name)));
c.distinct(true);
c.select(root);
c.where(cb.isNull(root.get(Department_.parent)));
How to achieve order by d.name, c.name with Criteria API? I tried with Expression, Path but didn't work.
Any pointers will be greatly appreciated.
If you need to add couple of orders you can make something like (but for your query and different root objects)
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Route> query = criteriaBuilder.createQuery(Route.class);
Root<Route> routeRoot = query.from(Route.class);
query.select(routeRoot);
List<Order> orderList = new ArrayList();
query.where(routeRoot.get("owner").in(user));
orderList.add(criteriaBuilder.desc(routeRoot.get("date")));
orderList.add(criteriaBuilder.desc(routeRoot.get("rating")));
query.orderBy(orderList);
I have the same problem with order by using Criteria API. I found this solution:
CriteriaQuery<Test> q = cb.createQuery(Test.class);
Root<Test> c = q.from(Test.class);
q.select(c);
q.orderBy(cb.asc(c.get("name")), cb.desc(c.get("prenom")));
I was having trouble doing the same, and I have found a solution on this page:
http://www.objectdb.com/api/java/jpa/criteria/CriteriaQuery/orderBy_Order_
//javax.persistence.criteria.CriteriaQuery
//CriteriaQuery<T> orderBy(Order... o)
Specify the ordering expressions that are used to order the query results. Replaces the previous ordering expressions, if any. If no ordering expressions are specified, the previous ordering, if any, is simply removed, and results will be returned in no particular order. The left-to-right sequence of the ordering expressions determines the precedence, whereby the leftmost has highest precedence.
Parameters:
o - zero or more ordering expressions
Returns:
the modified query
Since:
JPA 2.0
The solucion that work for me is the following
session=HibernateUtil.getSessionJavaConfigFactory_report().openSession();
CriteriaBuilder builder = session.getCriteriaBuilder();
CriteriaQuery<Object[]> criteriaQuery = builder.createQuery(Object[].class);
List<Order> orderList = new ArrayList();
orderList.add(builder.desc(ejeRoot.get("name")));
criteriaQuery.orderBy(orderList);
Note: ejeRoot is the class object
categoryRepository.findAll(predicates, new Sort(Direction.ASC, "sortOrder", "name"))
.forEach(categoryDtoList::add);