【问题标题】:Pageable Sorting not working in Spring Data JPA, Spring Framework可分页排序在 Spring Data JPA、Spring Framework 中不起作用
【发布时间】:2021-01-08 21:40:58
【问题描述】:

我正在尝试为一个人实现跨多个属性的搜索功能。

这是模型。

@Entity
@NoArgsConstructor
@Getter
@Setter
public class Person {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "USER_ID")
    private long id;
    
    private String firstName;
    
    private String surName;
    
    private int age;
    
    private Date DOB;
    
    private String description;
    
    private String highestEducationQualification;
    
    private String occupation;
    
    private String employer;
    
    private String college;
    
    private String school;
    
    private String eyecolor;
    
    private double weight;
    
    private double height;
    
    private String PPSnumber;
    
    private boolean driversLicence;
    
    private boolean provisionalLicence;
    
    private String bankIBAN;
    
    private long phoneNumber;
    
    private char gender;
    
    private String emailAddress;
    
    private String websiteAddress;
    
    private String homeAddress;
    
}

这是我的存储库。

@Repository
public interface PersonRepo extends JpaRepository<Person, Long>{
    
    List<Person> searchByFirstNameContainingAllIgnoreCase(String firstName, Pageable page);
    List<Person> searchBySurNameContainingAllIgnoreCase(String surName, Pageable page);
    List<Person> searchByAge(int age, Pageable page);
    List<Person> searchByDescriptionContainingAllIgnoreCase(String desc, Pageable page);
    List<Person> searchByHighestEducationQualificationContainingAllIgnoreCase(String edu, Pageable page);
    List<Person> searchByOccupationContainingAllIgnoreCase(String occ, Pageable page);
    List<Person> searchByEmployerContainingAllIgnoreCase(String emp, Pageable page);
    List<Person> searchByCollegeContainingAllIgnoreCase(String emp, Pageable page);
    List<Person> searchBySchoolContainingAllIgnoreCase(String emp, Pageable page);
    List<Person> searchByEyecolorContainingAllIgnoreCase(String eye, Pageable page);
    List<Person> searchByWeight(double weight, Pageable page);
    List<Person> searchByHeight(double height, Pageable page);
    List<Person> searchByPPSnumberIgnoreCase(String emp, Pageable page);
    List<Person> searchByDriversLicence(boolean emp, Pageable page);
    List<Person> searchByProvisionalLicence(boolean emp, Pageable page);
    List<Person> searchByBankIBANAllIgnoreCase(String emp, Pageable page);
    List<Person> searchByPhoneNumber(long phone, Pageable page);
    List<Person> searchByGender(char emp, Pageable page);
    List<Person> searchByEmailAddressIgnoreCase(String emp, Pageable page);
    List<Person> searchByWebsiteAddressContainingAllIgnoreCase(String emp, Pageable page);
    List<Person> searchByHomeAddressContainingAllIgnoreCase(String emp, Pageable page);
}

服务功能。

class PersonService {

    @Autowired
    private PersonRepo personRepo;

