Compare commits
2 Commits
2db84152b5
...
e9b0e79d48
| Author | SHA1 | Date | |
|---|---|---|---|
| e9b0e79d48 | |||
| 18cd1dbed8 |
879
README.md
879
README.md
@@ -11,6 +11,7 @@
|
||||
- [5. 使用指南](#5-使用指南)
|
||||
- [6. 开发规范](#6-开发规范)
|
||||
- [7. 项目集成指南](#7-项目集成指南)
|
||||
- [8. 更新日志](#8-更新日志)
|
||||
|
||||
## 1. 项目概述
|
||||
|
||||
@@ -20,23 +21,42 @@ Spring Boot Service Template 是一个标准化的微服务基础结构模板,
|
||||
|
||||
该模板内置了完善的实体审计机制,自动维护实体的创建时间和修改时间。同时,提供了强大的查询功能,支持多种条件查询、分页和排序功能。
|
||||
|
||||
**当前版本**:1.1.0-SNAPSHOT
|
||||
|
||||
**核心版本依赖**:
|
||||
- Java 17
|
||||
- Spring Boot 4.0.0
|
||||
- Spring Cloud 2025.1.0
|
||||
- Hibernate 7.1.8.Final
|
||||
- QueryDSL 7.1
|
||||
- Easy Query 3.1.68
|
||||
- Xbatis 1.9.7-spring-boot4
|
||||
|
||||
Spring Boot Service Template 是一个标准化的微服务基础结构模板,专为 Java 开发者和微服务架构设计者打造。该项目采用模块化架构设计,提供了一套完整的 CRUD 操作框架,支持多种数据库访问方式(JPA、Easy Query、Xbatis),通过泛型支持不同类型的数据转换,大大减少了重复代码的编写。
|
||||
|
||||
该模板内置了完善的实体审计机制,自动维护实体的创建时间和修改时间。同时,提供了强大的查询功能,支持多种条件查询、分页和排序功能。
|
||||
|
||||
### 1.2 项目目标
|
||||
|
||||
提供统一的服务模板,简化新项目的搭建过程,提高开发效率。通过封装通用的业务逻辑,让开发者能够专注于核心业务功能的实现。
|
||||
|
||||
### 1.3 核心特性
|
||||
|
||||
- 模块化架构:采用多模块设计,通用功能与数据库操作分离,便于按需引入
|
||||
- 标准化的项目结构:遵循业界最佳实践的目录结构和代码组织方式
|
||||
- 多数据库支持:支持 JPA、Easy Query、Xbatis 三种数据库访问方式
|
||||
- 简化依赖管理和构建流程:基于 Maven 的依赖管理,清晰的构建配置
|
||||
- 支持快速构建和部署微服务:提供完整的微服务基础组件
|
||||
- 泛型支持:通过泛型实现不同类型间的数据转换
|
||||
- 完善的审计机制:自动维护实体的创建时间和修改时间
|
||||
- 强大的查询功能:支持多种条件查询、分页和排序
|
||||
- 灵活的扩展机制:易于定制和扩展的架构设计
|
||||
- 统一的响应格式:标准化的 API 响应结构
|
||||
- 雪花 ID 生成器:支持分布式环境下的唯一 ID 生成
|
||||
- **模块化架构**:采用多模块设计,通用功能与数据库操作分离,便于按需引入
|
||||
- **标准化的项目结构**:遵循业界最佳实践的目录结构和代码组织方式
|
||||
- **多数据库支持**:支持 JPA、Easy Query、Xbatis 三种数据库访问方式,可根据项目需求灵活选择
|
||||
- **简化依赖管理和构建流程**:基于 Maven 的依赖管理,清晰的构建配置,统一的编译插件配置
|
||||
- **支持快速构建和部署微服务**:提供完整的微服务基础组件
|
||||
- **泛型支持**:通过泛型实现不同类型间的数据转换,减少重复代码
|
||||
- **完善的审计机制**(JPA 模块):自动维护实体的创建时间和修改时间,通过 Spring Data JPA 审计功能实现
|
||||
- **强大的查询功能**:支持 21 种查询条件类型,包括空值、相等、模糊匹配、范围、集合、区间等
|
||||
- **分页和排序支持**:内置分页查询和排序功能,支持多字段排序
|
||||
- **灵活的扩展机制**:易于定制和扩展的架构设计,支持自定义查询条件和 Mapper 方法
|
||||
- **统一的响应格式**:标准化的 API 响应结构,提供多种工厂方法简化响应创建
|
||||
- **雪花 ID 生成器**:支持分布式环境下的唯一 ID 生成,基于 Snowflake 算法
|
||||
- **逻辑删除支持**(EQ 和 Xbatis 模块):通过注解实现逻辑删除功能,避免物理删除数据
|
||||
- **查询解析器模板**:采用模板方法模式,统一查询条件解析逻辑,各数据库实现模块提供具体实现
|
||||
- **完整的异常处理**:提供业务异常类,支持类型检查异常处理
|
||||
|
||||
### 1.4 适用场景
|
||||
|
||||
@@ -55,7 +75,9 @@ Spring Boot Service Template 是一个标准化的微服务基础结构模板,
|
||||
- Spring Cloud 2025.1.0
|
||||
- Lombok
|
||||
- MapStruct 1.6.3
|
||||
- MapStruct Plus 1.5.0
|
||||
- Hutool 5.8.43
|
||||
- JSpecify 1.0.0
|
||||
|
||||
#### 2.1.2 数据库技术栈
|
||||
|
||||
@@ -64,12 +86,19 @@ Spring Boot Service Template 是一个标准化的微服务基础结构模板,
|
||||
- Hibernate 7.1.8.Final
|
||||
- QueryDSL 7.1
|
||||
- Fenix 4.0.0
|
||||
- Hibernate JPA Model Generator 7.1.8.Final
|
||||
|
||||
**Easy Query 模块**
|
||||
- Easy Query 3.1.68
|
||||
- SQL Processor 3.1.68
|
||||
|
||||
**Xbatis 模块**
|
||||
- Xbatis 1.9.7-spring-boot4
|
||||
- 基于增强的 MyBatis
|
||||
|
||||
**通用组件**
|
||||
- P6spy Spring Boot Starter 2.0.0(SQL 日志)
|
||||
- Datasource Proxy Spring Boot Starter 2.0.0(数据源代理)
|
||||
|
||||
#### 2.1.3 核心框架
|
||||
- Spring Boot 作为核心框架,提供自动配置和快速开发能力
|
||||
@@ -80,6 +109,7 @@ Spring Boot Service Template 是一个标准化的微服务基础结构模板,
|
||||
- Xbatis 用于增强的 MyBatis 数据访问(Xbatis 模块)
|
||||
- Lombok 减少样板代码,提高开发效率
|
||||
- MapStruct 用于对象映射,简化数据转换
|
||||
- JSpecify 提供空安全注解支持
|
||||
|
||||
#### 2.1.4 数据库技术
|
||||
- H2 Database (测试环境)
|
||||
@@ -120,10 +150,11 @@ Spring Boot Service Template 是一个标准化的微服务基础结构模板,
|
||||
- spring-boot-service-template-database-xbatis:Xbatis 实现模块,基于增强的 MyBatis
|
||||
|
||||
#### 2.2.3 设计模式
|
||||
- 模板方法模式:通过抽象类定义通用操作流程
|
||||
- 策略模式:通过函数式接口实现数据转换策略
|
||||
- 仓储模式:封装数据访问逻辑,提供统一的数据操作接口
|
||||
- 工厂模式:支持多种数据库实现方式的切换
|
||||
- **模板方法模式**:通过 SimpleControllerSupport 和 SimpleServiceSupport 抽象类定义通用操作流程,子类实现 Mapper 方法完成数据转换
|
||||
- **策略模式**:通过 Function 接口实现 saveItemMapper、listItemMapper、detailItemMapper 等数据转换策略
|
||||
- **仓储模式**:通过 SimpleRepository、MybatisBasicMapper 等接口封装数据访问逻辑,提供统一的数据操作接口
|
||||
- **工厂模式**:通过 GlobalResponse 提供多种工厂方法(responseSuccess、responseError、responseListData 等)创建响应对象
|
||||
- **模板方法模式(查询解析)**:通过 QueryParser 抽象类定义查询条件解析流程,各实现模块(JpaQueryParser、EqQueryParser、XBatisQueryParser)实现具体的查询条件构建逻辑
|
||||
|
||||
## 3. 项目结构
|
||||
|
||||
@@ -226,50 +257,67 @@ spring-boot-service-template/
|
||||
定义数据库操作的通用接口和实体类,主要包含:
|
||||
- **controller 包**:
|
||||
- [SimpleController](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/controller/SimpleController.java):定义基础 CRUD 控制器接口
|
||||
- [SaveController](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/controller/SaveController.java):保存操作接口
|
||||
- [QueryController](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/controller/QueryController.java):查询操作接口
|
||||
- [RemoveController](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/controller/RemoveController.java):删除操作接口
|
||||
- [SaveController](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/controller/SaveController.java):保存操作接口,提供 save 方法
|
||||
- [QueryController](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/controller/QueryController.java):查询操作接口,提供 list 和 detail 方法
|
||||
- [RemoveController](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/controller/RemoveController.java):删除操作接口,提供 remove 方法
|
||||
|
||||
- **service 包**:
|
||||
- [SimpleService](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/service/SimpleService.java):定义基础服务接口
|
||||
- [QueryParser](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/service/QueryParser.java):查询条件解析器
|
||||
- [SimpleService](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/service/SimpleService.java):定义基础服务接口,整合 SaveService、QueryService 和 RemoveService
|
||||
- [SaveService](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/service/SaveService.java):保存服务接口,支持单条和批量保存
|
||||
- [QueryService](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/service/QueryService.java):查询服务接口,支持详情查询、列表查询、分页查询和统计
|
||||
- [RemoveService](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/service/RemoveService.java):删除服务接口,支持单条和批量删除
|
||||
- [QueryParser](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/service/QueryParser.java):查询条件解析器抽象类,模板方法模式,支持 21 种查询条件类型
|
||||
|
||||
- **entity 包**:
|
||||
- [GlobalResponse](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/entity/GlobalResponse.java):统一 API 响应封装类
|
||||
- [Query](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/entity/Query.java):查询条件封装类
|
||||
- [Page](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/entity/Page.java):分页结果封装类
|
||||
- [GlobalResponse](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/entity/GlobalResponse.java):统一 API 响应封装类,提供多种工厂方法(responseSuccess、responseError、responseListData、responseDetailData 等)
|
||||
- [Query](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/entity/Query.java):查询条件封装类,包含 Queryable、Sortable 和 Pageable 三个嵌套 record
|
||||
- [Page](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/entity/Page.java):分页结果封装类,包含 items 列表和 total 总数
|
||||
|
||||
- **exception 包**:定义各种业务异常类
|
||||
- **exception 包**:
|
||||
- [IdNotFoundException](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/exception/IdNotFoundException.java):ID 不存在异常
|
||||
- [NotCollectionException](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/exception/NotCollectionException.java):非集合类型异常
|
||||
- [NotComparableException](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/exception/NotComparableException.java):不可比较异常
|
||||
- [NotStringException](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/exception/NotStringException.java):非字符串类型异常
|
||||
|
||||
- **helper 包**:
|
||||
- [SnowflakeHelper](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/helper/SnowflakeHelper.java):雪花 ID 生成器
|
||||
- [SnowflakeHelper](spring-boot-service-template-database/spring-boot-service-template-database-common/src/main/java/com/lanyuanxiaoyao/service/template/database/common/helper/SnowflakeHelper.java):雪花 ID 生成器,提供分布式唯一 ID 生成能力
|
||||
|
||||
#### 3.2.3 database-jpa 模块
|
||||
基于 Spring Data JPA 的实现,主要包含:
|
||||
- [SimpleControllerSupport](spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/main/java/com/lanyuanxiaoyao/service/template/database/jpa/controller/SimpleControllerSupport.java):JPA 控制器实现
|
||||
- [SimpleServiceSupport](spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/main/java/com/lanyuanxiaoyao/service/template/database/jpa/service/SimpleServiceSupport.java):JPA 服务实现
|
||||
- [SimpleRepository](spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/main/java/com/lanyuanxiaoyao/service/template/database/jpa/repository/SimpleRepository.java):JPA 仓储接口,整合多种数据访问功能
|
||||
- [SimpleEntity](spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/main/java/com/lanyuanxiaoyao/service/template/database/jpa/entity/SimpleEntity.java):包含基础字段的实体类
|
||||
- [SimpleControllerSupport](spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/main/java/com/lanyuanxiaoyao/service/template/database/jpa/controller/SimpleControllerSupport.java):JPA 控制器实现,通过泛型支持数据转换
|
||||
- [SimpleServiceSupport](spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/main/java/com/lanyuanxiaoyao/service/template/database/jpa/service/SimpleServiceSupport.java):JPA 服务实现,使用 JPA Criteria API 构建查询,包含内部类 JpaQueryParser
|
||||
- [SimpleRepository](spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/main/java/com/lanyuanxiaoyao/service/template/database/jpa/repository/SimpleRepository.java):JPA 仓储接口,整合 FenixJpaRepository、FenixJpaSpecificationExecutor、ListQueryByExampleExecutor 和 ListQuerydslPredicateExecutor 四种数据访问能力
|
||||
- [SimpleEntity](spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/main/java/com/lanyuanxiaoyao/service/template/database/jpa/entity/SimpleEntity.java):包含 ID、创建时间和修改时间的实体基类
|
||||
- [IdOnlyEntity](spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/main/java/com/lanyuanxiaoyao/service/template/database/jpa/entity/IdOnlyEntity.java):仅包含 ID 的基础实体
|
||||
- [SnowflakeId](spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/main/java/com/lanyuanxiaoyao/service/template/database/jpa/entity/SnowflakeId.java):自定义 Hibernate ID 生成器注解
|
||||
- [SnowflakeIdGenerator](spring-boot-service-template-database/spring-boot-service-template-database-jpa/src/main/java/com/lanyuanxiaoyao/service/template/database/jpa/entity/SnowflakeIdGenerator.java):Hibernate ID 生成器实现类,调用 SnowflakeHelper 生成 ID
|
||||
|
||||
#### 3.2.4 database-eq 模块
|
||||
基于 Easy Query 的实现,主要包含:
|
||||
- [SimpleControllerSupport](spring-boot-service-template-database/spring-boot-service-template-database-eq/src/main/java/com/lanyuanxiaoyao/service/template/database/eq/controller/SimpleControllerSupport.java):Easy Query 控制器实现
|
||||
- [SimpleServiceSupport](spring-boot-service-template-database/spring-boot-service-template-database-eq/src/main/java/com/lanyuanxiaoyao/service/template/database/eq/service/SimpleServiceSupport.java):Easy Query 服务实现
|
||||
- [SimpleControllerSupport](spring-boot-service-template-database/spring-boot-service-template-database-eq/src/main/java/com/lanyuanxiaoyao/service/template/database/eq/controller/SimpleControllerSupport.java):Easy Query 控制器实现,要求实体类实现 ProxyEntityAvailable 接口
|
||||
- [SimpleServiceSupport](spring-boot-service-template-database/spring-boot-service-template-database-eq/src/main/java/com/lanyuanxiaoyao/service/template/database/eq/service/SimpleServiceSupport.java):Easy Query 服务实现,使用 Easy Query API 构建查询,包含内部类 EqQueryParser
|
||||
- [SimpleEntity](spring-boot-service-template-database/spring-boot-service-template-database-eq/src/main/java/com/lanyuanxiaoyao/service/template/database/eq/entity/SimpleEntity.java):包含基础字段的实体类
|
||||
- [LogicDeleteEntity](spring-boot-service-template-database/spring-boot-service-template-database-eq/src/main/java/com/lanyuanxiaoyao/service/template/database/eq/entity/LogicDeleteEntity.java):逻辑删除实体
|
||||
- [LogicDeleteEntity](spring-boot-service-template-database/spring-boot-service-template-database-eq/src/main/java/com/lanyuanxiaoyao/service/template/database/eq/entity/LogicDeleteEntity.java):逻辑删除实体,使用 @LogicDelete 注解
|
||||
|
||||
#### 3.2.5 database-xbatis 模块
|
||||
基于 Xbatis 的实现,主要包含:
|
||||
- [SimpleControllerSupport](spring-boot-service-template-database/spring-boot-service-template-database-xbatis/src/main/java/com/lanyuanxiaoyao/service/template/database/xbatis/controller/SimpleControllerSupport.java):Xbatis 控制器实现
|
||||
- [SimpleServiceSupport](spring-boot-service-template-database/spring-boot-service-template-database-xbatis/src/main/java/com/lanyuanxiaoyao/service/template/database/xbatis/service/SimpleServiceSupport.java):Xbatis 服务实现
|
||||
- [MybatisBasicMapper](spring-boot-service-template-database/spring-boot-service-template-database-xbatis/src/main/java/com/lanyuanxiaoyao/service/template/database/xbatis/mapper/MybatisBasicMapper.java):MyBatis 基础 Mapper
|
||||
- [SimpleServiceSupport](spring-boot-service-template-database/spring-boot-service-template-database-xbatis/src/main/java/com/lanyuanxiaoyao/service/template/database/xbatis/service/SimpleServiceSupport.java):Xbatis 服务实现,使用 Xbatis API 构建查询,包含内部类 XBatisQueryParser
|
||||
- [MybatisBasicMapper](spring-boot-service-template-database/spring-boot-service-template-database-xbatis/src/main/java/com/lanyuanxiaoyao/service/template/database/xbatis/mapper/MybatisBasicMapper.java):Xbatis 基础 Mapper,继承 BasicMapper 提供基本 CRUD 操作
|
||||
- [SimpleEntity](spring-boot-service-template-database/spring-boot-service-template-database-xbatis/src/main/java/com/lanyuanxiaoyao/service/template/database/xbatis/entity/SimpleEntity.java):包含基础字段的实体类
|
||||
- [LogicDeleteEntity](spring-boot-service-template-database/spring-boot-service-template-database-xbatis/src/main/java/com/lanyuanxiaoyao/service/template/database/xbatis/entity/LogicDeleteEntity.java):逻辑删除实体
|
||||
- [LogicDeleteEntity](spring-boot-service-template-database/spring-boot-service-template-database-xbatis/src/main/java/com/lanyuanxiaoyao/service/template/database/xbatis/entity/LogicDeleteEntity.java):逻辑删除实体,使用 @LogicDelete 和 @LogicDeleteTime 注解
|
||||
|
||||
### 3.3 测试模块结构
|
||||
每个数据库实现模块都包含完整的测试用例,通过实际的业务实体(如 Employee、Company、Report)演示如何使用模板。
|
||||
|
||||
**database-common-test 模块**:
|
||||
- [AbstractTestApplication](spring-boot-service-template-database/spring-boot-service-template-database-common-test/src/main/java/com/lanyuanxiaoyao/service/template/database/common/test/AbstractTestApplication.java):测试基类,提供完整的 CRUD 测试流程
|
||||
- 提供测试用的 HTTP 客户端(RestTemplate)
|
||||
- 提供 JSON 序列化工具(ObjectMapper)
|
||||
- 提供随机数据生成方法
|
||||
- 提供 testCrud() 方法,完整测试保存、查询、分页、条件查询、删除等操作
|
||||
- 提供断言方法,验证返回结果
|
||||
|
||||
## 4. 核心功能
|
||||
|
||||
### 4.1 基础 CRUD 操作
|
||||
@@ -330,6 +378,24 @@ spring-boot-service-template/
|
||||
#### 4.3.1 基础实体类
|
||||
定义通用的实体基类,包括 IdOnlyEntity 和 SimpleEntity。
|
||||
|
||||
**IdOnlyEntity**:
|
||||
- 仅包含 ID 字段,使用 @SnowflakeId 注解自动生成雪花 ID
|
||||
- 使用 @EntityListeners(AuditingEntityListener.class) 启用审计功能
|
||||
|
||||
**SimpleEntity**(JPA 模块):
|
||||
- 继承 IdOnlyEntity
|
||||
- 添加 createdTime 字段(@CreatedDate 注解,自动设置创建时间)
|
||||
- 添加 modifiedTime 字段(@LastModifiedDate 注解,自动更新修改时间)
|
||||
|
||||
**SimpleEntity**(EQ/Xbatis 模块):
|
||||
- 继承 IdOnlyEntity
|
||||
- 作为实体基类,需要根据业务需求添加审计字段
|
||||
|
||||
**LogicDeleteEntity**(EQ/Xbatis 模块):
|
||||
- 继承 IdOnlyEntity
|
||||
- 添加 deleted 字段(@LogicDelete 注解,实现逻辑删除)
|
||||
- Xbatis 模块额外添加 deletedTime 字段(@LogicDeleteTime 注解,记录删除时间)
|
||||
|
||||
#### 4.3.2 审计字段
|
||||
包含创建时间 (createdTime) 和修改时间 (modifiedTime) 等审计字段,通过 Spring Data JPA 的审计功能自动维护(JPA 模块)。
|
||||
|
||||
@@ -337,10 +403,30 @@ spring-boot-service-template/
|
||||
支持常见的实体关系映射,如一对一、一对多、多对多等。
|
||||
|
||||
#### 4.3.4 雪花 ID 生成器
|
||||
支持分布式环境下的唯一 ID 生成,通过 SnowflakeIdGenerator 实现。
|
||||
支持分布式环境下的唯一 ID 生成,通过以下方式实现:
|
||||
- **SnowflakeHelper**:雪花 ID 生成工具类,提供 `next()` 方法生成唯一 ID
|
||||
- **@SnowflakeId 注解**(JPA 模块):自定义 Hibernate ID 生成器注解,标记需要使用雪花 ID 的字段
|
||||
- **SnowflakeIdGenerator**(JPA 模块):Hibernate IdentifierGenerator 实现,调用 SnowflakeHelper 生成 ID
|
||||
|
||||
#### 4.3.5 逻辑删除
|
||||
支持逻辑删除功能,通过 LogicDeleteEntity 实现(EQ 和 Xbatis 模块)。
|
||||
支持逻辑删除功能,通过 LogicDeleteEntity 实现(EQ 和 Xbatis 模块):
|
||||
- **EQ 模块**:使用 @LogicDelete 注解标记 deleted 字段
|
||||
- **Xbatis 模块**:使用 @LogicDelete 和 @LogicDeleteTime 注解标记 deleted 和 deletedTime 字段
|
||||
|
||||
#### 4.3.6 查询解析器
|
||||
QueryParser 是查询条件解析的抽象基类,采用模板方法模式,定义了 21 种查询条件的解析方法:
|
||||
- **空值条件**:nullEqual、notNullEqual、empty、notEmpty
|
||||
- **相等条件**:equal、notEqual
|
||||
- **模糊匹配**:like、notLike、contain、notContain
|
||||
- **前后缀匹配**:startWith、notStartWith、endWith、notEndWith
|
||||
- **范围条件**:great、less、greatEqual、lessEqual
|
||||
- **集合条件**:inside、notInside
|
||||
- **区间条件**:between、notBetween
|
||||
|
||||
各数据库实现模块通过继承 QueryParser 实现具体的查询条件构建逻辑:
|
||||
- **JpaQueryParser**(JPA 模块):将查询条件转换为 JPA Criteria API 的 Predicate
|
||||
- **EqQueryParser**(EQ 模块):将查询条件转换为 Easy Query 的代理 API 调用
|
||||
- **XBatisQueryParser**(Xbatis 模块):将查询条件转换为 Xbatis 的 Where 条件
|
||||
|
||||
### 4.4 统一响应格式
|
||||
|
||||
@@ -356,6 +442,19 @@ spring-boot-service-template/
|
||||
- 详情响应
|
||||
- 错误响应
|
||||
|
||||
**GlobalResponse 提供的工厂方法**:
|
||||
- `responseSuccess()`:返回默认成功响应
|
||||
- `responseSuccess(String message)`:返回指定消息的成功响应
|
||||
- `responseSuccess(E data)`:返回带数据的成功响应
|
||||
- `responseSuccess(String message, E data)`:返回指定消息和数据的成功响应
|
||||
- `responseError()`:返回默认错误响应
|
||||
- `responseError(String message)`:返回指定消息的错误响应
|
||||
- `responseMapData(Map data)`:返回 Map 格式数据的成功响应
|
||||
- `responseMapData(String key, Object value)`:返回单个键值对的成功响应
|
||||
- `responseListData()`:返回空列表的成功响应
|
||||
- `responseListData(Iterable data, Integer/Long total)`:返回列表数据的成功响应
|
||||
- `responseDetailData(Object data)`:返回详情数据的成功响应
|
||||
|
||||
## 5. 使用指南
|
||||
|
||||
### 5.1 环境准备
|
||||
@@ -801,16 +900,712 @@ spring:
|
||||
|
||||
#### 7.9.2 Easy Query 模块特点
|
||||
- 基于 Easy Query,提供类型安全的查询构建
|
||||
- 支持逻辑删除
|
||||
- 支持逻辑删除(@LogicDelete 注解)
|
||||
- 实体类需要实现 ProxyEntityAvailable 接口以支持代理查询
|
||||
- 支持雪花 ID 生成
|
||||
- 查询性能优异
|
||||
- 不需要复杂的注解配置
|
||||
- 提供丰富的查询 API(eq、ne、like、gt、lt、in、between 等)
|
||||
|
||||
#### 7.9.3 Xbatis 模块特点
|
||||
- 基于 MyBatis,提供灵活的 SQL 控制
|
||||
- 支持逻辑删除
|
||||
- 基于 Xbatis(增强的 MyBatis),提供灵活的 SQL 控制
|
||||
- 支持逻辑删除(@LogicDelete 和 @LogicDeleteTime 注解)
|
||||
- 支持雪花 ID 生成
|
||||
- 适合复杂查询场景
|
||||
- 需要编写 Mapper XML
|
||||
- MybatisBasicMapper 继承 BasicMapper,提供基本 CRUD 操作,通常不需要编写 Mapper XML
|
||||
- 提供链式查询 API(QueryChain)构建复杂查询条件
|
||||
|
||||
根据您的项目需求和技术栈,选择最适合的数据库实现方式。
|
||||
|
||||
### 7.10 Easy Query 模块集成示例
|
||||
|
||||
#### 7.10.1 创建实体类(EQ 模块)
|
||||
|
||||
创建实体类并继承 SimpleEntity,实现 ProxyEntityAvailable 接口:
|
||||
|
||||
```java
|
||||
import com.easy.query.core.proxy.AbstractProxyEntity;
|
||||
import com.easy.query.core.proxy.ProxyEntityAvailable;
|
||||
import com.lanyuanxiaoyao.service.template.database.eq.entity.SimpleEntity;
|
||||
import com.lanyuanxiaoyao.service.template.database.eq.entity.TableIndex;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@FieldNameConstants
|
||||
@TableIndex // EQ 注解,定义表名
|
||||
public class Employee extends SimpleEntity implements ProxyEntityAvailable<Employee, EmployeeProxy> {
|
||||
private String name;
|
||||
private Integer age;
|
||||
// 需要生成的代理类
|
||||
}
|
||||
```
|
||||
|
||||
#### 7.10.2 创建控制器类(EQ 模块)
|
||||
|
||||
创建控制器类并继承 SimpleControllerSupport,注意额外的泛型参数 PROXY:
|
||||
|
||||
```java
|
||||
import com.easy.query.core.proxy.AbstractProxyEntity;
|
||||
import com.easy.query.core.proxy.ProxyEntityAvailable;
|
||||
import com.lanyuanxiaoyao.service.template.database.eq.controller.SimpleControllerSupport;
|
||||
import com.lanyuanxiaoyao.service.template.database.eq.service.SimpleServiceSupport;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("employee")
|
||||
public class EmployeeController extends SimpleControllerSupport<
|
||||
Employee, // ENTITY
|
||||
EmployeeProxy, // PROXY(需要生成)
|
||||
EmployeeSaveItem, // SAVE_ITEM
|
||||
EmployeeListItem, // LIST_ITEM
|
||||
EmployeeDetailItem> { // DETAIL_ITEM
|
||||
|
||||
public EmployeeController(SimpleServiceSupport<Employee, EmployeeProxy> service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
// 实现三个 Mapper 方法,与 JPA 模块相同
|
||||
@Override
|
||||
protected Function<EmployeeSaveItem, Employee> saveItemMapper() {
|
||||
return item -> {
|
||||
Employee employee = new Employee();
|
||||
employee.setId(item.getId());
|
||||
employee.setName(item.getName());
|
||||
employee.setAge(item.getAge());
|
||||
return employee;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<Employee, EmployeeListItem> listItemMapper() {
|
||||
return employee -> {
|
||||
EmployeeListItem item = new EmployeeListItem();
|
||||
item.setId(employee.getId());
|
||||
item.setName(employee.getName());
|
||||
item.setAge(employee.getAge());
|
||||
return item;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<Employee, EmployeeDetailItem> detailItemMapper() {
|
||||
return employee -> {
|
||||
EmployeeDetailItem item = new EmployeeDetailItem();
|
||||
item.setId(employee.getId());
|
||||
item.setName(employee.getName());
|
||||
item.setAge(employee.getAge());
|
||||
return item;
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 7.11 Xbatis 模块集成示例
|
||||
|
||||
#### 7.11.1 创建实体类(Xbatis 模块)
|
||||
|
||||
创建实体类并继承 SimpleEntity:
|
||||
|
||||
```java
|
||||
import com.lanyuanxiaoyao.service.template.database.xbatis.entity.SimpleEntity;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@FieldNameConstants
|
||||
public class Employee extends SimpleEntity {
|
||||
private String name;
|
||||
private Integer age;
|
||||
}
|
||||
```
|
||||
|
||||
#### 7.11.2 创建 Mapper 接口(Xbatis 模块)
|
||||
|
||||
创建 Mapper 接口并继承 MybatisBasicMapper:
|
||||
|
||||
```java
|
||||
import com.lanyuanxiaoyao.service.template.database.xbatis.mapper.MybatisBasicMapper;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface EmployeeMapper extends MybatisBasicMapper {
|
||||
// MybatisBasicMapper 已经提供了基本 CRUD 操作
|
||||
// 可以添加自定义查询方法
|
||||
}
|
||||
```
|
||||
|
||||
#### 7.11.3 创建服务类(Xbatis 模块)
|
||||
|
||||
创建服务类并继承 SimpleServiceSupport,传入实体类和 Mapper:
|
||||
|
||||
```java
|
||||
import com.lanyuanxiaoyao.service.template.database.xbatis.mapper.MybatisBasicMapper;
|
||||
import com.lanyuanxiaoyao.service.template.database.xbatis.service.SimpleServiceSupport;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class EmployeeService extends SimpleServiceSupport<Employee> {
|
||||
public EmployeeService(MybatisBasicMapper mapper) {
|
||||
super(Employee.class, mapper);
|
||||
}
|
||||
// 自定义业务方法
|
||||
}
|
||||
```
|
||||
|
||||
### 7.12 模块对比总结
|
||||
|
||||
| 特性 | JPA 模块 | EQ 模块 | Xbatis 模块 |
|
||||
|------|----------|---------|------------|
|
||||
| 类型安全查询 | ✅ QueryDSL + Criteria API | ✅ 代理 API | ❌ 链式 API |
|
||||
| 审计功能 | ✅ 自动审计 | ❌ 需手动实现 | ❌ 需手动实现 |
|
||||
| 逻辑删除 | ❌ 仅物理删除 | ✅ @LogicDelete | ✅ @LogicDelete + @LogicDeleteTime |
|
||||
| 实体复杂度 | 低(仅需继承) | 中(需实现 ProxyEntityAvailable) | 低(仅需继承) |
|
||||
| SQL 控制 | 中(JPQL + Criteria) | 低(构建器 API) | 高(支持自定义 SQL) |
|
||||
| 查询灵活性 | 高 | 中 | 高 |
|
||||
| 配置复杂度 | 中(需要编译插件配置) | 中(需要编译插件配置) | 低 |
|
||||
| 适用场景 | 标准 JPA 项目,需要审计 | 需要类型安全查询和逻辑删除 | 复杂查询场景,需要 SQL 控制 |
|
||||
|
||||
根据您的项目需求和技术栈,选择最适合的数据库实现方式。
|
||||
|
||||
### 7.13 完整项目集成示例
|
||||
|
||||
本章节通过一个完整的"用户管理系统"示例,演示如何在独立项目中集成和使用 Spring Boot Service Template。以 JPA 模块为例,展示从依赖引入到运行测试的完整流程。
|
||||
|
||||
#### 7.13.1 项目初始化
|
||||
|
||||
创建一个新的 Spring Boot 项目,项目结构如下:
|
||||
|
||||
```
|
||||
user-management/
|
||||
├── src/main/java/com/example/usermanagement/
|
||||
│ ├── UserManagementApplication.java # 主应用类
|
||||
│ ├── controller/
|
||||
│ │ └── UserController.java # 用户控制器
|
||||
│ ├── service/
|
||||
│ │ └── UserService.java # 用户服务
|
||||
│ ├── repository/
|
||||
│ │ └── UserRepository.java # 用户仓储
|
||||
│ ├── entity/
|
||||
│ │ └── User.java # 用户实体
|
||||
│ └── dto/
|
||||
│ ├── UserSaveItem.java # 保存 DTO
|
||||
│ ├── UserListItem.java # 列表 DTO
|
||||
│ └── UserDetailItem.java # 详情 DTO
|
||||
├── src/main/resources/
|
||||
│ └── application.yml # 应用配置
|
||||
└── pom.xml # Maven 配置
|
||||
```
|
||||
|
||||
#### 7.13.2 配置 pom.xml
|
||||
|
||||
在 pom.xml 中添加依赖和插件配置:
|
||||
|
||||
```xml
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>4.0.0</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
<groupId>com.example</groupId>
|
||||
<artifactId>user-management</artifactId>
|
||||
<version>1.0.0-SNAPSHOT</version>
|
||||
<name>User Management</name>
|
||||
|
||||
<properties>
|
||||
<java.version>17</java.version>
|
||||
<spring-boot-service-template.version>1.1.0-SNAPSHOT</spring-boot-service-template.version>
|
||||
<hibernate.version>7.1.8.Final</hibernate.version>
|
||||
<querydsl.version>7.1</querydsl.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<!-- Spring Boot 基础依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- 数据库相关依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-data-jpa</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Spring Boot Service Template 依赖 -->
|
||||
<dependency>
|
||||
<groupId>com.lanyuanxiaoyao</groupId>
|
||||
<artifactId>spring-boot-service-template-database-jpa</artifactId>
|
||||
<version>${spring-boot-service-template.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- 其他依赖 -->
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.14.1</version>
|
||||
<configuration>
|
||||
<annotationProcessorPaths>
|
||||
<path>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>org.hibernate.orm</groupId>
|
||||
<artifactId>hibernate-jpamodelgen</artifactId>
|
||||
<version>${hibernate.version}</version>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>io.github.openfeign.querydsl</groupId>
|
||||
<artifactId>querydsl-apt</artifactId>
|
||||
<version>${querydsl.version}</version>
|
||||
<classifier>jpa</classifier>
|
||||
</path>
|
||||
<path>
|
||||
<groupId>jakarta.persistence</groupId>
|
||||
<artifactId>jakarta.persistence-api</artifactId>
|
||||
<version>3.2.0</version>
|
||||
</path>
|
||||
</annotationProcessorPaths>
|
||||
<compilerArgs>
|
||||
<arg>-Aquerydsl.entityAccessors=true</arg>
|
||||
<arg>-Aquerydsl.createDefaultVariable=true</arg>
|
||||
</compilerArgs>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</project>
|
||||
```
|
||||
|
||||
#### 7.13.3 配置 application.yml
|
||||
|
||||
在 application.yml 中配置数据源和 Fenix:
|
||||
|
||||
```yaml
|
||||
server:
|
||||
port: 8080
|
||||
|
||||
spring:
|
||||
application:
|
||||
name: user-management
|
||||
|
||||
datasource:
|
||||
url: jdbc:h2:mem:userdb
|
||||
driver-class-name: org.h2.Driver
|
||||
username: sa
|
||||
password:
|
||||
|
||||
h2:
|
||||
console:
|
||||
enabled: true
|
||||
|
||||
jpa:
|
||||
hibernate:
|
||||
ddl-auto: update
|
||||
show-sql: true
|
||||
|
||||
fenix:
|
||||
enabled: true
|
||||
output-format: console
|
||||
|
||||
logging:
|
||||
level:
|
||||
com.example.usermanagement: DEBUG
|
||||
com.lanyuanxiaoyao.service.template: DEBUG
|
||||
```
|
||||
|
||||
#### 7.13.4 创建主应用类
|
||||
|
||||
创建 UserManagementApplication.java,启用 JPA 审计:
|
||||
|
||||
```java
|
||||
package com.example.usermanagement;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
|
||||
@SpringBootApplication
|
||||
@EnableJpaAuditing
|
||||
public class UserManagementApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(UserManagementApplication.class, args);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 7.13.5 创建实体类
|
||||
|
||||
创建 User.java,继承 SimpleEntity:
|
||||
|
||||
```java
|
||||
package com.example.usermanagement.entity;
|
||||
|
||||
import com.lanyuanxiaoyao.service.template.database.jpa.entity.SimpleEntity;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.Table;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@FieldNameConstants
|
||||
@Entity
|
||||
@Table(name = "users")
|
||||
public class User extends SimpleEntity {
|
||||
|
||||
/**
|
||||
* 用户名
|
||||
*/
|
||||
private String username;
|
||||
|
||||
/**
|
||||
* 邮箱
|
||||
*/
|
||||
private String email;
|
||||
|
||||
/**
|
||||
* 年龄
|
||||
*/
|
||||
private Integer age;
|
||||
|
||||
/**
|
||||
* 状态(ACTIVE, INACTIVE)
|
||||
*/
|
||||
private String status;
|
||||
}
|
||||
```
|
||||
|
||||
#### 7.13.6 创建仓储接口
|
||||
|
||||
创建 UserRepository.java,继承 SimpleRepository:
|
||||
|
||||
```java
|
||||
package com.example.usermanagement.repository;
|
||||
|
||||
import com.example.usermanagement.entity.User;
|
||||
import com.lanyuanxiaoyao.service.template.database.jpa.repository.SimpleRepository;
|
||||
import org.springframework.stereotype.Repository;
|
||||
|
||||
@Repository
|
||||
public interface UserRepository extends SimpleRepository<User> {
|
||||
|
||||
// 可以添加自定义查询方法
|
||||
// 例如:User findByEmail(String email);
|
||||
}
|
||||
```
|
||||
|
||||
#### 7.13.7 创建 DTO 类
|
||||
|
||||
创建三个 DTO 类:
|
||||
|
||||
**UserSaveItem.java**:
|
||||
```java
|
||||
package com.example.usermanagement.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@FieldNameConstants
|
||||
public class UserSaveItem {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String email;
|
||||
private Integer age;
|
||||
private String status;
|
||||
}
|
||||
```
|
||||
|
||||
**UserListItem.java**:
|
||||
```java
|
||||
package com.example.usermanagement.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@FieldNameConstants
|
||||
public class UserListItem {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String email;
|
||||
private Integer age;
|
||||
private String status;
|
||||
}
|
||||
```
|
||||
|
||||
**UserDetailItem.java**:
|
||||
```java
|
||||
package com.example.usermanagement.dto;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.experimental.FieldNameConstants;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@FieldNameConstants
|
||||
public class UserDetailItem {
|
||||
private Long id;
|
||||
private String username;
|
||||
private String email;
|
||||
private Integer age;
|
||||
private String status;
|
||||
private LocalDateTime createdTime;
|
||||
private LocalDateTime modifiedTime;
|
||||
}
|
||||
```
|
||||
|
||||
#### 7.13.8 创建服务类
|
||||
|
||||
创建 UserService.java,继承 SimpleServiceSupport:
|
||||
|
||||
```java
|
||||
package com.example.usermanagement.service;
|
||||
|
||||
import com.example.usermanagement.entity.User;
|
||||
import com.example.usermanagement.repository.UserRepository;
|
||||
import com.lanyuanxiaoyao.service.template.database.jpa.service.SimpleServiceSupport;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class UserService extends SimpleServiceSupport<User> {
|
||||
|
||||
public UserService(UserRepository repository) {
|
||||
super(repository);
|
||||
}
|
||||
|
||||
// 可以添加自定义业务方法
|
||||
// 例如:public User findByEmail(String email) { ... }
|
||||
}
|
||||
```
|
||||
|
||||
#### 7.13.9 创建控制器类
|
||||
|
||||
创建 UserController.java,继承 SimpleControllerSupport,实现三个 Mapper 方法:
|
||||
|
||||
```java
|
||||
package com.example.usermanagement.controller;
|
||||
|
||||
import com.example.usermanagement.dto.UserDetailItem;
|
||||
import com.example.usermanagement.dto.UserListItem;
|
||||
import com.example.usermanagement.dto.UserSaveItem;
|
||||
import com.example.usermanagement.entity.User;
|
||||
import com.example.usermanagement.service.UserService;
|
||||
import com.lanyuanxiaoyao.service.template.database.jpa.controller.SimpleControllerSupport;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.function.Function;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/users")
|
||||
public class UserController extends SimpleControllerSupport<User, UserSaveItem, UserListItem, UserDetailItem> {
|
||||
|
||||
public UserController(UserService service) {
|
||||
super(service);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<UserSaveItem, User> saveItemMapper() {
|
||||
return item -> {
|
||||
User user = new User();
|
||||
user.setId(item.getId());
|
||||
user.setUsername(item.getUsername());
|
||||
user.setEmail(item.getEmail());
|
||||
user.setAge(item.getAge());
|
||||
user.setStatus(item.getStatus());
|
||||
return user;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<User, UserListItem> listItemMapper() {
|
||||
return user -> {
|
||||
UserListItem item = new UserListItem();
|
||||
item.setId(user.getId());
|
||||
item.setUsername(user.getUsername());
|
||||
item.setEmail(user.getEmail());
|
||||
item.setAge(user.getAge());
|
||||
item.setStatus(user.getStatus());
|
||||
return item;
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Function<User, UserDetailItem> detailItemMapper() {
|
||||
return user -> {
|
||||
UserDetailItem item = new UserDetailItem();
|
||||
item.setId(user.getId());
|
||||
item.setUsername(user.getUsername());
|
||||
item.setEmail(user.getEmail());
|
||||
item.setAge(user.getAge());
|
||||
item.setStatus(user.getStatus());
|
||||
item.setCreatedTime(user.getCreatedTime());
|
||||
item.setModifiedTime(user.getModifiedTime());
|
||||
return item;
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 7.13.10 运行和测试
|
||||
|
||||
1. **启动应用**:
|
||||
```bash
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
2. **访问 H2 控制台**:
|
||||
```
|
||||
http://localhost:8080/h2-console
|
||||
```
|
||||
|
||||
3. **测试 API**:
|
||||
|
||||
**创建用户**:
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/users/save \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"username": "zhangsan",
|
||||
"email": "zhangsan@example.com",
|
||||
"age": 25,
|
||||
"status": "ACTIVE"
|
||||
}'
|
||||
```
|
||||
|
||||
**查询所有用户**:
|
||||
```bash
|
||||
curl http://localhost:8080/users/list
|
||||
```
|
||||
|
||||
**条件查询用户**:
|
||||
```bash
|
||||
curl -X POST http://localhost:8080/users/list \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"query": {
|
||||
"equal": {
|
||||
"status": "ACTIVE"
|
||||
},
|
||||
"greatEqual": {
|
||||
"age": 20
|
||||
}
|
||||
},
|
||||
"sort": [
|
||||
{
|
||||
"column": "age",
|
||||
"direction": "DESC"
|
||||
}
|
||||
],
|
||||
"page": {
|
||||
"index": 1,
|
||||
"size": 10
|
||||
}
|
||||
}'
|
||||
```
|
||||
|
||||
**查询用户详情**:
|
||||
```bash
|
||||
curl http://localhost:8080/users/detail/{userId}
|
||||
```
|
||||
|
||||
**删除用户**:
|
||||
```bash
|
||||
curl http://localhost:8080/users/remove/{userId}
|
||||
```
|
||||
|
||||
#### 7.13.11 项目结构总结
|
||||
|
||||
通过以上步骤,您已经成功集成了 Spring Boot Service Template,获得了以下功能:
|
||||
|
||||
1. **自动 CRUD 接口**:无需手动编写增删改查的 Controller 方法
|
||||
2. **统一响应格式**:所有 API 返回标准的 GlobalResponse 格式
|
||||
3. **自动审计**:createdTime 和 modifiedTime 自动维护
|
||||
4. **雪花 ID**:自动生成分布式唯一 ID
|
||||
5. **复杂查询**:支持 21 种查询条件类型
|
||||
6. **分页排序**:内置分页和排序功能
|
||||
|
||||
#### 7.13.12 常见问题
|
||||
|
||||
**Q: 如何添加自定义业务逻辑?**
|
||||
A: 在 Service 类中添加自定义方法,然后在 Controller 中注入并使用。
|
||||
|
||||
**Q: 如何修改默认的 URL 路径?**
|
||||
A: 在 Controller 的 @RequestMapping 注解中修改路径。
|
||||
|
||||
**Q: 如何禁用某个接口?**
|
||||
A: 可以不实现对应的 Mapper 方法,或者在 Controller 中使用 @GetMapping/@PostMapping 注解时指定不同的路径。
|
||||
|
||||
**Q: 如何集成其他数据库(MySQL、PostgreSQL)?**
|
||||
A: 在 application.yml 中修改 datasource 配置,并添加对应的数据库驱动依赖。
|
||||
|
||||
**Q: 如何使用 Easy Query 或 Xbatis 替代 JPA?**
|
||||
A: 参考 7.10 和 7.11 节的集成示例,替换对应的依赖和实现类即可。
|
||||
|
||||
## 8. 更新日志
|
||||
|
||||
### 1.1.0-SNAPSHOT (当前版本)
|
||||
|
||||
#### 文档更新
|
||||
- 更新技术栈版本信息,补充 MapStruct Plus 1.5.0、JSpecify 1.0.0 等依赖
|
||||
- 补充通用组件说明(P6spy、Datasource Proxy)
|
||||
- 更新 database-common 模块说明,详细描述各个接口和类的功能
|
||||
- 更新 JPA 模块说明,添加 SnowflakeId 注解和生成器的详细说明
|
||||
- 更新 EQ 模块说明,说明需要实现 ProxyEntityAvailable 接口
|
||||
- 更新 Xbatis 模块说明,说明 MybatisBasicMapper 继承 BasicMapper
|
||||
- 添加测试模块说明,描述 AbstractTestApplication 的测试流程
|
||||
- 更新设计模式部分,补充 QueryParser 的模板方法模式说明
|
||||
- 更新核心特性,补充查询解析器模板、异常处理等特性
|
||||
- 更新数据实体设计,详细说明各种实体类的用途和注解
|
||||
- 添加查询解析器详细说明,描述 QueryParser 抽象类和 21 种查询条件
|
||||
- 更新统一响应格式,补充 GlobalResponse 工厂方法列表
|
||||
- 修正 Easy Query 模块特点,说明需要实现 ProxyEntityAvailable 接口
|
||||
- 修正 Xbatis 模块特点,说明 MybatisBasicMapper 的功能
|
||||
- 添加 EQ 模块集成示例,包含实体类和控制器示例
|
||||
- 添加 Xbatis 模块集成示例,包含实体类、Mapper 和服务示例
|
||||
- 添加模块对比总结,从多个维度对比三种数据库实现方式
|
||||
- 添加当前版本信息到项目概述
|
||||
|
||||
@@ -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.<Employee>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() {
|
||||
|
||||
Reference in New Issue
Block a user