下面详细讲解“SpringDataJPA之Specification复杂查询实战”的完整攻略。
一、什么是Specification
Specification(规范)是Spring Data JPA提供的一种查询定义方式,它可以让我们通过编写Java代码构造查询,从而实现类似HQL的灵活嵌入查询的功能。Specification提供了查询复杂条件时的灵活性,可以动态生成查询条件,可读性高,易于维护。
二、Specification的使用场景
Specification常用于以下场景中:
- 混合查询(多个查询条件);
- 复杂查询(多表查询、嵌套查询等);
- 动态查询(查询条件不固定)。
三、Specification的基本用法
Specification主要通过CriteriaQuery来实现,CriteriaQuery表示了一个查询对象,可以通过CriteriaBuilder来创建查询条件,CriteriaBuilder是CriteriaQuery的工厂类。
1、创建Specification
我们可以通过Specification的匿名内部类来创建一个Specification对象,该对象包含了查询条件的组装逻辑。例如:
public static Specification<User> getUserSpecification(String name, Integer age) {
return new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate predicate = cb.conjunction();
if(StringUtils.isNotBlank(name)){
predicate.getExpressions().add(cb.like(root.get("name"), "%" + name + "%"));
}
if(age != null){
predicate.getExpressions().add(cb.equal(root.get("age"), age));
}
return predicate;
}
};
}
上述代码中,我们创建了一个getUserSpecification方法,该方法返回了一个Specification
2、使用Specification进行查询
使用Specification对象进行查询的方法主要有两种,分别为:
- JpaSpecificationExecutor的findAll(Specification
var1)方法; - JpaSpecificationExecutor的findAll(Specification
var1, Pageable var2)方法。
其中,第一种方法可以用于普通查询,第二种方法则用于分页查询。
下面是一个使用Specification进行普通查询的示例代码:
@Autowired
private UserRepository userRepository;
public List<User> findUser(String name, Integer age) {
Specification<User> specification = getUserSpecification(name, age);
return userRepository.findAll(specification);
}
以上代码使用了getUserSpecification方法创建Specification对象,并调用了userRepository的findAll方法进行查询。
四、Specification的高级用法
除了上述的简单Query生成,Specification还支持许多其他功能,比如like查询、between查询、in查询、or查询、not查询等。下面将对这些方法进行逐一介绍:
1、like查询
下面是一个模糊查询的示例代码:
public static Specification<User> likeName(String name) {
return new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.like(root.get("name"), "%"+name+"%");
}
};
}
上述代码中,我们定义了一个方法用于模糊匹配。使用like方法,获取用户名称的关键字作为参数进行查询,并将查询结果以List的形式返回。
2、between查询
下面是一个根据年龄范围进行查询的示例代码:
public static Specification<User> betweenAge(int min, int max) {
return new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.between(root.get("age"), min, max);
}
};
}
上述代码中,我们定义了一个方法,该方法使用了CriteriaBuilder的between方法,以获取用户年龄在[min,max]之间的数据。
3、in查询
下面是一个根据多个名称进行匹配的示例代码:
public static Specification<User> nameIn(List<String> names) {
return new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return root.get("name").in(names);
}
};
}
上述代码中,我们定义了一个方法,该方法使用了CriteriaBuilder的in方法,以获取名称在指定集合中的用户数据。
4、or查询
下面是一个根据name或age进行匹配的示例代码:
public static Specification<User> orNameAndAge(String name, Integer age) {
return new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
Predicate predicate = cb.disjunction();
if(StringUtils.isNotBlank(name)){
predicate.getExpressions().add(cb.like(root.get("name"), "%" + name + "%"));
}
if(age != null){
predicate.getExpressions().add(cb.equal(root.get("age"), age));
}
return predicate;
}
};
}
上述代码中,我们定义了一个方法,该方法使用了CriteriaBuilder的disjunction方法,以获取name或age匹配的用户数据。
5、not查询
下面是一个否定查询的示例代码:
public static Specification<User> notName(String name) {
return new Specification<User>() {
@Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return cb.notEqual(root.get("name"), name);
}
};
}
上述代码中,我们定义了一个方法,该方法使用了CriteriaBuilder的notEqual方法,以获取除名称等于指定值之外的用户数据。
五、使用Specification实现复杂查询
下面我们以一个多表关联查询的场景为例,介绍如何使用Specification实现复杂查询。
在该场景中,我们需要查询班级信息,同时关联查询学生信息。对于多表查询操作,我们需要以下几步完成:
- 定义实体类,并使用@OneToMany或@ManyToOne注解进行关联定义;
- 确认关联表的名称,通过Root来获取表名;
- 使用join或leftJoin方法或者subQuery方式来关联查询;
- 在get方法上使用fetch方法或者subQuery方式来声明关联表。
下面是示例代码:
public static Specification<ClassInfo> queryByNameAndStuName(String className, String stuName) {
return new Specification<ClassInfo>() {
@Override
public Predicate toPredicate(Root<ClassInfo> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> predicates = new ArrayList<>();
if(StringUtils.isNotBlank(className)){
predicates.add(cb.like(root.get("className"), "%" + className + "%"));
}
Join<ClassInfo, Student> stuJoin = root.join("students", JoinType.LEFT);
if(StringUtils.isNotBlank(stuName)){
predicates.add(cb.like(stuJoin.get("stuName"), "%" + stuName + "%"));
}
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
}
};
}
以上代码中,我们定义了一个以班级名称和学生名称为查询条件的Specification对象。在该对象中,我们希望能获取包含关键词的班级和学生信息。
首先,我们在查询中通过Root获取到了主表的表名。然后,我们使用了join方法,声明了主表和从表之间的关系,并指定了从表名称。在最后的查询条件中,我们通过多个and和like方式,获取到了符合条件的数据。
六、Specification的注意点
使用Specification时,需要注意以下点:
- 多个条件的组合查询时,使用and或or操作符进行组合;
- 获取关联表数据时,需要使用fetch方法或者subQuery方式来获取数据,避免n+1问题;
- 所有查询条件都需要由Predicate类型构成;
- Specification中可以嵌套Specification,并使用combine进行组合;
- 避免在Specification中写复杂的查询逻辑,尽量把条件简化,尤其是多表关联查询。
本站文章如无特殊说明,均为本站原创,如若转载,请注明出处:SpringDataJPA之Specification复杂查询实战 - Python技术站