    @Override
    public List<Person> searchByAllAttributes(String toSearch, int page, int quan, String sortBy, boolean ascending) {
        int ageToSearch = 0;
        try {
            ageToSearch = Integer.parseInt(toSearch);
        } catch (Exception e) {
            
        }
        double toSearchDouble = 0;
        try {
            toSearchDouble = Double.parseDouble(toSearch);
        } catch (Exception e) {
            
        }
        long phoneToSearch = 0;
        try {
            phoneToSearch = Long.parseLong(toSearch);
        } catch (Exception e) {
            
        }
        
        System.out.println(toSearchDouble);
        
        List<Person> results;
        
        Pageable firstPageWithTwoElements = PageRequest.of(page, quan, Sort.by("firstName").descending());
        
        results = personRepo.searchByFirstNameContainingAllIgnoreCase(toSearch, firstPageWithTwoElements);
        results.addAll(personRepo.searchBySurNameContainingAllIgnoreCase(toSearch,firstPageWithTwoElements));
        results.addAll(personRepo.searchByAge(ageToSearch,firstPageWithTwoElements));
        results.addAll(personRepo.searchByDescriptionContainingAllIgnoreCase(toSearch,firstPageWithTwoElements));
        
        results.addAll(personRepo.searchByCollegeContainingAllIgnoreCase(toSearch, firstPageWithTwoElements));
        results.addAll(personRepo.searchBySchoolContainingAllIgnoreCase(toSearch,firstPageWithTwoElements));
        results.addAll(personRepo.searchByEmployerContainingAllIgnoreCase(toSearch,firstPageWithTwoElements));
        results.addAll(personRepo.searchByOccupationContainingAllIgnoreCase(toSearch,firstPageWithTwoElements));
        results.addAll(personRepo.searchByHighestEducationQualificationContainingAllIgnoreCase(toSearch,firstPageWithTwoElements));
        results.addAll(personRepo.searchByEyecolorContainingAllIgnoreCase(toSearch,firstPageWithTwoElements));
        results.addAll(personRepo.searchByWeight(toSearchDouble,firstPageWithTwoElements));
        results.addAll(personRepo.searchByHeight(toSearchDouble,firstPageWithTwoElements));
        results.addAll(personRepo.searchByPPSnumberIgnoreCase(toSearch,firstPageWithTwoElements));
        //drivers and provisional
        results.addAll(personRepo.searchByBankIBANAllIgnoreCase(toSearch,firstPageWithTwoElements));
        results.addAll(personRepo.searchByPhoneNumber(phoneToSearch,firstPageWithTwoElements));
        //gender
        results.addAll(personRepo.searchByEmailAddressIgnoreCase(toSearch,firstPageWithTwoElements));
        results.addAll(personRepo.searchByWebsiteAddressContainingAllIgnoreCase(toSearch,firstPageWithTwoElements));
        results.addAll(personRepo.searchByHomeAddressContainingAllIgnoreCase(toSearch,firstPageWithTwoElements));
        
        results = removeDuplicatePersons(results);
        
        return results;
    }

    List<Person> removeDuplicatePersons(List<Person> toRemove){
        List<Person> result = toRemove;
        
         List<Person> listWithoutDuplicates = new ArrayList<>(
                  new HashSet<Person>(result));
         
        return listWithoutDuplicates;
    }

}

正如您将看到的,有一个硬编码的 Sort 对象,其中包含 firstName 和降序。每当我调用这个函数时,它都会返回一个随机排序的数据。排序不起作用。我努力对其进行硬编码以消除参数数据损坏的可能性,但即使是硬编码也不起作用。

toSearch 是一个字符串搜索查询。 Pagequan(数量)用于分页。分页有效,但排序无效。感谢您的帮助,如果您需要我更多地解释代码,请添加评论。

正如您可能想象的那样,还有一个控制器类。我也可以添加该代码,但它不会直接影响该代码的逻辑。控制器调用服务函数并将其作为 JSON 返回给 Web 应用程序。通过请求调用服务功能的 REST 控制器功能,我已经在 Postman 中调试了这两个功能。它以 JSON 格式将数据返回给 Postman,我在实现 Web 应用程序中也做了同样的事情,但数据没有排序。

您会注意到 Person 类模型上的 4 个注释。实体是为了持久化。 NoArgsConstructor 和 Getter 和 Setter 是 Lombok 的一部分,Lombok 是一个允许您省略 getter、setter、构造函数的包,它们是在编译时添加的。

