spring boot,

Spring Data 灵活查询的三种方式

qihaiyan qihaiyan Follow Dec 27, 2023 · 5 mins read

在页面中展示列表数据时,通常需要根据用户输入的不同的查询条件返回不同的查询结果,传统的方式往往采用手动编写原始sql拼接where条件的方式,这种方式并不安全,容易存在sql注入漏洞。

本文介绍用SpringDataJpa实现灵活查询的方式,具体的代码参照 示例项目 https://github.com/qihaiyan/springcamp/tree/master/spring-data-flex-query

一、概述

SpringDataJpa提供了三种灵活查询的方式,分别是:1、通过@Query注解编写查询语句;2、Example查询;3、Specification查询。下面分别介绍这三种方式的使用方法。

二、通过@Query注解编写查询语句

这种方式使用比较简单,在 Repository 方法中编写查询语句。

public interface MyDataRepository extends JpaRepository<MyData, Long>, JpaSpecificationExecutor<MyData> {
    @Query("select U from MyData U where (?1 is null or U.id=?1) and (?2 is null or U.name=?2)")
    List<MyData> findByQuery(Long id, String name);
}

为了能够按照不同的查询条件进行查询,需要在查询语句中对查询参数进行判空,将所有的查询参数组合成and查询条件。当某个查询参数为空时,查询路径会被is null覆盖,该查询参数不会对数据进行过滤。

三、Example查询

Example查询是通过一个数据对象按照约定的规则进行查询,只有对象中的非空字段或加入过滤条件,为空的字段不会进行过滤。

public interface MyService {
    MyData example = new MyData();
        example.setName("two");
        log.info("find by example: {}", myDataRepository.findAll(Example.of(example)));
}

在上述代码中,由于我们只对example对象的name字段赋值,因此只会按照name条件进行过滤。

更加详细的使用方式,可以参照[官方文档(https://docs.spring.io/spring-data/jpa/reference/repositories/query-by-example.html)]

四、Specification查询

相比于前两种方式,Specification查询要灵活的多,可以任意组合查询条件,实现我们想要的查询结果。

首先 Repository 要扩展 JpaSpecificationExecutor :

public interface MyDataRepository extends JpaRepository<MyData, Long>, JpaSpecificationExecutor<MyData> {
}

用Specification编写一个查询方法:

private List<MyData> findBySpec(Long id, String name, List<Long> ids) {
        return myDataRepository.findAll((root, query, builder) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (id != null) {
                predicates.add(builder.equal(root.get("id"), id));
            }
            if (name != null) {
                predicates.add(builder.like(root.get("name"), name));
            }
            if (ids != null) {
                predicates.add(root.get("id").in(ids));
            }
            return builder.and(predicates.toArray(new Predicate[0]));
        }, Sort.by("id").descending());
    }

在查询方法中,我们分别通过 builder.equal、builder.like、 root.get(“id”).in 实现了 = like in 三个sql子句,除此之外,还有 greaterThan lessThan 等其它很多方法可以使用。

最后一个参数还可以指定分页和排序条件。

使用查询方法进行灵活查询:

log.info("find by spec with id: {}", findBySpec(1L, null, null));
log.info("find by spec with name: {}", findBySpec(null, "%wo%", null));
log.info("find by spec with id list: {}", findBySpec(null, null, List.of(1L, 2L)));

执行程序后,我们可以在日志中看到,不同的查询条件返回了不同的查询结果。

完整的实例代码:

@Slf4j
@SpringBootApplication
public class Application implements CommandLineRunner {

    @Autowired
    private MyDataRepository myDataRepository;

    @Override
    public void run(String... args) {
        MyData myData1 = new MyData();
        myData1.setName("one");
        myDataRepository.save(myData1);
        MyData myData2 = new MyData();
        myData2.setName("two");
        myDataRepository.save(myData2);

        log.info("find by id with query: {}", myDataRepository.findByQuery(1L, null));
        log.info("find by id and name with query: {}", myDataRepository.findByQuery(1L, "one"));

        MyData example = new MyData();
        example.setName("two");
        log.info("find by example: {}", myDataRepository.findAll(Example.of(example)));

        log.info("find by spec with id: {}", findBySpec(1L, null, null));
        log.info("find by spec with name: {}", findBySpec(null, "%wo%", null));
        log.info("find by spec with id list: {}", findBySpec(null, null, List.of(1L, 2L)));
    }

    private List<MyData> findBySpec(Long id, String name, List<Long> ids) {
        return myDataRepository.findAll((root, query, builder) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (id != null) {
                predicates.add(builder.equal(root.get("id"), id));
            }
            if (name != null) {
                predicates.add(builder.like(root.get("name"), name));
            }
            if (ids != null) {
                predicates.add(root.get("id").in(ids));
            }
            return builder.and(predicates.toArray(new Predicate[0]));
        }, Sort.by("id").descending());
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
qihaiyan
Written by qihaiyan
业精于勤而荒于嬉,行成于思而毁于随