spring data jpa Specification 复杂查询+分页查询

当Repository接口继承了JpaSpecificationExecutor后,我们就可以使用如下接口进行分页查询:

    /**
     * Returns a {@link Page} of entities matching the given {@link Specification}.
     *
     * @param spec can be {@literal null}.
     * @param pageable must not be {@literal null}.
     * @return never {@literal null}.
     */
    Page<T> findAll(@Nullable Specification<T> spec, Pageable pageable);

结合jpa-spec可以很容易构造出Specification:

jpa-spec github地址:https://github.com/wenhao/jpa-spec

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 Date(), new Date())
            .like("nickName", "%og%", "%me")
            .build();

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

单表查询确实很简单,但对复杂查询,就复杂上些了:

public List<Phone> findAll(SearchRequest request) {
    Specification<Phone> specification = Specifications.<Phone>and()
        .eq(StringUtils.isNotBlank(request.getBrand()), "brand", "HuaWei")
        .eq(StringUtils.isNotBlank(request.getPersonName()), "person.name", "Jack")
        .build();

    return phoneRepository.findAll(specification);
}

这里主表是phone,使用了person的name做条件,使用方法是person.name。

jpa-spec内部会分析person.name,如下代码:

public From getRoot(String property, Root<T> root) {
        if (property.contains(".")) {
            String joinProperty = StringUtils.split(property, ".")[0];
            return root.join(joinProperty, JoinType.LEFT);
        } else {
            return root;
        }
    }

就可看到它用了root.join,那就有一个问题,如果有两个person字段的条件,那就要再join一次,就会生成这样的sql:

select * from phone left outer join person on XX=XX left outer join person XX=XX.

这样肯定不满足需求。这应该也是jpa-spec的一个bug吧

为了解决这个问题,可以使用它提供的另一种方式查询:

public List<Phone> findAll(SearchRequest request) {
    Specification<Person> specification = Specifications.<Person>and()
        .between("age", 10, 35)
        .predicate(StringUtils.isNotBlank(jack.getName()), ((root, query, cb) -> {
            Join address = root.join("addresses", JoinType.LEFT);
            return cb.equal(address.get("street"), "Chengdu");
        }))
        .build();

    return phoneRepository.findAll(specification);
}

这要就可以解决大多数情况了,除了分页

看下正常的单表分页+排序查询:

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

    Sort sort = Sorts.builder()
        .desc(StringUtils.isNotBlank(request.getName()), "name")
        .asc("birthday")
        .build();

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

如果在此基础上增加关联,如下代码:

public Page<Person> findAll(SearchRequest request) {
        Specification<Person> specification = Specifications.<Person>and()
                .predicate(StringUtils.isNotBlank(jack.getName()), ((root, query, cb) -> {
                    Join address = root.join("addresses", JoinType.LEFT);
                    return cb.equal(address.get("street"), "Chengdu");
                }))
                .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName())
                .gt("age", 18)
                .between("birthday", new Date(), new Date())
                .like("nickName", "%og%")
                .build();

        Sort sort = Sorts.builder()
                .desc(StringUtils.isNotBlank(request.getName()), "name")
                .asc("birthday")
                .build();

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

就会发现addresses的延迟加载失效,生成很多查询addresses的语句,解决方案如下:

