[英]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.