简体   繁体   中英

Generate a “where in” statement using the Criteria API in Hibernate

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 ?

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)

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).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM