From 2db84152b58e42ecffe1367ac8eb755ab82f5abc Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Thu, 22 Jan 2026 23:55:26 +0800 Subject: [PATCH] =?UTF-8?q?feat(jpa):=20=E8=A1=A5=E5=85=85=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E6=9F=A5=E8=AF=A2=E7=9A=84=E6=B5=8B=E8=AF=95=E7=94=A8?= =?UTF-8?q?=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../pom.xml | 4 + .../database/jpa/TestApplication.java | 380 +++++++++++++++++- .../jpa/repository/EmployeeRepository.java | 197 +++++++++ 3 files changed, 579 insertions(+), 2 deletions(-) diff --git a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/pom.xml b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/pom.xml index 839342d..e0d393b 100644 --- a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/pom.xml +++ b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/pom.xml @@ -94,6 +94,10 @@ + + org.springframework.boot + spring-boot-maven-plugin + \ No newline at end of file diff --git a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/TestApplication.java b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/TestApplication.java index 429faf6..6f90c44 100644 --- a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/TestApplication.java +++ b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/TestApplication.java @@ -56,7 +56,7 @@ public class TestApplication extends AbstractTestApplication { testCrud(); testDelete(); testQuery(); - testNative(); + testMethodQuery(); System.exit(0); } @@ -910,7 +910,383 @@ public class TestApplication extends AbstractTestApplication { companyRepository.deleteAllInBatch(); } - private void testNative() { + private void testMethodQuery() { + formatLog("准备 Query Method 测试数据"); + var company1 = companyRepository.save(Company.builder().name("TechCorp").members(100).build()); + var company2 = companyRepository.save(Company.builder().name("DataInc").members(50).build()); + var company3 = companyRepository.save(Company.builder().name("CloudSys").members(150).build()); + var skill1 = Skill.builder().name("Java").description("Java 编程语言").build(); + var skill2 = Skill.builder().name("Python").description("Python 编程语言").build(); + var skill3 = Skill.builder().name("Spring").description("Spring 框架").build(); + var skill4 = Skill.builder().name("MySQL").description("MySQL 数据库").build(); + + var emp1 = employeeRepository.save(Employee.builder() + .name("Alice").age(30).role(Employee.Role.ADMIN).code("E001") + .salary(new BigDecimal("50000.00")).bonus(new BigDecimal("5000.00")) + .active(true).company(company1) + .address(Address.builder().street("Main St").city("Beijing").state("Beijing").zipCode("100000").country("China").build()) + .skills(Set.of(skill1, skill3)) + .hobbies(List.of("Reading", "Swimming")) + .properties(Map.of("department", "Engineering", "level", "Senior")) + .connections(Map.of(Employee.ConnectionType.EMAIL, "alice@example.com")) + .build()); + + var emp2 = employeeRepository.save(Employee.builder() + .name("Bob").age(25).role(Employee.Role.USER).code("E002") + .salary(new BigDecimal("40000.00")).bonus(new BigDecimal("4000.00")) + .active(true).company(company2) + .address(Address.builder().street("Oak Ave").city("Shanghai").state("Shanghai").zipCode("200000").country("China").build()) + .skills(Set.of(skill2)) + .hobbies(List.of("Gaming")) + .properties(Map.of("department", "Marketing", "level", "Junior")) + .connections(Map.of(Employee.ConnectionType.PHONE, "1234567890")) + .build()); + + var emp3 = employeeRepository.save(Employee.builder() + .name("Charlie").age(35).role(Employee.Role.ADMIN).code("E003") + .salary(new BigDecimal("60000.00")).bonus(new BigDecimal("6000.00")) + .active(false).company(company1) + .address(Address.builder().street("Pine Rd").city("Shenzhen").state("Guangdong").zipCode("518000").country("China").build()) + .skills(Set.of(skill1, skill2, skill3)) + .hobbies(List.of("Reading", "Gaming", "Music")) + .properties(Map.of("department", "Engineering", "level", "Lead")) + .connections(Map.of(Employee.ConnectionType.EMAIL, "charlie@example.com", Employee.ConnectionType.PHONE, "0987654321")) + .build()); + + var emp4 = employeeRepository.save(Employee.builder() + .name("David").age(28).role(Employee.Role.USER).code("E004") + .salary(new BigDecimal("45000.00")).bonus(null) + .active(true).company(company3) + .address(Address.builder().street("Elm Ln").city("Guangzhou").state("Guangdong").zipCode("510000").country("China").build()) + .skills(Set.of(skill4)) + .hobbies(List.of()) + .properties(Map.of()) + .connections(Map.of()) + .build()); + + var emp5 = employeeRepository.save(Employee.builder() + .name("Alice Smith").age(32).role(Employee.Role.USER).code("E005") + .salary(new BigDecimal("55000.00")).bonus(new BigDecimal("5500.00")) + .active(true).company(company2) + .address(Address.builder().street("Maple Dr").city("Hangzhou").state("Zhejiang").zipCode("310000").country("China").build()) + .skills(Set.of(skill1, skill4)) + .hobbies(List.of("Swimming", "Music")) + .properties(Map.of("department", "Sales", "level", "Middle")) + .connections(Map.of(Employee.ConnectionType.EMAIL, "alicesmith@example.com")) + .build()); + + // ==================== 1. 基本字段查询测试 ==================== + + formatLog("1.1 单字段精确匹配查询"); + var r1_1 = employeeRepository.findByName("Bob"); + Assert.isTrue(!r1_1.isEmpty(), "findByName failed"); + Assert.isTrue(r1_1.get(0).getName().equals("Bob"), "findByName 结果不正确"); + + var r1_2 = employeeRepository.findByCode("E003"); + Assert.isTrue(r1_2.isPresent(), "findByCode failed"); + Assert.isTrue(r1_2.orElseThrow().getCode().equals("E003"), "findByCode 结果不正确"); + + var r1_3 = employeeRepository.findByRole(Employee.Role.ADMIN); + Assert.isTrue(r1_3.size() == 2, "findByRole 结果数量应为2,实际:" + r1_3.size()); + + formatLog("1.2 布尔值查询"); + var r1_4 = employeeRepository.findByActiveTrue(); + Assert.isTrue(r1_4.size() == 4, "findByActiveTrue 结果数量应为4,实际:" + r1_4.size()); + + var r1_5 = employeeRepository.findByActiveFalse(); + Assert.isTrue(r1_5.size() == 1, "findByActiveFalse 结果数量应为1,实际:" + r1_5.size()); + + var r1_6 = employeeRepository.countByActiveTrue(); + Assert.isTrue(r1_6 == 4, "countByActiveTrue 结果应为4,实际:" + r1_6); + + // ==================== 2. 比较运算符查询测试 ==================== + + formatLog("2.1 大于/小于/等于/不等于查询"); + var r2_1 = employeeRepository.findByAgeGreaterThan(30); + Assert.isTrue(r2_1.size() == 2, "findByAgeGreaterThan(30) 结果数量应为2,实际:" + r2_1.size()); + + var r2_2 = employeeRepository.findByAgeLessThan(30); + Assert.isTrue(r2_2.size() == 2, "findByAgeLessThan(30) 结果数量应为2,实际:" + r2_2.size()); + + var r2_3 = employeeRepository.findByAgeGreaterThanEqual(30); + Assert.isTrue(r2_3.size() == 3, "findByAgeGreaterThanEqual(30) 结果数量应为3,实际:" + r2_3.size()); + + var r2_4 = employeeRepository.findByAgeLessThanEqual(30); + Assert.isTrue(r2_4.size() == 3, "findByAgeLessThanEqual(30) 结果数量应为3,实际:" + r2_4.size()); + + var r2_5 = employeeRepository.findByAgeBetween(25, 35); + Assert.isTrue(r2_5.size() == 4, "findByAgeBetween(25,35) 结果数量应为4,实际:" + r2_5.size()); + + formatLog("2.2 薪资范围查询"); + var r2_6 = employeeRepository.findBySalaryGreaterThan(new BigDecimal("50000.00")); + Assert.isTrue(r2_6.size() == 3, "findBySalaryGreaterThan 结果数量应为3,实际:" + r2_6.size()); + + var r2_7 = employeeRepository.findBySalaryLessThan(new BigDecimal("50000.00")); + Assert.isTrue(r2_7.size() == 2, "findBySalaryLessThan 结果数量应为2,实际:" + r2_7.size()); + + var r2_8 = employeeRepository.findBySalaryBetween(new BigDecimal("40000.00"), new BigDecimal("55000.00")); + Assert.isTrue(r2_8.size() == 4, "findBySalaryBetween 结果数量应为4,实际:" + r2_8.size()); + + // ==================== 3. 字符串匹配查询测试 ==================== + + formatLog("3.1 字符串包含/开头/结尾查询"); + var r3_1 = employeeRepository.findByNameContaining("Alice"); + Assert.isTrue(r3_1.size() == 2, "findByNameContaining('Alice') 结果数量应为2,实际:" + r3_1.size()); + + var r3_2 = employeeRepository.findByNameStartingWith("A"); + Assert.isTrue(r3_2.size() == 2, "findByNameStartingWith('A') 结果数量应为2,实际:" + r3_2.size()); + + var r3_3 = employeeRepository.findByNameEndingWith("e"); + Assert.isTrue(r3_3.size() == 3, "findByNameEndingWith('e') 结果数量应为3,实际:" + r3_3.size()); + + var r3_4 = employeeRepository.findByNameLike("%ar%"); + Assert.isTrue(r3_4.size() == 1, "findByNameLike('%ar%') 结果数量应为1,实际:" + r3_4.size()); + + formatLog("3.2 忽略大小写查询"); + var r3_5 = employeeRepository.findByNameContainingIgnoreCase("ALICE"); + Assert.isTrue(r3_5.size() == 2, "findByNameContainingIgnoreCase('ALICE') 结果数量应为2,实际:" + r3_5.size()); + + var r3_6 = employeeRepository.findByNameStartingWithIgnoreCase("B"); + Assert.isTrue(r3_6.size() == 1, "findByNameStartingWithIgnoreCase('B') 结果数量应为1,实际:" + r3_6.size()); + + var r3_7 = employeeRepository.findByNameIgnoreCase("BOB"); + Assert.notNull(r3_7, "findByNameIgnoreCase('BOB') 返回null"); + + // ==================== 4. NULL值和空值查询测试 ==================== + + formatLog("4.1 NULL值查询"); + var r4_1 = employeeRepository.findByBonusIsNull(); + Assert.isTrue(r4_1.size() == 1, "findByBonusIsNull 结果数量应为1,实际:" + r4_1.size()); + Assert.isTrue(r4_1.get(0).getName().equals("David"), "findByBonusIsNull 结果应为David"); + + var r4_2 = employeeRepository.findByBonusIsNotNull(); + Assert.isTrue(r4_2.size() == 4, "findByBonusIsNotNull 结果数量应为4,实际:" + r4_2.size()); + + var r4_3 = employeeRepository.findByResumeIsNull(); + Assert.isTrue(r4_3.size() == 5, "findByResumeIsNull 结果数量应为5,实际:" + r4_3.size()); + + formatLog("4.2 空集合查询"); + var r4_4 = employeeRepository.findByHobbiesEmpty(); + Assert.isTrue(r4_4.size() == 1, "findByHobbiesEmpty 结果数量应为1,实际:" + r4_4.size()); + + var r4_5 = employeeRepository.findByHobbiesIsNotEmpty(); + Assert.isTrue(r4_5.size() == 4, "findByHobbiesIsNotEmpty 结果数量应为4,实际:" + r4_5.size()); + + // ==================== 5. 集合成员查询测试 ==================== + + formatLog("5.1 IN/NOT IN 查询"); + var r5_1 = employeeRepository.findByRoleIn(Set.of(Employee.Role.ADMIN)); + Assert.isTrue(r5_1.size() == 2, "findByRoleIn 结果数量应为2,实际:" + r5_1.size()); + + var r5_2 = employeeRepository.findByRoleNotIn(Set.of(Employee.Role.ADMIN)); + Assert.isTrue(r5_2.size() == 3, "findByRoleNotIn 结果数量应为3,实际:" + r5_2.size()); + + var r5_3 = employeeRepository.findByNameIn(List.of("Alice", "Bob")); + Assert.isTrue(r5_3.size() == 2, "findByNameIn 结果数量应为2,实际:" + r5_3.size()); + + var r5_4 = employeeRepository.findByNameNotIn(List.of("Charlie", "David")); + Assert.isTrue(r5_4.size() == 3, "findByNameNotIn 结果数量应为3,实际:" + r5_4.size()); + + var r5_5 = employeeRepository.findByAgeIn(List.of(25, 30, 35)); + Assert.isTrue(r5_5.size() == 3, "findByAgeIn 结果数量应为3,实际:" + r5_5.size()); + + // ==================== 6. 逻辑运算查询测试 ==================== + + formatLog("6.1 AND 组合查询"); + var r6_1 = employeeRepository.findByNameAndRole("Alice", Employee.Role.ADMIN); + Assert.isTrue(r6_1.size() == 1, "findByNameAndRole 结果数量应为1,实际:" + r6_1.size()); + + var r6_2 = employeeRepository.findByAgeAndActive(30, true); + Assert.isTrue(r6_2.size() == 1, "findByAgeAndActive 结果数量应为1,实际:" + r6_2.size()); + + var r6_3 = employeeRepository.findByNameAndSalaryGreaterThan("Alice", new BigDecimal("40000.00")); + Assert.isTrue(r6_3.size() == 1, "findByNameAndSalaryGreaterThan 结果数量应为1,实际:" + r6_3.size()); + + formatLog("6.2 OR 组合查询"); + var r6_4 = employeeRepository.findByNameOrRole("Alice", Employee.Role.USER); + Assert.isTrue(r6_4.size() == 4, "findByNameOrRole 结果数量应为4,实际:" + r6_4.size()); + + var r6_5 = employeeRepository.findByAgeLessThanOrSalaryGreaterThan(30, new BigDecimal("55000.00")); + Assert.isTrue(r6_5.size() == 3, "findByAgeLessThanOrSalaryGreaterThan 结果数量应为3,实际:" + r6_5.size()); + + var r6_6 = employeeRepository.findByNameOrCode("Alice", "E003"); + Assert.isTrue(r6_6.size() == 2, "findByNameOrCode 结果数量应为2,实际:" + r6_6.size()); + + formatLog("6.3 NOT 查询"); + var r6_7 = employeeRepository.findByRoleNot(Employee.Role.ADMIN); + Assert.isTrue(r6_7.size() == 3, "findByRoleNot 结果数量应为3,实际:" + r6_7.size()); + + var r6_8 = employeeRepository.findByNameNot("Alice"); + Assert.isTrue(r6_8.size() == 4, "findByNameNot 结果数量应为4,实际:" + r6_8.size()); + + // ==================== 7. 排序查询测试 ==================== + + formatLog("7.1 单字段排序查询"); + var r7_1 = employeeRepository.findByActiveTrueOrderByAgeAsc(); + Assert.isTrue(r7_1.size() == 4, "findByActiveTrueOrderByAgeAsc 结果数量应为4"); + Assert.isTrue(r7_1.get(0).getAge() == 25, "findByActiveTrueOrderByAgeAsc 第一条年龄应为25"); + + var r7_2 = employeeRepository.findByActiveTrueOrderByAgeDesc(); + Assert.isTrue(r7_2.size() == 4, "findByActiveTrueOrderByAgeDesc 结果数量应为4"); + Assert.isTrue(r7_2.get(0).getAge() == 32, "findByActiveTrueOrderByAgeDesc 第一条年龄应为32"); + + var r7_3 = employeeRepository.findByActiveTrueOrderBySalaryDesc(); + Assert.isTrue(r7_3.size() == 4, "findByActiveTrueOrderBySalaryDesc 结果数量应为4"); + Assert.isTrue(r7_3.get(0).getSalary().compareTo(new BigDecimal("55000.00")) == 0, "findByActiveTrueOrderBySalaryDesc 第一条薪资应为55000"); + + formatLog("7.2 多字段排序查询"); + var r7_4 = employeeRepository.findByRoleOrderByAgeDescNameAsc(Employee.Role.ADMIN); + Assert.isTrue(r7_4.size() == 2, "findByRoleOrderByAgeDescNameAsc 结果数量应为2"); + + // ==================== 8. 分页查询测试 ==================== + + formatLog("8.1 分页查询"); + var r8_1 = employeeRepository.findByActiveTrue(PageRequest.of(0, 2)); + Assert.isTrue(r8_1.getContent().size() == 2, "分页结果数量应为2,实际:" + r8_1.getContent().size()); + Assert.isTrue(r8_1.getTotalElements() == 4, "总元素数应为4,实际:" + r8_1.getTotalElements()); + + var r8_2 = employeeRepository.findByRole(Employee.Role.USER, PageRequest.of(0, 10)); + Assert.isTrue(r8_2.getContent().size() == 3, "findByRole 分页结果数量应为3,实际:" + r8_2.getContent().size()); + + var r8_3 = employeeRepository.findBySalaryGreaterThan(new BigDecimal("45000.00"), PageRequest.of(0, 2)); + Assert.isTrue(r8_3.getContent().size() == 2, "findBySalaryGreaterThan 分页结果数量应为2"); + + // ==================== 9. 关联查询测试 ==================== + + formatLog("9.1 公司关联查询"); + var r9_1 = employeeRepository.findByCompany(company1); + Assert.isTrue(r9_1.size() == 2, "findByCompany 结果数量应为2,实际:" + r9_1.size()); + + var r9_2 = employeeRepository.findByCompanyName("TechCorp"); + Assert.isTrue(r9_2.size() == 2, "findByCompanyName 结果数量应为2,实际:" + r9_2.size()); + + var r9_3 = employeeRepository.findByCompanyNameContaining("Corp"); + Assert.isTrue(r9_3.size() == 2, "findByCompanyNameContaining 结果数量应为2,实际:" + r9_3.size()); + + var r9_4 = employeeRepository.findByCompanyMembersGreaterThan(75); + Assert.isTrue(r9_4.size() == 2, "findByCompanyMembersGreaterThan 结果数量应为2,实际:" + r9_4.size()); + + formatLog("9.2 技能关联查询"); + var r9_5 = employeeRepository.findBySkillsContaining(skill1); + Assert.isTrue(r9_5.size() == 3, "findBySkillsContaining 结果数量应为3,实际:" + r9_5.size()); + + var r9_6 = employeeRepository.findBySkillsName("Java"); + Assert.isTrue(r9_6.size() == 3, "findBySkillsName 结果数量应为3,实际:" + r9_6.size()); + + var r9_7 = employeeRepository.findBySkillsNameContaining("Pyt"); + Assert.isTrue(r9_7.size() == 2, "findBySkillsNameContaining 结果数量应为2,实际:" + r9_7.size()); + + var r9_8 = employeeRepository.findBySkillsNameIn(List.of("Java", "Python")); + Assert.isTrue(r9_8.size() == 3, "findBySkillsNameIn 结果数量应为3,实际:" + r9_8.size()); + + // ==================== 10. 嵌入式对象查询测试 ==================== + + formatLog("10.1 嵌入式对象查询"); + var r10_1 = employeeRepository.findByAddressCity("Beijing"); + Assert.isTrue(r10_1.size() == 1, "findByAddressCity 结果数量应为1,实际:" + r10_1.size()); + Assert.isTrue(r10_1.get(0).getName().equals("Alice"), "findByAddressCity 结果应为Alice"); + + var r10_2 = employeeRepository.findByAddressCityContaining("ang"); + Assert.isTrue(r10_2.size() == 2, "findByAddressCityContaining 结果数量应为2,实际:" + r10_2.size()); + + var r10_3 = employeeRepository.findByAddressState("Guangdong"); + Assert.isTrue(r10_3.size() == 2, "findByAddressState 结果数量应为2,实际:" + r10_3.size()); + + var r10_4 = employeeRepository.findByAddressCountry("China"); + Assert.isTrue(r10_4.size() == 5, "findByAddressCountry 结果数量应为5,实际:" + r10_4.size()); + + var r10_5 = employeeRepository.findByAddressCityAndAddressState("Shenzhen", "Guangdong"); + Assert.isTrue(r10_5.size() == 1, "findByAddressCityAndAddressState 结果数量应为1"); + + // ==================== 11. 复合复杂查询测试 ==================== + + formatLog("11.1 多条件组合查询"); + var r11_1 = employeeRepository.findByNameAndRoleAndAgeGreaterThan("Alice", Employee.Role.ADMIN, 25); + Assert.isTrue(r11_1.size() == 1, "findByNameAndRoleAndAgeGreaterThan 结果数量应为1"); + + var r11_2 = employeeRepository.findByRoleAndActiveTrueAndSalaryGreaterThan(Employee.Role.USER, new BigDecimal("40000.00")); + Assert.isTrue(r11_2.size() == 2, "findByRoleAndActiveTrueAndSalaryGreaterThan 结果数量应为2"); + + var r11_3 = employeeRepository.findByNameContainingIgnoreCaseAndActiveTrueAndAgeBetween("a", 25, 35); + Assert.isTrue(r11_3.size() == 3, "findByNameContainingIgnoreCaseAndActiveTrueAndAgeBetween 结果数量应为3"); + + formatLog("11.2 EXISTS语义查询"); + var r11_4 = employeeRepository.findByCompanyNameContainingAndActiveTrue("Corp"); + Assert.isTrue(r11_4.size() == 2, "findByCompanyNameContainingAndActiveTrue 结果数量应为2"); + + var r11_5 = employeeRepository.findBySkillsNameContainingAndAgeGreaterThan("Java", 25); + Assert.isTrue(r11_5.size() == 2, "findBySkillsNameContainingAndAgeGreaterThan 结果数量应为2"); + + // ==================== 12. DISTINCT 查询测试 ==================== + + formatLog("12.1 DISTINCT 查询"); + var r12_1 = employeeRepository.findDistinctByRole(Employee.Role.ADMIN); + Assert.isTrue(r12_1.size() == 2, "findDistinctByRole 结果数量应为2"); + + // ==================== 13. TOP/LIMIT 查询测试 ==================== + + formatLog("13.1 TOP/LIMIT 查询"); + var r13_1 = employeeRepository.findTop5BySalaryGreaterThan(new BigDecimal("40000.00")); + Assert.isTrue(r13_1.size() == 4, "findTop5BySalaryGreaterThan 结果数量应为4"); + + var r13_3 = employeeRepository.findFirstByRoleOrderBySalaryDesc(Employee.Role.ADMIN); + Assert.notNull(r13_3, "findFirstByRoleOrderBySalaryDesc 返回null"); + Assert.isTrue(r13_3.orElseThrow().getSalary().compareTo(new BigDecimal("60000.00")) == 0, "findFirstByRoleOrderBySalaryDesc 最高薪资应为60000"); + + // ==================== 14. LIKE 模式查询测试 ==================== + + formatLog("14.1 LIKE 模式查询"); + var r14_1 = employeeRepository.findByNameLikeIgnoreCase("%ALICE%"); + Assert.isTrue(r14_1.size() == 2, "findByNameLikeIgnoreCase 结果数量应为2"); + + var r14_2 = employeeRepository.findByNameStartingWithAndRole("A", Employee.Role.ADMIN); + Assert.isTrue(r14_2.size() == 1, "findByNameStartingWithAndRole 结果数量应为1"); + + var r14_3 = employeeRepository.findByNameEndingWithAndAgeLessThan("e", 30); + Assert.isTrue(r14_3.size() == 1, "findByNameEndingWithAndAgeLessThan 结果数量应为1"); + + // ==================== 15. 其他未覆盖的有用方法测试 ==================== + + formatLog("15.1 聚合计数查询"); + var r15_1 = employeeRepository.countByActiveTrue(); + Assert.isTrue(r15_1 == 4, "countByActiveTrue 结果应为4,实际:" + r15_1); + + var r15_2 = employeeRepository.countByActiveFalse(); + Assert.isTrue(r15_2 == 1, "countByActiveFalse 结果应为1,实际:" + r15_2); + + formatLog("15.2 技能和简历查询"); + var r15_3 = employeeRepository.findByResumeIsNotNull(); + Assert.isTrue(r15_3.size() == 5, "findByResumeIsNotNull 结果数量应为5,实际:" + r15_3.size()); + + var r15_4 = employeeRepository.findBySkillsEmpty(); + Assert.isTrue(r15_4.isEmpty(), "findBySkillsEmpty 结果应该为空,实际:" + r15_4.size()); + + formatLog("15.3 公司成员数查询"); + var r15_5 = employeeRepository.findByCompanyMembersGreaterThan(100); + Assert.isTrue(r15_5.size() == 1, "findByCompanyMembersGreaterThan(100) 结果数量应为1,实际:" + r15_5.size()); + + var r15_6 = employeeRepository.findByCompanyMembersGreaterThan(50); + Assert.isTrue(r15_6.size() == 3, "findByCompanyMembersGreaterThan(50) 结果数量应为3,实际:" + r15_6.size()); + + formatLog("15.4 城市包含查询"); + var r15_7 = employeeRepository.findByAddressCityContaining("ang"); + Assert.isTrue(r15_7.size() == 2, "findByAddressCityContaining('ang') 结果数量应为2,实际:" + r15_7.size()); + + formatLog("15.5 DISTINCT 查询"); + var r15_8 = employeeRepository.findDistinctByRole(Employee.Role.USER); + Assert.isTrue(r15_8.size() == 3, "findDistinctByRole 结果数量应为3,实际:" + r15_8.size()); + + formatLog("15.6 TOP/LIMIT 查询"); + var r15_9 = employeeRepository.findTop5BySalaryGreaterThan(new BigDecimal("40000.00")); + Assert.isTrue(r15_9.size() == 4, "findTop5BySalaryGreaterThan 结果数量应为4,实际:" + r15_9.size()); + + var r15_10 = employeeRepository.findFirstByRoleOrderBySalaryDesc(Employee.Role.ADMIN); + Assert.isTrue(r15_10.isPresent(), "findFirstByRoleOrderBySalaryDesc 返回null"); + Assert.isTrue(r15_10.orElseThrow().getSalary().compareTo(new BigDecimal("60000.00")) == 0, "findFirstByRoleOrderBySalaryDesc 最高薪资应为60000"); + + formatLog("清理测试数据"); + employeeRepository.deleteAllInBatch(); + companyRepository.deleteAllInBatch(); } } diff --git a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/repository/EmployeeRepository.java b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/repository/EmployeeRepository.java index 826258c..a522168 100644 --- a/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/repository/EmployeeRepository.java +++ b/spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/test/java/com/lanyuanxiaoyao/service/template/database/jpa/repository/EmployeeRepository.java @@ -1,7 +1,14 @@ package com.lanyuanxiaoyao.service.template.database.jpa.repository; +import com.lanyuanxiaoyao.service.template.database.jpa.entity.Company; import com.lanyuanxiaoyao.service.template.database.jpa.entity.Employee; +import com.lanyuanxiaoyao.service.template.database.jpa.entity.Skill; +import java.math.BigDecimal; +import java.util.List; import java.util.Optional; +import java.util.Set; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.stereotype.Repository; @@ -12,4 +19,194 @@ public interface EmployeeRepository extends SimpleRepository { @EntityGraph(attributePaths = {"company"}) @Override Optional findOne(Specification specification); + + // ==================== 1. 基本字段查询 ==================== + + // 单字段精确匹配 + List findByName(String name); + + Optional findByCode(String code); + + List findByRole(Employee.Role role); + + // 布尔值查询 + List findByActiveTrue(); + + List findByActiveFalse(); + + long countByActiveTrue(); + + long countByActiveFalse(); + + // ==================== 2. 比较运算符查询 ==================== + + List findByAgeGreaterThan(Integer age); + + List findByAgeLessThan(Integer age); + + List findByAgeGreaterThanEqual(Integer age); + + List findByAgeLessThanEqual(Integer age); + + List findByAgeBetween(Integer startAge, Integer endAge); + + List findBySalaryGreaterThan(BigDecimal salary); + + List findBySalaryLessThan(BigDecimal salary); + + List findBySalaryGreaterThanEqual(BigDecimal salary); + + List findBySalaryLessThanEqual(BigDecimal salary); + + List findBySalaryBetween(BigDecimal minSalary, BigDecimal maxSalary); + + // ==================== 3. 字符串匹配查询 ==================== + + // 精确匹配 + List findByNameContaining(String name); + + List findByNameStartingWith(String prefix); + + List findByNameEndingWith(String suffix); + + List findByNameLike(String pattern); + + // 忽略大小写 + List findByNameContainingIgnoreCase(String name); + + List findByNameStartingWithIgnoreCase(String prefix); + + List findByNameEndingWithIgnoreCase(String suffix); + + List findByNameIgnoreCase(String name); + + // ==================== 4. NULL值和空值查询 ==================== + + List findByBonusIsNull(); + + List findByBonusIsNotNull(); + + List findByResumeIsNull(); + + List findByResumeIsNotNull(); + + List findByHobbiesEmpty(); + + List findByHobbiesIsNotEmpty(); + + List findBySkillsEmpty(); + + List findBySkillsIsNotEmpty(); + + // ==================== 5. 集合成员查询 (IN/NOT IN) ==================== + + List findByRoleIn(Set roles); + + List findByRoleNotIn(Set roles); + + List findByNameIn(List names); + + List findByNameNotIn(List names); + + List findByAgeIn(List ages); + + List findByAgeNotIn(List ages); + + // ==================== 6. 逻辑运算查询 (AND/OR/NOT) ==================== + + List findByNameAndRole(String name, Employee.Role role); + + List findByAgeAndActive(Integer age, Boolean active); + + List findByNameAndSalaryGreaterThan(String name, BigDecimal salary); + + List findByRoleAndActiveAndAgeGreaterThan(Employee.Role role, Boolean active, Integer age); + + List findByNameOrRole(String name, Employee.Role role); + + List findByAgeLessThanOrSalaryGreaterThan(Integer age, BigDecimal salary); + + List findByNameOrCode(String name, String code); + + List findByRoleNot(Employee.Role role); + + List findByNameNot(String name); + + // ==================== 7. 排序查询 ==================== + + List findByActiveTrueOrderByAgeAsc(); + + List findByActiveTrueOrderByAgeDesc(); + + List findByActiveTrueOrderBySalaryDesc(); + + List findByRoleOrderByAgeDescNameAsc(Employee.Role role); + + // ==================== 8. 分页查询 ==================== + + Page findByActiveTrue(Pageable pageable); + + Page findByRole(Employee.Role role, Pageable pageable); + + Page findBySalaryGreaterThan(BigDecimal salary, Pageable pageable); + + // ==================== 9. 关联查询 (JOIN) ==================== + + List findByCompany(Company company); + + List findByCompanyName(String companyName); + + List findByCompanyNameContaining(String companyName); + + List findByCompanyMembersGreaterThan(Integer members); + + List findBySkillsContaining(Skill skill); + + List findBySkillsName(String skillName); + + List findBySkillsNameContaining(String skillName); + + List findBySkillsNameIn(List skillNames); + + // ==================== 10. 嵌入式对象查询 ==================== + + List findByAddressCity(String city); + + List findByAddressCityContaining(String city); + + List findByAddressState(String state); + + List findByAddressCountry(String country); + + List findByAddressCityAndAddressState(String city, String state); + + // ==================== 11. 复合复杂查询 ==================== + + List findByNameAndRoleAndAgeGreaterThan(String name, Employee.Role role, Integer age); + + List findByRoleAndActiveTrueAndSalaryGreaterThan(Employee.Role role, BigDecimal salary); + + List findByNameContainingIgnoreCaseAndActiveTrueAndAgeBetween(String name, Integer minAge, Integer maxAge); + + List findByCompanyNameContainingAndActiveTrue(String companyName); + + List findBySkillsNameContainingAndAgeGreaterThan(String skillName, Integer age); + + // ==================== 12. DISTINCT 查询 ==================== + + List findDistinctByRole(Employee.Role role); + + // ==================== 13. TOP/LIMIT 查询 ==================== + + List findTop5BySalaryGreaterThan(BigDecimal salary); + + Optional findFirstByRoleOrderBySalaryDesc(Employee.Role role); + + // ==================== 14. LIKE 模式查询 ==================== + + List findByNameLikeIgnoreCase(String pattern); + + List findByNameStartingWithAndRole(String prefix, Employee.Role role); + + List findByNameEndingWithAndAgeLessThan(String suffix, Integer age); }