繁体   English   中英

可分页排序在 Spring 数据 JPA、Spring 框架中不起作用

[英]Pageable Sorting not working in Spring Data JPA, Spring Framework

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

这是 model。

@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);
}

服务 function。

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;
    }

}

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

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

正如您可能想象的那样,还有一个 controller class 。 我也可以添加该代码,但它不会直接影响该代码的逻辑。 controller 调用服务 function 并将其作为 JSON 返回给 Z2567A5EC3705EB7AC2C1E96 应用程序。 I have debugged both in Postman, by requesting the REST controller function which invokes the service function. 它以 JSON 的形式将数据返回给 Postman,我在实现 web 应用程序时也做了同样的事情,但数据没有排序。

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

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

例子:

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

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

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

要解决此问题,您需要使用一个查询进行搜索。 您可以按规范使用 spring 引导查询来实现这一点,您可以在此处找到更多信息。

您可能正在寻找 JPA 标准 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 ?

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM