From e9b0e79d48ff4350f39790bdda59030ed5612f41 Mon Sep 17 00:00:00 2001 From: lanyuanxiaoyao Date: Tue, 27 Jan 2026 10:06:57 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BC=98=E5=8C=96=E6=97=A5=E5=BF=97?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../database/jpa/TestApplication.java | 314 ++++++++++++------ 1 file changed, 208 insertions(+), 106 deletions(-) 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 6f90c44..1d3146f 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 @@ -89,7 +89,8 @@ public class TestApplication extends AbstractTestApplication { } private void testQuery() { - formatLog("准备 Specification 查询的测试数据"); + // ==================== 测试阶段 1: 准备测试数据 ==================== + formatLog("阶段 1: 准备测试数据"); 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()); @@ -155,7 +156,11 @@ public class TestApplication extends AbstractTestApplication { .connections(Map.of(Employee.ConnectionType.EMAIL, "alicesmith@example.com")) .build()); - formatLog("1. 基本比较操作符查询 JPA"); + // ==================== 测试阶段 2: 基本比较操作符查询 ==================== + formatLog("阶段 2: 基本比较操作符查询"); + log.info("查询条件: 姓名='Bob' AND 角色!=ADMIN AND 年龄>20 AND 年龄<30 AND 薪资>=40000 AND 薪资<=45000"); + + formatLog("2.1 JPA Specification"); // 查找姓名为"Bob"、角色不是ADMIN、年龄在20-30之间、薪资在40000-45000之间的员工 var result1_jpa = employeeRepository.findAll((root, query, cb) -> cb.and( cb.equal(root.get(Employee_.name), "Bob"), @@ -165,9 +170,10 @@ public class TestApplication extends AbstractTestApplication { cb.greaterThanOrEqualTo(root.get(Employee_.salary), new BigDecimal("40000.00")), cb.lessThanOrEqualTo(root.get(Employee_.salary), new BigDecimal("45000.00")) )); - Assert.isTrue(result1_jpa.size() == 1, "基本比较操作符查询失败 (%d)".formatted(result1_jpa.size())); + Assert.isTrue(result1_jpa.size() == 1, "JPA Specification 查询失败 (%d)".formatted(result1_jpa.size())); + log.info("JPA Specification 结果: {} 条记录", result1_jpa.size()); - formatLog("1. 基本比较操作符查询 Fenix"); + formatLog("2.2 Fenix"); var result1_fenix = employeeRepository.findAll( builder -> builder.andEquals(Employee.Fields.name, "Bob") .andNotEquals(Employee.Fields.role, Employee.Role.ADMIN) @@ -177,9 +183,10 @@ public class TestApplication extends AbstractTestApplication { .andLessThanEqual(Employee.Fields.salary, new BigDecimal("45000.00")) .build() ); - Assert.isTrue(result1_fenix.size() == 1, "基本比较操作符查询失败 (%d)".formatted(result1_fenix.size())); + Assert.isTrue(result1_fenix.size() == 1, "Fenix 查询失败 (%d)".formatted(result1_fenix.size())); + log.info("Fenix 结果: {} 条记录", result1_fenix.size()); - formatLog("1. 基本比较操作符查询 QueryDSL"); + formatLog("2.3 QueryDSL"); var result1_querydsl = employeeRepository.findAll( QEmployee.employee.name.eq("Bob") .and(QEmployee.employee.role.ne(Employee.Role.ADMIN)) @@ -188,9 +195,10 @@ public class TestApplication extends AbstractTestApplication { .and(QEmployee.employee.salary.goe(new BigDecimal("40000.00"))) .and(QEmployee.employee.salary.loe(new BigDecimal("45000.00"))) ); - Assert.isTrue(result1_querydsl.size() == 1, "基本比较操作符查询失败 (%d)".formatted(result1_querydsl.size())); + Assert.isTrue(result1_querydsl.size() == 1, "QueryDSL 查询失败 (%d)".formatted(result1_querydsl.size())); + log.info("QueryDSL 结果: {} 条记录", result1_querydsl.size()); - formatLog("1. 基本比较操作符查询 HQL"); + formatLog("2.4 HQL"); var result1_hql = manager.createQuery( """ from Employee employee @@ -203,9 +211,14 @@ public class TestApplication extends AbstractTestApplication { """, Employee.class ).getResultList(); - Assert.isTrue(result1_hql.size() == 1, "基本比较操作符查询失败 (%d)".formatted(result1_hql.size())); + Assert.isTrue(result1_hql.size() == 1, "HQL 查询失败 (%d)".formatted(result1_hql.size())); + log.info("HQL 结果: {} 条记录", result1_hql.size()); - formatLog("2. 区间和集合操作符查询 JPA"); + // ==================== 测试阶段 3: 区间和集合操作符查询 ==================== + formatLog("阶段 3: 区间和集合操作符查询"); + log.info("查询条件: 年龄 BETWEEN 25-30 AND NOT BETWEEN 40-50 AND 年龄 IN (25,30,35) AND 角色 IN (USER,ADMIN) AND 姓名 NOT IN (Charlie,David)"); + + formatLog("3.1 JPA Specification"); // 查找年龄在25-30之间、年龄不在40-50之间、年龄在25/30/35中、角色是USER或ADMIN、姓名不在Charlie/David中的员工 var result2_jpa = employeeRepository.findAll((root, query, cb) -> cb.and( cb.between(root.get(Employee_.age), 25, 30), @@ -215,9 +228,10 @@ public class TestApplication extends AbstractTestApplication { cb.not(root.get(Employee_.name).in("Charlie", "David")), root.get(Employee_.name).in("Charlie", "David").not() )); - Assert.isTrue(result2_jpa.size() == 2, "区间和集合操作符查询失败 (%d)".formatted(result2_jpa.size())); + Assert.isTrue(result2_jpa.size() == 2, "JPA Specification 查询失败 (%d)".formatted(result2_jpa.size())); + log.info("JPA Specification 结果: {} 条记录", result2_jpa.size()); - formatLog("2. 区间和集合操作符查询 Fenix"); + formatLog("3.2 Fenix"); var result2_fenix = employeeRepository.findAll( builder -> builder.andBetween(Employee.Fields.age, 25, 30) .andNotBetween(Employee.Fields.age, 40, 50) @@ -226,9 +240,10 @@ public class TestApplication extends AbstractTestApplication { .andNotIn(Employee.Fields.name, List.of("Charlie", "David")) .build() ); - Assert.isTrue(result2_fenix.size() == 2, "区间和集合操作符查询失败 (%d)".formatted(result2_fenix.size())); + Assert.isTrue(result2_fenix.size() == 2, "Fenix 查询失败 (%d)".formatted(result2_fenix.size())); + log.info("Fenix 结果: {} 条记录", result2_fenix.size()); - formatLog("2. 区间和集合操作符查询 QueryDSL"); + formatLog("3.3 QueryDSL"); var result2_querydsl = employeeRepository.findAll( QEmployee.employee.age.between(25, 30) .and(QEmployee.employee.age.between(40, 50).not()) @@ -236,9 +251,10 @@ public class TestApplication extends AbstractTestApplication { .and(QEmployee.employee.role.in(Employee.Role.USER, Employee.Role.ADMIN)) .and(QEmployee.employee.name.in(List.of("Charlie", "David")).not()) ); - Assert.isTrue(result2_querydsl.size() == 2, "区间和集合操作符查询失败 (%d)".formatted(result2_querydsl.size())); + Assert.isTrue(result2_querydsl.size() == 2, "QueryDSL 查询失败 (%d)".formatted(result2_querydsl.size())); + log.info("QueryDSL 结果: {} 条记录", result2_querydsl.size()); - formatLog("2. 区间和集合操作符查询 HQL"); + formatLog("3.4 HQL"); var result2_hql = manager.createQuery( """ from Employee employee @@ -250,9 +266,14 @@ public class TestApplication extends AbstractTestApplication { """, Employee.class ).getResultList(); - Assert.isTrue(result2_hql.size() == 2, "区间和集合操作符查询失败 (%d)".formatted(result2_hql.size())); + Assert.isTrue(result2_hql.size() == 2, "HQL 查询失败 (%d)".formatted(result2_hql.size())); + log.info("HQL 结果: {} 条记录", result2_hql.size()); - formatLog("3. 字符串操作符查询 JPA"); + // ==================== 测试阶段 4: 字符串操作符查询 ==================== + formatLog("阶段 4: 字符串操作符查询"); + log.info("查询条件: 姓名 LIKE 'A%' AND NOT LIKE 'C%' AND LOWER(姓名) LIKE '%ali%' AND UPPER(姓名) LIKE '%ALI%' AND LENGTH(姓名)>4 AND LENGTH(姓名)<=10 AND SUBSTRING(姓名,1,3)='Ali'"); + + formatLog("4.1 JPA Specification"); // 查找以A开头、不以C开头、包含"ali"(忽略大小写)、名称长度在4-10之间、前3个字符为"Ali"的员工 var result3_jpa = employeeRepository.findAll((root, query, cb) -> cb.and( cb.like(root.get(Employee_.name), "A%"), @@ -263,9 +284,10 @@ public class TestApplication extends AbstractTestApplication { cb.lessThanOrEqualTo(cb.length(root.get(Employee_.name)), 10), cb.equal(cb.substring(root.get(Employee_.name), 0, 3), "Ali") )); - Assert.isTrue(result3_jpa.size() == 1, "字符串操作符查询失败 (%d)".formatted(result3_jpa.size())); + Assert.isTrue(result3_jpa.size() == 1, "JPA Specification 查询失败 (%d)".formatted(result3_jpa.size())); + log.info("JPA Specification 结果: {} 条记录", result3_jpa.size()); - formatLog("3. 字符串操作符查询 Fenix"); + formatLog("4.2 Fenix"); log.info(""" Fenix框架当前版本不支持以下字符串操作符: - cb.length() - 字符串长度函数 @@ -277,9 +299,9 @@ public class TestApplication extends AbstractTestApplication { .andNotStartsWith(Employee.Fields.name, "C") .build() ); - log.info("Fenix查询结果: {} 条记录(仅支持部分条件)", result3_fenix.size()); + log.info("Fenix 结果: {} 条记录(仅支持部分条件)", result3_fenix.size()); - formatLog("3. 字符串操作符查询 QueryDSL"); + formatLog("4.3 QueryDSL"); var result3_querydsl = employeeRepository.findAll( QEmployee.employee.name.startsWith("A") .and(QEmployee.employee.name.startsWith("C").not()) @@ -289,9 +311,10 @@ public class TestApplication extends AbstractTestApplication { .and(QEmployee.employee.name.length().loe(10)) .and(QEmployee.employee.name.substring(0, 3).eq("Ali")) ); - Assert.isTrue(result3_querydsl.size() == 1, "字符串操作符查询失败 (%d)".formatted(result3_querydsl.size())); + Assert.isTrue(result3_querydsl.size() == 1, "QueryDSL 查询失败 (%d)".formatted(result3_querydsl.size())); + log.info("QueryDSL 结果: {} 条记录", result3_querydsl.size()); - formatLog("3. 字符串操作符查询 HQL"); + formatLog("4.4 HQL"); var result3_hql = manager.createQuery( """ from Employee employee @@ -305,35 +328,43 @@ public class TestApplication extends AbstractTestApplication { """, Employee.class ).getResultList(); - Assert.isTrue(result3_hql.size() == 1, "字符串操作符查询失败 (%d)".formatted(result3_hql.size())); + Assert.isTrue(result3_hql.size() == 1, "HQL 查询失败 (%d)".formatted(result3_hql.size())); + log.info("HQL 结果: {} 条记录", result3_hql.size()); - formatLog("4. NULL 和布尔操作符查询 JPA"); + // ==================== 测试阶段 5: NULL 和布尔操作符查询 ==================== + formatLog("阶段 5: NULL 和布尔操作符查询"); + log.info("查询条件: active=true AND bonus IS NOT NULL AND code NOT IN ('E999')"); + + formatLog("5.1 JPA Specification"); // 查找激活状态为true、bonus不为null、code不在E999中的员工 var result4_jpa = employeeRepository.findAll((root, query, cb) -> cb.and( cb.isTrue(root.get(Employee_.active)), cb.isNotNull(root.get(Employee_.bonus)), cb.not(root.get(Employee_.code).in("E999")) )); - Assert.isTrue(!result4_jpa.isEmpty(), "NULL 和布尔操作符查询失败 (%d)".formatted(result4_jpa.size())); + Assert.isTrue(!result4_jpa.isEmpty(), "JPA Specification 查询失败 (%d)".formatted(result4_jpa.size())); + log.info("JPA Specification 结果: {} 条记录", result4_jpa.size()); - formatLog("4. NULL 和布尔操作符查询 Fenix"); + formatLog("5.2 Fenix"); var result4_fenix = employeeRepository.findAll( builder -> builder.andEquals(Employee.Fields.active, true) .andIsNotNull(Employee.Fields.bonus) .andNotIn(Employee.Fields.code, List.of("E999")) .build() ); - Assert.isTrue(!result4_fenix.isEmpty(), "NULL 和布尔操作符查询失败 (%d)".formatted(result4_fenix.size())); + Assert.isTrue(!result4_fenix.isEmpty(), "Fenix 查询失败 (%d)".formatted(result4_fenix.size())); + log.info("Fenix 结果: {} 条记录", result4_fenix.size()); - formatLog("4. NULL 和布尔操作符查询 QueryDSL"); + formatLog("5.3 QueryDSL"); var result4_querydsl = employeeRepository.findAll( QEmployee.employee.active.isTrue() .and(QEmployee.employee.bonus.isNotNull()) .and(QEmployee.employee.code.in("E999").not()) ); - Assert.isTrue(!result4_querydsl.isEmpty(), "NULL 和布尔操作符查询失败 (%d)".formatted(result4_querydsl.size())); + Assert.isTrue(!result4_querydsl.isEmpty(), "QueryDSL 查询失败 (%d)".formatted(result4_querydsl.size())); + log.info("QueryDSL 结果: {} 条记录", result4_querydsl.size()); - formatLog("4. NULL 和布尔操作符查询 HQL"); + formatLog("5.4 HQL"); var result4_hql = manager.createQuery( """ from Employee employee @@ -343,9 +374,14 @@ public class TestApplication extends AbstractTestApplication { """, Employee.class ).getResultList(); - Assert.isTrue(!result4_hql.isEmpty(), "NULL 和布尔操作符查询失败 (%d)".formatted(result4_hql.size())); + Assert.isTrue(!result4_hql.isEmpty(), "HQL 查询失败 (%d)".formatted(result4_hql.size())); + log.info("HQL 结果: {} 条记录", result4_hql.size()); - formatLog("5. 集合操作符查询 JPA"); + // ==================== 测试阶段 6: 集合操作符查询 ==================== + formatLog("阶段 6: 集合操作符查询"); + log.info("查询条件: skills IS NOT EMPTY AND hobbies IS NOT EMPTY AND 'Reading' MEMBER OF hobbies AND 'Riding' NOT MEMBER OF hobbies AND SIZE(hobbies)>1 AND SIZE(skills)<4"); + + formatLog("6.1 JPA Specification"); // 查找技能集合非空、爱好集合非空、包含"Reading"爱好、不包含"Riding"爱好、爱好数量大于1、技能数量小于4的员工 var result5_jpa = employeeRepository.findAll((root, query, cb) -> cb.and( cb.isNotEmpty(root.get(Employee_.skills)), @@ -355,9 +391,10 @@ public class TestApplication extends AbstractTestApplication { cb.greaterThan(cb.size(root.get(Employee_.hobbies)), 1), cb.lessThan(cb.size(root.get(Employee_.skills)), 4) )); - Assert.isTrue(result5_jpa.size() == 2, "集合操作符查询失败 (%d)".formatted(result5_jpa.size())); + Assert.isTrue(result5_jpa.size() == 2, "JPA Specification 查询失败 (%d)".formatted(result5_jpa.size())); + log.info("JPA Specification 结果: {} 条记录", result5_jpa.size()); - formatLog("5. 集合操作符查询 Fenix"); + formatLog("6.2 Fenix"); log.info(""" Fenix框架当前版本不支持以下集合操作符: - cb.isNotEmpty() / cb.isEmpty() - 集合非空/空判断 @@ -365,7 +402,7 @@ public class TestApplication extends AbstractTestApplication { - cb.size() - 集合大小函数 这些集合操作在JPA Criteria中需要复杂的join处理,Fenix当前不支持"""); - formatLog("5. 集合操作符查询 QueryDSL"); + formatLog("6.3 QueryDSL"); var result5_querydsl = employeeRepository.findAll( QEmployee.employee.skills.isNotEmpty() .and(QEmployee.employee.hobbies.isNotEmpty()) @@ -374,9 +411,10 @@ public class TestApplication extends AbstractTestApplication { .and(QEmployee.employee.hobbies.size().gt(1)) .and(QEmployee.employee.skills.size().lt(4)) ); - Assert.isTrue(result5_querydsl.size() == 2, "集合操作符查询失败 (%d)".formatted(result5_querydsl.size())); + Assert.isTrue(result5_querydsl.size() == 2, "QueryDSL 查询失败 (%d)".formatted(result5_querydsl.size())); + log.info("QueryDSL 结果: {} 条记录", result5_querydsl.size()); - formatLog("5. 集合操作符查询 HQL"); + formatLog("6.4 HQL"); var result5_hql = manager.createQuery( """ from Employee employee @@ -389,9 +427,14 @@ public class TestApplication extends AbstractTestApplication { """, Employee.class ).getResultList(); - Assert.isTrue(result5_hql.size() == 2, "集合操作符查询失败 (%d)".formatted(result5_hql.size())); + Assert.isTrue(result5_hql.size() == 2, "HQL 查询失败 (%d)".formatted(result5_hql.size())); + log.info("HQL 结果: {} 条记录", result5_hql.size()); - formatLog("6. 逻辑操作符查询 JPA"); + // ==================== 测试阶段 7: 逻辑操作符查询 ==================== + formatLog("阶段 7: 逻辑操作符查询"); + log.info("查询条件: (姓名='Alice' OR 姓名='Bob') AND NOT (姓名='Charlie' OR 姓名='David')"); + + formatLog("7.1 JPA Specification"); // 查找姓名为Alice或Bob、且姓名不为Charlie或David的员工 var result6_jpa = employeeRepository.findAll((root, query, cb) -> cb.and( cb.or( @@ -405,9 +448,10 @@ public class TestApplication extends AbstractTestApplication { ) ) )); - Assert.isTrue(result6_jpa.size() == 2, "逻辑操作符查询失败 (%d)".formatted(result6_jpa.size())); + Assert.isTrue(result6_jpa.size() == 2, "JPA Specification 查询失败 (%d)".formatted(result6_jpa.size())); + log.info("JPA Specification 结果: {} 条记录", result6_jpa.size()); - formatLog("6. 逻辑操作符查询 Fenix"); + formatLog("7.2 Fenix"); log.info(""" Fenix框架当前版本不支持复杂的嵌套OR和NOT组合逻辑 Fenix支持简单的orEquals,但不支持嵌套的or + not组合"""); @@ -417,16 +461,18 @@ public class TestApplication extends AbstractTestApplication { .andNotIn(Employee.Fields.name, List.of("Charlie", "David")) .build() ); - Assert.isTrue(result6_fenix.size() == 3, "逻辑操作符查询失败 (%d)".formatted(result6_fenix.size())); + Assert.isTrue(result6_fenix.size() == 3, "Fenix 查询失败 (%d)".formatted(result6_fenix.size())); + log.info("Fenix 结果: {} 条记录(逻辑不完全等价)", result6_fenix.size()); - formatLog("6. 逻辑操作符查询 QueryDSL"); + formatLog("7.3 QueryDSL"); var result6_querydsl = employeeRepository.findAll( QEmployee.employee.name.eq("Alice").or(QEmployee.employee.name.eq("Bob")) .and(QEmployee.employee.name.eq("Charlie").or(QEmployee.employee.name.eq("David")).not()) ); - Assert.isTrue(result6_querydsl.size() == 2, "逻辑操作符查询失败 (%d)".formatted(result6_querydsl.size())); + Assert.isTrue(result6_querydsl.size() == 2, "QueryDSL 查询失败 (%d)".formatted(result6_querydsl.size())); + log.info("QueryDSL 结果: {} 条记录", result6_querydsl.size()); - formatLog("6. 逻辑操作符查询 HQL"); + formatLog("7.4 HQL"); var result6_hql = manager.createQuery( """ from Employee employee @@ -435,9 +481,14 @@ public class TestApplication extends AbstractTestApplication { """, Employee.class ).getResultList(); - Assert.isTrue(result6_hql.size() == 2, "逻辑操作符查询失败 (%d)".formatted(result6_hql.size())); + Assert.isTrue(result6_hql.size() == 2, "HQL 查询失败 (%d)".formatted(result6_hql.size())); + log.info("HQL 结果: {} 条记录", result6_hql.size()); - formatLog("7. Specification 链式调用查询 JPA"); + // ==================== 测试阶段 8: Specification 链式调用查询 ==================== + formatLog("阶段 8: Specification 链式调用查询"); + log.info("查询条件: active=true AND age>25 AND role!=ADMIN OR name='Charlie' AND name!='Alice Smith'"); + + formatLog("8.1 JPA Specification"); // 链式组合:激活状态为true、年龄大于25、角色不是ADMIN、或姓名为Charlie、且姓名不为Alice Smith var result7_jpa = employeeRepository.findAll( Specification.where((root, query, cb) -> cb.isTrue(root.get(Employee_.active))) @@ -446,9 +497,10 @@ public class TestApplication extends AbstractTestApplication { .or((root, query, cb) -> cb.equal(root.get(Employee_.name), "Charlie")) .and((root, query, cb) -> cb.notEqual(root.get(Employee_.name), "Alice Smith")) ); - Assert.isTrue(result7_jpa.size() == 2, "Specification 链式调用失败 (%d)".formatted(result7_jpa.size())); + Assert.isTrue(result7_jpa.size() == 2, "JPA Specification 链式调用失败 (%d)".formatted(result7_jpa.size())); + log.info("JPA Specification 结果: {} 条记录", result7_jpa.size()); - formatLog("7. Specification 链式调用查询 Fenix"); + formatLog("8.2 Fenix"); var result7_fenix = employeeRepository.findAll( builder -> builder.andEquals(Employee.Fields.active, true) .andGreaterThan(Employee.Fields.age, 25) @@ -457,9 +509,10 @@ public class TestApplication extends AbstractTestApplication { .andNotEquals(Employee.Fields.name, "Alice Smith") .build() ); - Assert.isTrue(result7_fenix.size() == 2, "Specification 链式调用失败 (%d)".formatted(result7_fenix.size())); + Assert.isTrue(result7_fenix.size() == 2, "Fenix 链式调用失败 (%d)".formatted(result7_fenix.size())); + log.info("Fenix 结果: {} 条记录", result7_fenix.size()); - formatLog("7. Specification 链式调用查询 QueryDSL"); + formatLog("8.3 QueryDSL"); var result7_querydsl = employeeRepository.findAll( QEmployee.employee.active.isTrue() .and(QEmployee.employee.age.gt(25)) @@ -467,9 +520,10 @@ public class TestApplication extends AbstractTestApplication { .or(QEmployee.employee.name.eq("Charlie")) .and(QEmployee.employee.name.ne("Alice Smith")) ); - Assert.isTrue(result7_querydsl.size() == 2, "Specification 链式调用失败 (%d)".formatted(result7_querydsl.size())); + Assert.isTrue(result7_querydsl.size() == 2, "QueryDSL 链式调用失败 (%d)".formatted(result7_querydsl.size())); + log.info("QueryDSL 结果: {} 条记录", result7_querydsl.size()); - formatLog("7. Specification 链式调用查询 HQL"); + formatLog("8.4 HQL"); var result7_hql = manager.createQuery( """ from Employee employee @@ -481,9 +535,14 @@ public class TestApplication extends AbstractTestApplication { """, Employee.class ).getResultList(); - Assert.isTrue(result7_hql.size() == 2, "Specification 链式调用失败 (%d)".formatted(result7_hql.size())); + Assert.isTrue(result7_hql.size() == 2, "HQL 链式调用失败 (%d)".formatted(result7_hql.size())); + log.info("HQL 结果: {} 条记录", result7_hql.size()); - formatLog("8. Join 操作查询 JPA"); + // ==================== 测试阶段 9: Join 操作查询 ==================== + formatLog("阶段 9: Join 操作查询"); + log.info("查询条件: company.name='TechCorp' AND company.name!='DataInc' AND skill.name='Java' AND skill.name!='MySQL' AND prop.value='Senior' AND prop.value!='Junior'"); + + formatLog("9.1 JPA Specification"); // 查找公司名为TechCorp、技能包含Java、属性值为Senior的员工(使用join、fetch、集合join、map join) var result8_jpa = employeeRepository.findAll((root, query, cb) -> { return cb.and( @@ -498,9 +557,10 @@ public class TestApplication extends AbstractTestApplication { cb.notEqual(root.join(Employee_.properties).value(), "Junior") ); }); - Assert.isTrue(result8_jpa.size() == 1, "Join 操作查询失败 (%d)".formatted(result8_jpa.size())); + Assert.isTrue(result8_jpa.size() == 1, "JPA Specification Join 查询失败 (%d)".formatted(result8_jpa.size())); + log.info("JPA Specification 结果: {} 条记录", result8_jpa.size()); - formatLog("8. Join 操作查询 Fenix"); + formatLog("9.2 Fenix"); log.info(""" Fenix框架当前版本不支持显式的join操作: - root.join() - 显式关联查询 @@ -511,7 +571,7 @@ public class TestApplication extends AbstractTestApplication { 注意:由于类型系统的限制,doAny中使用join可能会有类型推断问题 建议:对于join等复杂查询,直接使用JPA Specification原生方式"""); - formatLog("8. Join 操作查询 QueryDSL"); + formatLog("9.3 QueryDSL"); var result8_querydsl = employeeRepository.findAll( QEmployee.employee.company().name.eq("TechCorp") .and(QEmployee.employee.company().name.ne("DataInc")) @@ -520,9 +580,10 @@ public class TestApplication extends AbstractTestApplication { .and(QEmployee.employee.properties.containsValue("Senior")) .and(QEmployee.employee.properties.containsValue("Junior").not()) ); - Assert.isTrue(result8_querydsl.size() == 1, "Join 操作查询失败 (%d)".formatted(result8_querydsl.size())); + Assert.isTrue(result8_querydsl.size() == 1, "QueryDSL Join 查询失败 (%d)".formatted(result8_querydsl.size())); + log.info("QueryDSL 结果: {} 条记录", result8_querydsl.size()); - formatLog("8. Join 操作查询 HQL"); + formatLog("9.4 HQL"); var result8_hql = manager.createQuery( """ from Employee employee @@ -538,9 +599,14 @@ public class TestApplication extends AbstractTestApplication { """, Employee.class ).getResultList(); - Assert.isTrue(result8_hql.size() == 1, "Join 操作查询失败 (%d)".formatted(result8_hql.size())); + Assert.isTrue(result8_hql.size() == 1, "HQL Join 查询失败 (%d)".formatted(result8_hql.size())); + log.info("HQL 结果: {} 条记录", result8_hql.size()); - formatLog("9. 子查询和聚合函数查询 JPA"); + // ==================== 测试阶段 10: 子查询和聚合函数查询 ==================== + formatLog("阶段 10: 子查询和聚合函数查询"); + log.info("查询条件: salary>AVG(salary) AND 5<>COUNT(id) AND salary+COALESCE(bonus,0)>55000 AND salary+COALESCE(bonus,0)<70000 AND active=true AND name!='David' AND age>28 AND role!=USER"); + + formatLog("10.1 JPA Specification"); // 查找薪资高于平均薪资、总记录数不为5、总薪酬在55000-70000之间、激活状态为true、姓名不为David、年龄大于28、角色不是USER的员工 var result9_jpa = employeeRepository.findAll((root, query, cb) -> { var avgSalarySubquery = query.subquery(Double.class); @@ -566,9 +632,10 @@ public class TestApplication extends AbstractTestApplication { cb.notEqual(root.get(Employee_.role), Employee.Role.USER) ); }); - Assert.isTrue(result9_jpa.isEmpty(), "子查询(聚合函数)+ 数学运算失败 (%d)".formatted(result9_jpa.size())); + Assert.isTrue(result9_jpa.isEmpty(), "JPA Specification 子查询(聚合函数)+ 数学运算失败 (%d)".formatted(result9_jpa.size())); + log.info("JPA Specification 结果: {} 条记录", result9_jpa.size()); - formatLog("9. 子查询和聚合函数查询 Fenix"); + formatLog("10.2 Fenix"); log.info(""" Fenix框架当前版本不支持以下高级查询特性: - query.subquery() - 子查询 @@ -578,7 +645,7 @@ public class TestApplication extends AbstractTestApplication { 这些是SQL级别的复杂查询,Fenix主要用于动态条件构建 可以通过doAny使用原生CriteriaBuilder实现部分聚合操作"""); - formatLog("9. 子查询和聚合函数查询 QueryDSL"); + formatLog("10.3 QueryDSL"); var avgQuery = factory.select(QEmployee.employee.salary.avg()); var countQuery = factory.select(QEmployee.employee.count()); var result9_querydsl = employeeRepository.findAll( @@ -591,9 +658,10 @@ public class TestApplication extends AbstractTestApplication { .and(QEmployee.employee.age.gt(28)) .and(QEmployee.employee.role.ne(Employee.Role.USER)) ); - Assert.isTrue(result9_querydsl.isEmpty(), "子查询(聚合函数)+ 数学运算失败 (%d)".formatted(result9_querydsl.size())); + Assert.isTrue(result9_querydsl.isEmpty(), "QueryDSL 子查询(聚合函数)+ 数学运算失败 (%d)".formatted(result9_querydsl.size())); + log.info("QueryDSL 结果: {} 条记录", result9_querydsl.size()); - formatLog("9. 子查询和聚合函数查询 HQL"); + formatLog("10.4 HQL"); var result9_hql = manager.createQuery( """ from Employee employee @@ -608,9 +676,14 @@ public class TestApplication extends AbstractTestApplication { """, Employee.class ).getResultList(); - Assert.isTrue(result9_hql.isEmpty(), "子查询(聚合函数)+ 数学运算失败 (%d)".formatted(result9_hql.size())); + Assert.isTrue(result9_hql.isEmpty(), "HQL 子查询(聚合函数)+ 数学运算失败 (%d)".formatted(result9_hql.size())); + log.info("HQL 结果: {} 条记录", result9_hql.size()); - formatLog("10. 排序查询 JPA"); + // ==================== 测试阶段 11: 排序查询 ==================== + formatLog("阶段 11: 排序查询"); + log.info("查询条件: active=true AND role!=ADMIN AND age>20 AND salary<60000 ORDER BY age DESC, name ASC"); + + formatLog("11.1 JPA Specification"); // 查找激活状态为true、角色不是ADMIN、年龄大于20、薪资小于60000的员工,按年龄降序、姓名升序排序 var result10_jpa = employeeRepository.findAll( (root, query, cb) -> cb.and( @@ -624,9 +697,10 @@ public class TestApplication extends AbstractTestApplication { Sort.Order.asc(Employee_.NAME) ) ); - Assert.isTrue(result10_jpa.size() == 3, "排序查询失败 (%d)".formatted(result10_jpa.size())); + Assert.isTrue(result10_jpa.size() == 3, "JPA Specification 排序查询失败 (%d)".formatted(result10_jpa.size())); + log.info("JPA Specification 结果: {} 条记录", result10_jpa.size()); - formatLog("10. 排序查询 Fenix"); + formatLog("11.2 Fenix"); log.info(""" Fenix框架使用Spring Data JPA原生的Sort对象进行排序 Fenix构建查询条件,Sort对象通过repository.findAll()的第二个参数传入"""); @@ -641,9 +715,10 @@ public class TestApplication extends AbstractTestApplication { Sort.Order.asc(Employee.Fields.name) ) ); - Assert.isTrue(result10_fenix.size() == 3, "排序查询失败 (%d)".formatted(result10_fenix.size())); + Assert.isTrue(result10_fenix.size() == 3, "Fenix 排序查询失败 (%d)".formatted(result10_fenix.size())); + log.info("Fenix 结果: {} 条记录", result10_fenix.size()); - formatLog("10. 排序查询 QueryDSL"); + formatLog("11.3 QueryDSL"); var result10_querydsl = employeeRepository.findAll( QEmployee.employee.active.isTrue() .and(QEmployee.employee.role.ne(Employee.Role.ADMIN)) @@ -652,9 +727,10 @@ public class TestApplication extends AbstractTestApplication { QEmployee.employee.age.desc(), QEmployee.employee.name.asc() ); - Assert.isTrue(result10_querydsl.size() == 3, "排序查询失败 (%d)".formatted(result10_querydsl.size())); + Assert.isTrue(result10_querydsl.size() == 3, "QueryDSL 排序查询失败 (%d)".formatted(result10_querydsl.size())); + log.info("QueryDSL 结果: {} 条记录", result10_querydsl.size()); - formatLog("10. 排序查询 HQL"); + formatLog("11.4 HQL"); var result10_hql = manager.createQuery( """ from Employee employee @@ -666,9 +742,14 @@ public class TestApplication extends AbstractTestApplication { """, Employee.class ).getResultList(); - Assert.isTrue(result10_hql.size() == 3, "排序查询失败 (%d)".formatted(result10_hql.size())); + Assert.isTrue(result10_hql.size() == 3, "HQL 排序查询失败 (%d)".formatted(result10_hql.size())); + log.info("HQL 结果: {} 条记录", result10_hql.size()); - formatLog("11. 分页查询 JPA"); + // ==================== 测试阶段 12: 分页查询 ==================== + formatLog("阶段 12: 分页查询"); + log.info("查询条件: active=true AND role!=ADMIN AND age>20 AND salary<60000 ORDER BY age LIMIT 2 OFFSET 0"); + + formatLog("12.1 JPA Specification"); // 分页查找激活状态为true、角色不是ADMIN、年龄大于20、薪资小于60000的员工,每页2条,按年龄排序 var page11_jpa = employeeRepository.findAll( (root, query, cb) -> cb.and( @@ -679,10 +760,11 @@ public class TestApplication extends AbstractTestApplication { ), PageRequest.of(0, 2, Sort.by(Employee_.AGE)) ); - Assert.isTrue(page11_jpa.getContent().size() == 2, "分页大小不正确 (%d)".formatted(page11_jpa.getContent().size())); - Assert.isTrue(page11_jpa.getTotalElements() == 3, "总元素数不正确 (%d)".formatted(page11_jpa.getTotalElements())); + Assert.isTrue(page11_jpa.getContent().size() == 2, "JPA Specification 分页大小不正确 (%d)".formatted(page11_jpa.getContent().size())); + Assert.isTrue(page11_jpa.getTotalElements() == 3, "JPA Specification 总元素数不正确 (%d)".formatted(page11_jpa.getTotalElements())); + log.info("JPA Specification 结果: {} 条记录,总记录数: {}", page11_jpa.getContent().size(), page11_jpa.getTotalElements()); - formatLog("11. 分页查询 Fenix"); + formatLog("12.2 Fenix"); log.info(""" Fenix框架使用Spring Data JPA原生的Pageable对象进行分页 Fenix构建查询条件,Pageable对象通过repository.findAll()的第二个参数传入"""); @@ -694,10 +776,11 @@ public class TestApplication extends AbstractTestApplication { .build(), PageRequest.of(0, 2, Sort.by(Employee.Fields.age)) ); - Assert.isTrue(page11_fenix.getContent().size() == 2, "分页大小不正确 (%d)".formatted(page11_fenix.getContent().size())); - Assert.isTrue(page11_fenix.getTotalElements() == 3, "总元素数不正确 (%d)".formatted(page11_fenix.getTotalElements())); + Assert.isTrue(page11_fenix.getContent().size() == 2, "Fenix 分页大小不正确 (%d)".formatted(page11_fenix.getContent().size())); + Assert.isTrue(page11_fenix.getTotalElements() == 3, "Fenix 总元素数不正确 (%d)".formatted(page11_fenix.getTotalElements())); + log.info("Fenix 结果: {} 条记录,总记录数: {}", page11_fenix.getContent().size(), page11_fenix.getTotalElements()); - formatLog("11. 分页查询 QueryDSL"); + formatLog("12.3 QueryDSL"); log.info(""" QueryDSL支持分页查询: - offset() - 跳过记录数 @@ -710,10 +793,11 @@ public class TestApplication extends AbstractTestApplication { .and(QEmployee.employee.salary.lt(new BigDecimal("60000.00"))), PageRequest.of(0, 2, Sort.by(QEmployee.employee.age.getMetadata().getName())) ); - Assert.isTrue(page11_querydsl.getContent().size() == 2, "分页大小不正确 (%d)".formatted(page11_querydsl.getContent().size())); - Assert.isTrue(page11_querydsl.getTotalElements() == 3, "总元素数不正确 (%d)".formatted(page11_querydsl.getTotalElements())); + Assert.isTrue(page11_querydsl.getContent().size() == 2, "QueryDSL 分页大小不正确 (%d)".formatted(page11_querydsl.getContent().size())); + Assert.isTrue(page11_querydsl.getTotalElements() == 3, "QueryDSL 总元素数不正确 (%d)".formatted(page11_querydsl.getTotalElements())); + log.info("QueryDSL 结果: {} 条记录,总记录数: {}", page11_querydsl.getContent().size(), page11_querydsl.getTotalElements()); - formatLog("11. 分页查询 HQL"); + formatLog("12.4 HQL"); var page11_hql = manager.createQuery( """ from Employee employee @@ -735,10 +819,15 @@ public class TestApplication extends AbstractTestApplication { """, Employee.class ).getResultList().size(); - Assert.isTrue(page11_hql.size() == 2, "分页大小不正确 (%d)".formatted(page11_hql.size())); - Assert.isTrue(total11_hql == 3, "总元素数不正确 (%d)".formatted(total11_hql)); + Assert.isTrue(page11_hql.size() == 2, "HQL 分页大小不正确 (%d)".formatted(page11_hql.size())); + Assert.isTrue(total11_hql == 3, "HQL 总元素数不正确 (%d)".formatted(total11_hql)); + log.info("HQL 结果: {} 条记录,总记录数: {}", page11_hql.size(), total11_hql); - formatLog("12. CASE WHEN 条件表达式查询 JPA"); + // ==================== 测试阶段 13: CASE WHEN 条件表达式查询 ==================== + formatLog("阶段 13: CASE WHEN 条件表达式查询"); + log.info("查询条件: CASE WHEN age>30 THEN 'Senior' WHEN age BETWEEN 25-30 THEN 'Middle' ELSE 'Junior' END = 'Senior' AND CASE WHEN age>30 THEN 'Senior' WHEN age BETWEEN 25-30 THEN 'Middle' ELSE 'Junior' END != 'Junior'"); + + formatLog("13.1 JPA Specification"); // 查找年龄大于30(Senior)或年龄在25-30之间(Middle)的员工,排除Junior级别的员工 var result12_jpa = employeeRepository.findAll((root, query, cb) -> cb.and( cb.equal( @@ -756,9 +845,10 @@ public class TestApplication extends AbstractTestApplication { "Junior" ) )); - Assert.isTrue(result12_jpa.size() == 2, "CASE WHEN 查询失败 (%d)".formatted(result12_jpa.size())); + Assert.isTrue(result12_jpa.size() == 2, "JPA Specification CASE WHEN 查询失败 (%d)".formatted(result12_jpa.size())); + log.info("JPA Specification 结果: {} 条记录", result12_jpa.size()); - formatLog("12. CASE WHEN 条件表达式查询 Fenix"); + formatLog("13.2 Fenix"); log.info(""" Fenix框架当前版本不支持CASE WHEN条件表达式: - cb.selectCase() - SQL CASE WHEN表达式 @@ -781,9 +871,10 @@ public class TestApplication extends AbstractTestApplication { ); }).build(); }); - Assert.isTrue(result12_fenix.size() == 2, "CASE WHEN 查询失败 (%d)".formatted(result12_fenix.size())); + Assert.isTrue(result12_fenix.size() == 2, "Fenix CASE WHEN 查询失败 (%d)".formatted(result12_fenix.size())); + log.info("Fenix 结果: {} 条记录", result12_fenix.size()); - formatLog("12. CASE WHEN 条件表达式查询 QueryDSL"); + formatLog("13.3 QueryDSL"); var caseExpr = new CaseBuilder() .when(QEmployee.employee.age.gt(30)).then("Senior") .when(QEmployee.employee.age.between(25, 30)).then("Middle") @@ -791,9 +882,10 @@ public class TestApplication extends AbstractTestApplication { var result12_querydsl = employeeRepository.findAll( caseExpr.eq("Senior").and(caseExpr.ne("Junior")) ); - Assert.isTrue(result12_querydsl.size() == 2, "CASE WHEN 查询失败 (%d)".formatted(result12_querydsl.size())); + Assert.isTrue(result12_querydsl.size() == 2, "QueryDSL CASE WHEN 查询失败 (%d)".formatted(result12_querydsl.size())); + log.info("QueryDSL 结果: {} 条记录", result12_querydsl.size()); - formatLog("12. CASE WHEN 条件表达式查询 HQL"); + formatLog("13.4 HQL"); var result12_hql = manager.createQuery( """ from Employee employee @@ -808,9 +900,14 @@ public class TestApplication extends AbstractTestApplication { """, Employee.class ).getResultList(); - Assert.isTrue(result12_hql.size() == 2, "CASE WHEN 查询失败 (%d)".formatted(result12_hql.size())); + Assert.isTrue(result12_hql.size() == 2, "HQL CASE WHEN 查询失败 (%d)".formatted(result12_hql.size())); + log.info("HQL 结果: {} 条记录", result12_hql.size()); - formatLog("13. 综合多条件查询 JPA"); + // ==================== 测试阶段 14: 综合多条件查询 ==================== + formatLog("阶段 14: 综合多条件查询"); + log.info("查询条件: company.name='TechCorp' AND company.name!='DataInc' AND skill.name='Java' AND skill.name!='MySQL' AND address.city='Beijing' AND address.city!='Shanghai' AND skills IS NOT EMPTY AND hobbies IS NOT EMPTY AND properties IS NOT EMPTY AND createdTime IS NOT NULL AND modifiedTime IS NOT NULL AND active=true AND name!='Alice Smith' AND age>25 AND role!=USER"); + + formatLog("14.1 JPA Specification"); // 综合查询:公司名为TechCorp、技能包含Java、城市为Beijing、技能和爱好非空、属性非空、创建和修改时间不为null、激活状态为true、姓名不为Alice Smith、年龄大于25、角色不是USER var result13_jpa = employeeRepository.findAll((root, query, cb) -> { query.distinct(true); @@ -839,9 +936,10 @@ public class TestApplication extends AbstractTestApplication { cb.notEqual(root.get(Employee_.role), Employee.Role.USER) ); }); - Assert.isTrue(result13_jpa.size() == 1 && result13_jpa.get(0).getName().equals("Alice"), "综合多条件查询失败 (%d)".formatted(result13_jpa.size())); + Assert.isTrue(result13_jpa.size() == 1 && result13_jpa.get(0).getName().equals("Alice"), "JPA Specification 综合多条件查询失败 (%d)".formatted(result13_jpa.size())); + log.info("JPA Specification 结果: {} 条记录", result13_jpa.size()); - formatLog("13. 综合多条件查询 Fenix"); + formatLog("14.2 Fenix"); log.info(""" Fenix框架不支持综合多条件查询中的复杂join和集合操作: - 显式join操作(company, skills, properties) @@ -851,7 +949,7 @@ public class TestApplication extends AbstractTestApplication { Fenix主要支持简单的单表字段查询 可以通过doAny使用原生CriteriaBuilder实现复杂综合查询"""); - formatLog("13. 综合多条件查询 QueryDSL"); + formatLog("14.3 QueryDSL"); var result13_querydsl = employeeRepository.findAll( // Company Join 条件 QEmployee.employee.company().name.eq("TechCorp") @@ -876,9 +974,10 @@ public class TestApplication extends AbstractTestApplication { .and(QEmployee.employee.age.gt(25)) .and(QEmployee.employee.role.ne(Employee.Role.USER)) ); - Assert.isTrue(result13_querydsl.size() == 1 && result13_querydsl.get(0).getName().equals("Alice"), "综合多条件查询失败 (%d)".formatted(result13_querydsl.size())); + Assert.isTrue(result13_querydsl.size() == 1 && result13_querydsl.get(0).getName().equals("Alice"), "QueryDSL 综合多条件查询失败 (%d)".formatted(result13_querydsl.size())); + log.info("QueryDSL 结果: {} 条记录", result13_querydsl.size()); - formatLog("13. 综合多条件查询 HQL"); + formatLog("14.4 HQL"); var result13_hql = manager.createQuery( """ select distinct employee @@ -903,11 +1002,14 @@ public class TestApplication extends AbstractTestApplication { """, Employee.class ).getResultList(); - Assert.isTrue(result13_hql.size() == 1 && result13_hql.get(0).getName().equals("Alice"), "综合多条件查询失败 (%d)".formatted(result13_hql.size())); + Assert.isTrue(result13_hql.size() == 1 && result13_hql.get(0).getName().equals("Alice"), "HQL 综合多条件查询失败 (%d)".formatted(result13_hql.size())); + log.info("HQL 结果: {} 条记录", result13_hql.size()); - formatLog("清理测试数据"); + // ==================== 测试阶段 15: 清理测试数据 ==================== + formatLog("阶段 15: 清理测试数据"); employeeRepository.deleteAllInBatch(); companyRepository.deleteAllInBatch(); + log.info("测试数据清理完成"); } private void testMethodQuery() {