[英]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
是一個字符串搜索查詢。 Page
和quan
(數量)用於分頁。 分頁有效,但排序無效。 任何幫助表示贊賞,如果您需要我更多地解釋代碼,請添加評論。
正如您可能想象的那樣,還有一個 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
列表已排序。
例子:
{"Betty", "Adam"}
(按名字降序正確排序){"Zack", "Fiona"}
(按名字降序正確排序) 但結果: {"Betty", "Adam", "Zack", "Fiona"}
看起來像一個隨機順序。
您始終可以在 Java 端進行排序,但由於大型列表的性能問題,不建議這樣做。
要解決此問題,您需要使用一個查詢進行搜索。 您可以按規范使用 spring 引導查詢來實現這一點,您可以在此處找到更多信息。
您可能正在尋找 JPA 標准 API。 您可以使用 JPA 規范 API 構建您的查詢,它比您正在做的事情有兩個明顯的優勢:
在這里給你一個簡單的例子:
@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.