【问题讨论】:

    标签: java spring spring-boot hibernate spring-data-jpa


    【解决方案1】:

    问题不在于排序。问题在于您执行搜索的方式。您调用 18 个不同的搜索并将它们合并。每一次搜索都已排序,但不能保证您的 results 列表已排序。

    例子:

    1. 第一次搜索返回:{"Betty", "Adam"}(按名字降序正确排序)
    2. 第二次搜索返回:{"Zack", "Fiona"}(按名字降序正确排序)

    但结果:{"Betty", "Adam", "Zack", "Fiona"} 看起来像一个随机顺序。

    您始终可以在 Java 端进行排序,但由于大型列表的性能问题,不建议这样做。

    要解决此问题,您需要使用一个查询进行搜索。您可以通过规范使用 Spring Boot 查询来实现这一点,更多信息您可以找到here

    【讨论】:

    • 嗨,我确实使用一个函数进行了自己的“搜索所有属性”搜索,但是 repo 函数非常长......除了 repo 中的一个非常长的函数之外,还有其他解决方案吗?
    • 是的,您可以使用query by specificationcriteria query。不幸的是,每一个都需要比你的方法更多的工作。我会去按规范查询
    【解决方案2】:

    您可能正在寻找 JPA Criteria API。 您可以使用 JPA 规范 API 构建查询,它与您正在做的事情相比有两个明显的优势:

    1. 可以在一个查询对多个查询中进行查询(性能更高)。
    2. 构建后更易于更改和调整(如果您设计好类/模式)。

    这里给你一个简单的例子:

    @Repository
    public interface PersonRepo extends JpaRepository<Person,Long>, JpaSpecificationExecutor<Person> {}
    

    这是我写的一个快速组件。 您可以看到它在哪里使用 JPA 规范 API 创建 SQL,然后使用 Repo 运行它。

    @Component
    public class PersonSearcher {
    
        @Autowired
        private PersonRepo personRepo;
    
        /*
            Would be better taking a "Form"/Object with your search criteria.
         */
        public Page<Person> search(String name, Integer ageMin, Integer ageMax, Pageable pageable) {
    
            //Get "all"
            Specification<Person> personSpecification = Specification.not(null);
    
            //Create "Predicates" (like the where clauses).
            if (name != null) {
                personSpecification = personSpecification.and(new MyPersonSpec("firstName", name, "like"));
            }
            if (ageMin != null) {
                personSpecification = personSpecification.and(new MyPersonSpec("age", ageMin, "gt"));
            }
    
            if (ageMax != null) {
                personSpecification = personSpecification.and(new MyPersonSpec("age", ageMax, "lt"));
            }
    
    
            //Run query using Repo. Spring paging still works.
            return personRepo.findAll(personSpecification, pageable);
    
        }
    
        private static class MyPersonSpec implements Specification<Person> {
    
            private final String field;
            private final Object value;
            private final String operation;
    
            private MyPersonSpec(String field, Object value, String operation) {
                this.field = field;
                this.value = value;
                this.operation = operation;
            }
    
            @Override
            public Predicate toPredicate(Root<Person> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
                switch (operation) {
                    case "like":
                        return criteriaBuilder.like(root.get(field), "%" + value.toString().toLowerCase() + "%");
                    case "equal":
                        return criteriaBuilder.equal(root.get(field), value);
                    case "gt":
                        return criteriaBuilder.greaterThan(root.get(field), (int) value);
                    case "lt":
                        return criteriaBuilder.lessThan(root.get(field), (int) value);
    
                    default:
                        throw new RuntimeException("Unexpected `op`.");
                }
            }
        }
    }
    

    这是一个快速测试,以确保它可以编译...您需要做一些正确的断言,也许是 @DataJpa 测试而不是 @SpringBootTest...

    @DataJpaTest
    @ActiveProfiles("tc")
    @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
    @Log4j2
    @Transactional
    @Sql(statements = {
            "INSERT INTO person (USER_ID,first_name,age,drivers_licence,provisional_licence) values(1,'Bob',31,true,false)",
            "INSERT INTO person (USER_ID,first_name,age,drivers_licence,provisional_licence) values(2,'Alice',56,true,false)",
            "INSERT INTO person (USER_ID,first_name,age,drivers_licence,provisional_licence) values(3,'Charlie',18,false,false)",
            "INSERT INTO person (USER_ID,first_name,age,drivers_licence,provisional_licence) values(4,'Dave',72,true,true)",
            "INSERT INTO person (USER_ID,first_name,age,drivers_licence,provisional_licence) values(5,'Emma',21,false,true)"
    })
    @Import(PersonSearcher.class)
    class PersonSearcherTest {
    
        @Autowired
        private PersonSearcher personSearcher;
    
        @Test
        void search() {
    
            /*
             * Test 1 : Finds Bob using name.
             */
    
            //Run the searcher to find "Bob".
            final Page<Person> search = personSearcher.search("bob", null, null,
                    PageRequest.of(0, 5, Sort.by(Sort.Direction.DESC, "emailAddress"))
            );
            log.info(search);
    
            //Assert Returns Bob (ID 1)
            assertEquals(1L, search.getContent().get(0).getId());
    
            /*
             * Test 2: Name and age range with an order by age in Paging.
             */
    
            //Search with any name with an A/a in it between ages 20->99.
            final Page<Person> search2 = personSearcher.search("a", 20, 100,
                    PageRequest.of(0, 5, Sort.Direction.ASC, "age"));
    
            log.info(search2.getContent());
            //Assert fetches only ones with an `a` or `A` and should have age >20 and <100.
            assertTrue(search2
                    .stream()
                    .allMatch(it ->
                            it.getFirstName().toLowerCase().contains("a")
                                    && it.getAge() > 20
                                    && it.getAge() < 100
                    ));
    
            //Assert they are Alice,Dave and Emma (NOT Charlie as <20 age) and in age order from youngest to oldest...
            Assertions.assertEquals(Arrays.asList("Emma", "Alice", "Dave"),
                    search2.get().map(Person::getFirstName).collect(Collectors.toList()));
    
    
            /*
             * Test 3 : With null values gets all back with order by firstName ASC
             */
    
            final Page<Person> allSearch = personSearcher.search(null, null, null,
                    PageRequest.of(0, 10, Sort.Direction.ASC, "firstName"));
    
            //Assert all back in name order.
            assertEquals(Arrays.asList("Alice", "Bob", "Charlie", "Dave", "Emma"),
                    allSearch.get().map(Person::getFirstName).collect(Collectors.toList()));
        }
    }
    

    规范生成并在日志中运行的 SQL:

    08 Jan 2021 23:24:19,840 [DEBUG] --- o.h.SQL                        : 
        select
            person0_.user_id as user_id1_18_,
            person0_.dob as dob2_18_,
            person0_.ppsnumber as ppsnumbe3_18_,
            person0_.age as age4_18_,
            person0_.bankiban as bankiban5_18_,
            person0_.college as college6_18_,
            person0_.description as descript7_18_,
            person0_.drivers_licence as drivers_8_18_,
            person0_.email_address as email_ad9_18_,
            person0_.employer as employe10_18_,
            person0_.eyecolor as eyecolo11_18_,
            person0_.first_name as first_n12_18_,
            person0_.gender as gender13_18_,
            person0_.height as height14_18_,
            person0_.highest_education_qualification as highest15_18_,
            person0_.home_address as home_ad16_18_,
            person0_.occupation as occupat17_18_,
            person0_.phone_number as phone_n18_18_,
            person0_.provisional_licence as provisi19_18_,
            person0_.school as school20_18_,
            person0_.sur_name as sur_nam21_18_,
            person0_.website_address as website22_18_,
            person0_.weight as weight23_18_ 
        from
            person person0_ 
        where
            person0_.age<20 
            and person0_.age>1 
            and (
                person0_.first_name like ?
            ) 
        order by
            person0_.email_address desc limit ?
    

    【讨论】:

    • 感谢您提供有用的内容,jcov。
    • JPA 规范在像这里这样低级别使用它时可能有点令人生畏,因此认为一个简单的示例将有助于您熟悉的类。我今天在工作中这样做是为了我们的搜索服务。值得学习,因为您可以执行 JOINS 和子查询等各种操作。另一方面,编写 SQL 查询可能非常困难,与维护 10/20 个不同的 SQL 查询相比,专门使用它来构建查询要好得多。
    • 太好了,谢谢。我会查看文档并参考您的答案。新年快乐!
    • 我注意到,这与您的原始问题无关,但您似乎有一个非常大的 Person 模型/表。通常我们制作seoerate表并在它们之间建立关系。例如,Person 可能与 PersonBioDetails 有@OneToOne 关系。
    • 是的,我并没有让自己变得容易。我尝试实施您的解决方案,但我无法让它工作!我尝试只使用一个带有 firstName 的 personspecification 并将其设置为 like 和 equal ,但它不起作用。我将搜索查询“名称”传递给它,但什么也没传递。
    猜你喜欢
    • 2018-02-28
    • 2017-04-19
    • 2018-01-02
    • 2013-09-15
    • 2017-09-21
    • 2012-12-16
    • 1970-01-01
    • 2014-12-16
    • 2018-04-03
    相关资源
    最近更新 更多