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查詢就可以解決大多數應用問題了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM