分类
persistence

Criteria API IN表达式使用示例

Criteria API – An Example of IN Expressions

1. 概述

我们在进行查询时,经常会遇到查询实体的某个字段位于某个范围的情况。

本文我们将介绍如何使用Criteria API来解决此类问题。

2. 示例实体

在正式开始以前,我们先准备如下两个实体:Department以及DeptEmployee,这两个实体间的关系为 1:n

DeptEmployee实体如下,该实体中使用@ManyToOne注解声明了多对一的关系。

@Entity
public class DeptEmployee {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;
 
    private String title;
 
    @ManyToOne
    private Department department;
    
    // 以下省略了setter/getter
}

Department 实体如下,使用@OneToMany注解声明了一对多的关系:

@Entity
public class Department {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;
 
    private String name;
 
    @OneToMany(mappedBy="department")
    private List<DeptEmployee> employees;

    // 以下省略了setter/getter
}

3. CriteriaBuilder.In

首先让我们看一下CriteriaBuilder接口。该接口上定义了一个in()方法,此方法接收的参数类型为 Expression,返回值类型为Predicate。将返回的Predicate做为查询条件传入至where()方法中,即可将范围做为某个字段的查询条件:

CriteriaQuery<DeptEmployee> criteriaQuery = 
  criteriaBuilder.createQuery(DeptEmployee.class);
Root<DeptEmployee> root = criteriaQuery.from(DeptEmployee.class);
In<String> inClause = criteriaBuilder.in(root.get("title"));
for (String title : titles) {
    inClause.value(title);
}
criteriaQuery.select(root).where(inClause);

在Spring Boot中,你可以如下获取criteriaBuilder(GITHUB示例代码):

    @Autowired
    EntityManager entityManager;
    ...
    CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();

或者配合仓库层如下使用(GITHUB示例代码):

    @Test
    void spring() {
        String[] titles = {title};
        Specification<DeptEmployee> deptEmployeeSpecification = new Specification<DeptEmployee>() {
            @Override
            public Predicate toPredicate(Root<DeptEmployee> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder criteriaBuilder) {

                CriteriaBuilder.In<String> inClause = criteriaBuilder.in(root.get("title"));
                for (String title : titles) {
                    inClause.value(title);
                }
               return inClause;
            }
        };


        assertEquals(10,  this.deptEmployeeCrudRepository.findAll(deptEmployeeSpecification).size());
    }

4. Expression.In

另外,还可以使用Expression接口提供的in()方法:

criteriaQuery.select(root)
  .where(root.get("title")
  .in(titles));

CriteriaBuilder.in()不同的是,Expression.in() 接收的参数类型为Collection。对比上述两处代码可以看出Expression.in() 的写法更简单一些。

5. 在子查询中使用IN表达式

接下来让我们看看如何在子查询中使用IN表达式。

比如有如下需求:查询出所有部门名称中包含某个搜索值的所有部门Department的所有员工DeptEmployee

        Subquery<Department> subquery = criteriaQuery.subquery(Department.class);
        Root<Department> dept = subquery.from(Department.class);
        subquery.select(dept)
                .distinct(true)
                .where(criteriaBuilder.like(dept.get("name"), "%" + departmentName + "%"));

        criteriaQuery.select(employeeRoot)
                                                            .where(criteriaBuilder.in(employeeRoot.get("department")).value(subquery));

如上代码:我们创建一个名为subquery的子查询,并将其做为一个表达式传入了value()方法中。

6. 总结

本文中,我们介绍了几种IN查询的方法,在文章的最后对子查询给出了示例。希望对你能有所帮助。