×

【一目了然】Spring Data JPA使用Specification动态构建多表查询、复杂查询及排序示例

96
三汪
2017.11.14 14:49* 字数 379

本文阅读时长5分钟。由作者三汪首发于简书。


前言

发这篇的目的是为了提供一篇涵盖Specification各种写法的备忘。
Specification算是JPA中比较灵活的查询方式了,
也少不了Criteria类型安全和面向对象的优点。
之前都是点到即止的简单使用,刚好最近系统地使用了一下,遂有本文。

示例

(先贴用法,再贴环境配置。有不明白的地方欢迎留言讨论。)

1. 用法

@Service
public class StudentService implements IStudentService {

    @Autowired
    private IStudentRepository repository;

    //无关代码略

    @Override
    public List<Student> getStudent(String studentNumber,String name ,String nickName,
            Date birthday,String courseName,float chineseScore,float mathScore,
            float englishScore,float performancePoints) {
        Specification<Student> specification = new Specification<Student>(){

            @Override
            public Predicate toPredicate(Root<Student> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
                //用于暂时存放查询条件的集合
                List<Predicate> predicatesList = new ArrayList<>();
                //--------------------------------------------
                //查询条件示例
                //equal示例
                if (!StringUtils.isEmpty(name)){
                    Predicate namePredicate = cb.equal(root.get("name"), name);
                    predicatesList.add(namePredicate);
                }
                //like示例
                if (!StringUtils.isEmpty(nickName)){
                    Predicate nickNamePredicate = cb.like(root.get("nickName"), '%'+nickName+'%');
                    predicatesList.add(nickNamePredicate);
                }
                //between示例
                if (birthday != null) {
                    Predicate birthdayPredicate = cb.between(root.get("birthday"), birthday, new Date());
                    predicatesList.add(birthdayPredicate);
                }
                
                //关联表查询示例
                if (!StringUtils.isEmpty(courseName)) {
                    Join<Student,Teacher> joinTeacher = root.join("teachers",JoinType.LEFT);
                    Predicate coursePredicate = cb.equal(joinTeacher.get("courseName"), courseName);
                    predicatesList.add(coursePredicate);
                }
                
                //复杂条件组合示例
                if (chineseScore!=0 && mathScore!=0 && englishScore!=0 && performancePoints!=0) {
                    Join<Student,Examination> joinExam = root.join("exams",JoinType.LEFT);
                    Predicate predicateExamChinese = cb.ge(joinExam.get("chineseScore"),chineseScore);
                    Predicate predicateExamMath = cb.ge(joinExam.get("mathScore"),mathScore);
                    Predicate predicateExamEnglish = cb.ge(joinExam.get("englishScore"),englishScore);
                    Predicate predicateExamPerformance = cb.ge(joinExam.get("performancePoints"),performancePoints);
                    //组合
                    Predicate predicateExam = cb.or(predicateExamChinese,predicateExamEnglish,predicateExamMath);
                    Predicate predicateExamAll = cb.and(predicateExamPerformance,predicateExam);
                    predicatesList.add(predicateExamAll);
                }
                //--------------------------------------------
                //排序示例(先根据学号排序,后根据姓名排序)
                query.orderBy(cb.asc(root.get("studentNumber")),cb.asc(root.get("name")));
                //--------------------------------------------
                //最终将查询条件拼好然后return
                Predicate[] predicates = new Predicate[predicatesList.size()];
                return cb.and(predicatesList.toArray(predicates));
            }

        
        };
        return repository.findAll(specification);
    }

}

2. 环境配置

pom文件引入依赖(对此尚不了解的请自行补充maven相关知识)

        <!-- jpa -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

Dao层文件继承JpaSpecificationExecutor。这边多继承了一个JpaRepository,不需要的可以自行去除。

@Repository
public interface IStudentRepository extends JpaRepository<Student, String>,JpaSpecificationExecutor<Student>{

}

3. 一个第三方库

如果你觉得每次实现接口重写toPredicate太麻烦了,也可以使用这个第三方库:jpa-spec
,能够减少一些代码量。

示例(更多示例请移步该库github查看)

public Page<Person> findAll(SearchRequest request) {
    Specification<Person> specification = Specifications.<Person>and()
            .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName())
            .gt(Objects.nonNull(request.getAge()), "age", 18)
            .between("birthday", new Range<>(new Date(), new Date()))
            .like("nickName", "%og%", "%me")
            .build();

    return personRepository.findAll(specification, new PageRequest(0, 15));
}

maven依赖

<dependency>
    <groupId>com.github.wenhao</groupId>
    <artifactId>jpa-spec</artifactId>
    <version>3.1.1</version>
</dependency>

4. 推荐阅读

5. 扩展阅读


以上。
希望我的文章对你能有所帮助。
我不能保证文中所有说法的百分百正确,但我能保证它们都是我的理解和感悟以及拒绝复制黏贴。
有什么意见、见解或疑惑,欢迎留言讨论。

Java后端开发
Web note ad 1