public Page<Person> findAll(SearchRequest request) {
        Specification<Person> specification = Specifications.<Person>and()
                .predicate(StringUtils.isNotBlank(jack.getName()), ((root, query, cb) -> {
                    Join address;
                    if (Long.class != query.getResultType()) {
                        address = (Join) root.fetch("addresses", JoinType.LEFT);
                    } else {
                        address = root.join("addresses", JoinType.LEFT);
                    }
                    return cb.equal(address.get("street"), "Chengdu");
                }))
                .eq(StringUtils.isNotBlank(request.getName()), "name", request.getName())
                .gt("age", 18)
                .between("birthday", new Date(), new Date())
                .like("nickName", "%og%")
                .build();

        Sort sort = Sorts.builder()
                .desc(StringUtils.isNotBlank(request.getName()), "name")
                .asc("birthday")
                .build();

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

至此,用Specification查询就应该够用了,再配合JpaRepository (SimpleJpaRepository)提供的方法 和@Query注解方法,和criteria api查询,这四种JPA查询就可以解决大多数应用问题了。

原文地址:https://www.cnblogs.com/hankuikui/p/11414316.html

时间: 2024-08-13 22:11:50

spring data jpa Specification 复杂查询+分页查询的相关文章

SpringBoot中使用Spring Data Jpa 实现简单的动态查询的两种方法

首先谢谢大佬的简书文章:http://www.jianshu.com/p/45ad65690e33# 这篇文章中讲的是spring中使用spring data jpa,使用了xml配置文件.我现在使用的是spring boot ,没有了xml文件配置就方便多了.我同样尝试了两种方式,也都是简单的查询,需要更复杂的查询,还需要我研究研究.往下看,需要先配置springboot的开发环境,需要大致了解springboot,这里可以看下面两篇文章: springboot 项目新建 springboot

Spring Data JPA Specification Query

private Specification<VO> createSpecification(final Map<String, String> filterData) throws Exception { final Map<String, String> filterOperators = new HashMap<>(); final Map<String, Object> filterParams = new HashMap(); extra

Spring data jpa Specification查询关于日期的范围搜索

代码: 时间格式化类型: SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); SimpleDateFormat sdfmat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); try { if (StringUtils.isNotEmpty(startTime) && StringUtils.isNotEmpty(endTime)) { pr

【spring data jpa】使用repository进行查询,使用userRepository.getOne(id)和userRepository.findById(id)无法从数据库查询到数据

如题: 使用repository进行查询,使用CrudRepository自带的getOne()方法和findById()方法查询,数据库中有这条数据,但是并不能查到. userRepository.getOne(id)和userRepository.findById(id)无法从数据库查询到数据. 而在userRepository中,写一个findUserById()方法,即可以查询到数据 具体原因有谁知道,可否告知? 原文地址:https://www.cnblogs.com/sxdcgaq8

spring data jpa 自定义sql 左链接查询

@Query(value = "select u.* from appuser_te u LEFT JOIN app_user_history his on his.user_id=u.id where his.apple_app_id=:appleAppId limit 1,3",nativeQuery = true) List<AppUser> findByFlushId(@Param(value="appleAppId") Integer appl

Spring Data JPA的Respository接口中查询方法

Spring data jpa 实现简单动态查询的通用Specification方法

本篇前提: SpringBoot中使用Spring Data Jpa 实现简单的动态查询的两种方法 这篇文章中的第二种方法 实现Specification 这块的方法 只适用于一个对象针对某一个固定字段查询,下面通过泛型改写了这个方法: import java.util.List; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import j

Spring Data JPA 查询结果返回至自定义实体

本人在实际工作中使用Spring Data Jpa框架时,一般查询结果只返回对应的Entity实体.但有时根据实际业务,需要进行一些较复杂的查询,比较棘手.虽然在框架上我们可以使用@Query注解执行我们自定义的sql语句,但是其返回值为List<Object[]> 类型,即多个Object数组的List集合,然后通过解析获取需要的数据,比较麻烦. 于是,开始考虑能否将查询结果返回至自定义的实体类,网上寻找解决方案并自己不断实践,遇到一些问题,跟大家分享下. 首先,介绍一种可行的方案: 1.自

Spring Data JPA实战视频教程

视频大纲 JPA入门 Spring Data JPA入门 Repository的定义 查询方法的命名策略 JPQL查询,结果映射 Named Query,Named Native Query 排序,分页 JPA Criteria查询 Querydsl查询 Query by Example 一对一,一对多,多对一,多对多 @EnableJpaRepositories 注解 自定义.扩展Repository 实体的生命周期 审计 乐观锁,悲观锁 集成 OpenJPA 查询批注 缓存 事务 Sprin