Compare commits
7 Commits
f81217f14a
...
2db84152b5
| Author | SHA1 | Date | |
|---|---|---|---|
| 2db84152b5 | |||
| 28baf5600b | |||
| 015016a2da | |||
| 7b555492ee | |||
| e7fa23a365 | |||
| 2adf4951f7 | |||
| 3692657b64 |
527
README.md
527
README.md
@@ -1,6 +1,6 @@
|
|||||||
# Spring Boot Service Template
|
# Spring Boot Service Template
|
||||||
|
|
||||||
这是一个基于 Spring Boot 的服务模板项目,旨在为开发者提供一个标准化的微服务基础结构,简化新项目的搭建过程,提高开发效率。
|
这是一个基于 Spring Boot 的服务模板项目,采用模块化架构设计,旨在为开发者提供标准化的微服务基础结构,简化新项目的搭建过程,提高开发效率。
|
||||||
|
|
||||||
## 目录
|
## 目录
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
### 1.1 项目简介
|
### 1.1 项目简介
|
||||||
|
|
||||||
Spring Boot Service Template 是一个标准化的微服务基础结构模板,专为 Java 开发者和微服务架构设计者打造。该项目提供了一套完整的 CRUD 操作框架,通过泛型支持不同类型的数据转换,大大减少了重复代码的编写。
|
Spring Boot Service Template 是一个标准化的微服务基础结构模板,专为 Java 开发者和微服务架构设计者打造。该项目采用模块化架构设计,提供了一套完整的 CRUD 操作框架,支持多种数据库访问方式(JPA、Easy Query、Xbatis),通过泛型支持不同类型的数据转换,大大减少了重复代码的编写。
|
||||||
|
|
||||||
该模板内置了完善的实体审计机制,自动维护实体的创建时间和修改时间。同时,提供了强大的查询功能,支持多种条件查询、分页和排序功能。
|
该模板内置了完善的实体审计机制,自动维护实体的创建时间和修改时间。同时,提供了强大的查询功能,支持多种条件查询、分页和排序功能。
|
||||||
|
|
||||||
@@ -26,17 +26,24 @@ Spring Boot Service Template 是一个标准化的微服务基础结构模板,
|
|||||||
|
|
||||||
### 1.3 核心特性
|
### 1.3 核心特性
|
||||||
|
|
||||||
|
- 模块化架构:采用多模块设计,通用功能与数据库操作分离,便于按需引入
|
||||||
- 标准化的项目结构:遵循业界最佳实践的目录结构和代码组织方式
|
- 标准化的项目结构:遵循业界最佳实践的目录结构和代码组织方式
|
||||||
|
- 多数据库支持:支持 JPA、Easy Query、Xbatis 三种数据库访问方式
|
||||||
- 简化依赖管理和构建流程:基于 Maven 的依赖管理,清晰的构建配置
|
- 简化依赖管理和构建流程:基于 Maven 的依赖管理,清晰的构建配置
|
||||||
- 支持快速构建和部署微服务:提供完整的微服务基础组件
|
- 支持快速构建和部署微服务:提供完整的微服务基础组件
|
||||||
- 泛型支持:通过泛型实现不同类型间的数据转换
|
- 泛型支持:通过泛型实现不同类型间的数据转换
|
||||||
- 完善的审计机制:自动维护实体的创建时间和修改时间
|
- 完善的审计机制:自动维护实体的创建时间和修改时间
|
||||||
- 强大的查询功能:支持多种条件查询、分页和排序
|
- 强大的查询功能:支持多种条件查询、分页和排序
|
||||||
- 灵活的扩展机制:易于定制和扩展的架构设计
|
- 灵活的扩展机制:易于定制和扩展的架构设计
|
||||||
|
- 统一的响应格式:标准化的 API 响应结构
|
||||||
|
- 雪花 ID 生成器:支持分布式环境下的唯一 ID 生成
|
||||||
|
|
||||||
### 1.4 适用场景
|
### 1.4 适用场景
|
||||||
|
|
||||||
适用于需要快速搭建 Spring Boot 微服务的项目,特别是那些需要大量 CRUD 操作的业务系统。
|
适用于需要快速搭建 Spring Boot 微服务的项目,特别是那些需要大量 CRUD 操作的业务系统。支持三种数据库访问方式,可根据项目需求灵活选择:
|
||||||
|
- **JPA 模块**:适合传统的 Spring Data JPA 项目,提供完整的 JPA 功能
|
||||||
|
- **Easy Query 模块**:适合需要类型安全查询的项目,提供强大的查询构建能力
|
||||||
|
- **Xbatis 模块**:适合需要 MyBatis 灵活性的项目,提供增强的 MyBatis 功能
|
||||||
|
|
||||||
## 2. 技术架构
|
## 2. 技术架构
|
||||||
|
|
||||||
@@ -44,20 +51,37 @@ Spring Boot Service Template 是一个标准化的微服务基础结构模板,
|
|||||||
|
|
||||||
#### 2.1.1 后端技术栈
|
#### 2.1.1 后端技术栈
|
||||||
- Java 17
|
- Java 17
|
||||||
- Spring Boot 3.4.3
|
- Spring Boot 4.0.0
|
||||||
- Spring Data JPA
|
- Spring Cloud 2025.1.0
|
||||||
- QueryDSL 7.0
|
|
||||||
- Lombok
|
- Lombok
|
||||||
- Fenix Spring Boot Starter 3.1.0
|
- MapStruct 1.6.3
|
||||||
|
- Hutool 5.8.43
|
||||||
|
|
||||||
#### 2.1.2 核心框架
|
#### 2.1.2 数据库技术栈
|
||||||
|
|
||||||
|
**JPA 模块**
|
||||||
|
- Spring Data JPA
|
||||||
|
- Hibernate 7.1.8.Final
|
||||||
|
- QueryDSL 7.1
|
||||||
|
- Fenix 4.0.0
|
||||||
|
|
||||||
|
**Easy Query 模块**
|
||||||
|
- Easy Query 3.1.68
|
||||||
|
|
||||||
|
**Xbatis 模块**
|
||||||
|
- Xbatis 1.9.7-spring-boot4
|
||||||
|
|
||||||
|
#### 2.1.3 核心框架
|
||||||
- Spring Boot 作为核心框架,提供自动配置和快速开发能力
|
- Spring Boot 作为核心框架,提供自动配置和快速开发能力
|
||||||
- Spring Data JPA 用于数据访问,简化数据库操作
|
- Spring Data JPA 用于数据访问,简化数据库操作(JPA 模块)
|
||||||
- QueryDSL 用于类型安全的查询构建,避免运行时错误
|
- QueryDSL 用于类型安全的查询构建,避免运行时错误(JPA 模块)
|
||||||
- Fenix 用于复杂动态查询,提供更灵活的查询能力
|
- Fenix 用于复杂动态查询,提供更灵活的查询能力(JPA 模块)
|
||||||
|
- Easy Query 用于类型安全的查询构建和灵活的数据访问(EQ 模块)
|
||||||
|
- Xbatis 用于增强的 MyBatis 数据访问(Xbatis 模块)
|
||||||
- Lombok 减少样板代码,提高开发效率
|
- Lombok 减少样板代码,提高开发效率
|
||||||
|
- MapStruct 用于对象映射,简化数据转换
|
||||||
|
|
||||||
#### 2.1.3 数据库技术
|
#### 2.1.4 数据库技术
|
||||||
- H2 Database (测试环境)
|
- H2 Database (测试环境)
|
||||||
|
|
||||||
### 2.2 架构设计
|
### 2.2 架构设计
|
||||||
@@ -71,7 +95,7 @@ Spring Boot Service Template 是一个标准化的微服务基础结构模板,
|
|||||||
│ Service Layer │
|
│ Service Layer │
|
||||||
│ (业务逻辑处理,事务管理) │
|
│ (业务逻辑处理,事务管理) │
|
||||||
├─────────────────────────────────────┤
|
├─────────────────────────────────────┤
|
||||||
│ Repository Layer │
|
│ Repository/Mapper Layer │
|
||||||
│ (数据访问,数据库交互) │
|
│ (数据访问,数据库交互) │
|
||||||
├─────────────────────────────────────┤
|
├─────────────────────────────────────┤
|
||||||
│ Entity Layer │
|
│ Entity Layer │
|
||||||
@@ -80,86 +104,189 @@ Spring Boot Service Template 是一个标准化的微服务基础结构模板,
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### 2.2.2 模块划分
|
#### 2.2.2 模块划分
|
||||||
- controller: 控制层,处理 HTTP 请求,包括接口定义和支持类
|
|
||||||
- entity: 实体层,定义数据模型和数据库映射
|
项目采用模块化架构,主要包含以下模块:
|
||||||
- service: 服务层,处理业务逻辑和事务管理
|
|
||||||
- repository: 仓储层,处理数据访问和数据库交互
|
**通用模块**
|
||||||
- helper: 辅助类模块,提供工具类和通用方法
|
- spring-boot-service-template-common:通用工具类模块,提供对象操作等基础功能
|
||||||
|
|
||||||
|
**数据库通用模块**
|
||||||
|
- spring-boot-service-template-database-common:数据库通用功能模块,定义统一的 Controller、Service 接口和实体类
|
||||||
|
- spring-boot-service-template-database-common-test:数据库通用测试模块,提供测试基类和配置
|
||||||
|
|
||||||
|
**数据库实现模块**
|
||||||
|
- spring-boot-service-template-database-jpa:JPA 实现模块,基于 Spring Data JPA
|
||||||
|
- spring-boot-service-template-database-eq:Easy Query 实现模块,基于 Easy Query
|
||||||
|
- spring-boot-service-template-database-xbatis:Xbatis 实现模块,基于增强的 MyBatis
|
||||||
|
|
||||||
#### 2.2.3 设计模式
|
#### 2.2.3 设计模式
|
||||||
- 模板方法模式:通过抽象类定义通用操作流程
|
- 模板方法模式:通过抽象类定义通用操作流程
|
||||||
- 策略模式:通过函数式接口实现数据转换策略
|
- 策略模式:通过函数式接口实现数据转换策略
|
||||||
- 仓储模式:封装数据访问逻辑,提供统一的数据操作接口
|
- 仓储模式:封装数据访问逻辑,提供统一的数据操作接口
|
||||||
|
- 工厂模式:支持多种数据库实现方式的切换
|
||||||
|
|
||||||
## 3. 项目结构
|
## 3. 项目结构
|
||||||
|
|
||||||
### 3.1 目录结构说明
|
### 3.1 目录结构说明
|
||||||
```
|
```
|
||||||
src/
|
spring-boot-service-template/
|
||||||
├── main/
|
├── spring-boot-service-template-common/ # 通用工具类模块
|
||||||
│ └── java/
|
│ └── src/main/java/com/lanyuanxiaoyao/service/template/common/
|
||||||
│ └── com/lanyuanxiaoyao/service/template/
|
│ └── helper/
|
||||||
│ ├── controller/
|
│ └── ObjectHelper.java # 对象工具类
|
||||||
│ ├── entity/
|
│
|
||||||
│ ├── helper/
|
├── spring-boot-service-template-database/ # 数据库模块目录
|
||||||
│ ├── repository/
|
│ ├── spring-boot-service-template-database-common/ # 数据库通用模块
|
||||||
│ └── service/
|
│ │ └── src/main/java/com/lanyuanxiaoyao/service/template/database/common/
|
||||||
└── test/
|
│ │ ├── controller/ # 控制器接口定义
|
||||||
└── java/
|
│ │ │ ├── SimpleController.java
|
||||||
└── com/lanyuanxiaoyao/service/template/
|
│ │ │ ├── SaveController.java
|
||||||
├── controller/
|
│ │ │ ├── QueryController.java
|
||||||
├── entity/
|
│ │ │ └── RemoveController.java
|
||||||
├── repository/
|
│ │ ├── service/ # 服务接口定义
|
||||||
└── service/
|
│ │ │ ├── SimpleService.java
|
||||||
|
│ │ │ ├── SaveService.java
|
||||||
|
│ │ │ ├── QueryService.java
|
||||||
|
│ │ │ ├── RemoveService.java
|
||||||
|
│ │ │ └── QueryParser.java # 查询解析器
|
||||||
|
│ │ ├── entity/ # 通用实体类
|
||||||
|
│ │ │ ├── GlobalResponse.java # 统一响应格式
|
||||||
|
│ │ │ ├── Query.java # 查询条件封装
|
||||||
|
│ │ │ └── Page.java # 分页结果封装
|
||||||
|
│ │ ├── exception/ # 异常定义
|
||||||
|
│ │ │ ├── IdNotFoundException.java
|
||||||
|
│ │ │ ├── NotCollectionException.java
|
||||||
|
│ │ │ ├── NotComparableException.java
|
||||||
|
│ │ │ └── NotStringException.java
|
||||||
|
│ │ └── helper/ # 辅助类
|
||||||
|
│ │ └── SnowflakeHelper.java # 雪花ID生成器
|
||||||
|
│ │
|
||||||
|
│ ├── spring-boot-service-template-database-common-test/ # 数据库通用测试模块
|
||||||
|
│ │ └── src/main/java/com/lanyuanxiaoyao/service/template/database/common/test/
|
||||||
|
│ │ └── AbstractTestApplication.java # 测试应用基类
|
||||||
|
│ │
|
||||||
|
│ ├── spring-boot-service-template-database-jpa/ # JPA 实现模块
|
||||||
|
│ │ ├── src/main/java/com/lanyuanxiaoyao/service/template/database/jpa/
|
||||||
|
│ │ │ ├── controller/
|
||||||
|
│ │ │ │ └── SimpleControllerSupport.java # JPA 控制器实现
|
||||||
|
│ │ │ ├── service/
|
||||||
|
│ │ │ │ └── SimpleServiceSupport.java # JPA 服务实现
|
||||||
|
│ │ │ ├── repository/
|
||||||
|
│ │ │ │ └── SimpleRepository.java # JPA 仓储接口
|
||||||
|
│ │ │ ├── entity/
|
||||||
|
│ │ │ │ ├── IdOnlyEntity.java # 仅包含 ID 的实体
|
||||||
|
│ │ │ │ ├── SimpleEntity.java # 包含基础字段的实体
|
||||||
|
│ │ │ │ ├── SnowflakeId.java # 雪花 ID 注解
|
||||||
|
│ │ │ │ └── SnowflakeIdGenerator.java # 雪花 ID 生成器
|
||||||
|
│ │ │ └── helper/
|
||||||
|
│ │ │ └── DatabaseHelper.java # 数据库辅助类
|
||||||
|
│ │ └── src/test/java/... # 测试用例
|
||||||
|
│ │
|
||||||
|
│ ├── spring-boot-service-template-database-eq/ # Easy Query 实现模块
|
||||||
|
│ │ ├── src/main/java/com/lanyuanxiaoyao/service/template/database/eq/
|
||||||
|
│ │ │ ├── controller/
|
||||||
|
│ │ │ │ └── SimpleControllerSupport.java # EQ 控制器实现
|
||||||
|
│ │ │ ├── service/
|
||||||
|
│ │ │ │ └── SimpleServiceSupport.java # EQ 服务实现
|
||||||
|
│ │ │ └── entity/
|
||||||
|
│ │ │ ├── IdOnlyEntity.java # 仅包含 ID 的实体
|
||||||
|
│ │ │ ├── LogicDeleteEntity.java # 逻辑删除实体
|
||||||
|
│ │ │ ├── SimpleEntity.java # 包含基础字段的实体
|
||||||
|
│ │ │ └── SnowflakeIdGenerator.java # 雪花 ID 生成器
|
||||||
|
│ │ └── src/test/java/... # 测试用例
|
||||||
|
│ │
|
||||||
|
│ └── spring-boot-service-template-database-xbatis/ # Xbatis 实现模块
|
||||||
|
│ ├── src/main/java/com/lanyuanxiaoyao/service/template/database/xbatis/
|
||||||
|
│ │ ├── controller/
|
||||||
|
│ │ │ └── SimpleControllerSupport.java # Xbatis 控制器实现
|
||||||
|
│ │ ├── service/
|
||||||
|
│ │ │ └── SimpleServiceSupport.java # Xbatis 服务实现
|
||||||
|
│ │ ├── mapper/
|
||||||
|
│ │ │ └── MybatisBasicMapper.java # MyBatis 基础 Mapper
|
||||||
|
│ │ ├── entity/
|
||||||
|
│ │ │ ├── IdOnlyEntity.java # 仅包含 ID 的实体
|
||||||
|
│ │ │ ├── LogicDeleteEntity.java # 逻辑删除实体
|
||||||
|
│ │ │ ├── SimpleEntity.java # 包含基础字段的实体
|
||||||
|
│ │ │ └── SnowflakeIdGenerator.java # 雪花 ID 生成器
|
||||||
|
│ │ └── configuration/
|
||||||
|
│ │ └── MybatisConfiguration.java # MyBatis 配置
|
||||||
|
│ └── src/test/java/... # 测试用例
|
||||||
|
│
|
||||||
|
├── pom.xml # 父 POM 文件
|
||||||
|
└── README.md # 项目文档
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.2 核心模块介绍
|
### 3.2 核心模块介绍
|
||||||
|
|
||||||
#### 3.2.1 controller模块
|
#### 3.2.1 common 模块
|
||||||
提供基础的 CRUD 操作控制器接口和支持类。主要包含:
|
提供通用的工具类,主要包含:
|
||||||
- [SimpleController](src/main/java/com/lanyuanxiaoyao/service/template/controller/SimpleController.java):定义基础 CRUD 接口
|
- [ObjectHelper](spring-boot-service-template-common/src/main/java/com/lanyuanxiaoyao/service/template/common/helper/ObjectHelper.java):对象工具类,提供对象空值判断、类型判断等常用功能
|
||||||
- [SimpleControllerSupport](src/main/java/com/lanyuanxiaoyao/service/template/controller/SimpleControllerSupport.java):实现基础 CRUD 功能
|
|
||||||
- [Query](src/main/java/com/lanyuanxiaoyao/service/template/controller/Query.java):查询条件封装类
|
|
||||||
- 其他辅助接口如 SaveController、ListController、DetailController、RemoveController
|
|
||||||
|
|
||||||
#### 3.2.2 entity模块
|
#### 3.2.2 database-common 模块
|
||||||
定义基础实体类,包含审计字段。主要包含:
|
定义数据库操作的通用接口和实体类,主要包含:
|
||||||
- [IdOnlyEntity](src/main/java/com/lanyuanxiaoyao/service/template/entity/IdOnlyEntity.java):仅包含 ID 的基础实体
|
- **controller 包**:
|
||||||
- [SimpleEntity](src/main/java/com/lanyuanxiaoyao/service/template/entity/SimpleEntity.java):包含基础字段的实体类,继承自 IdOnlyEntity
|
- [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):删除操作接口
|
||||||
|
|
||||||
#### 3.2.3 service模块
|
- **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):定义基础服务接口
|
||||||
- [SimpleService](src/main/java/com/lanyuanxiaoyao/service/template/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):查询条件解析器
|
||||||
- [SimpleServiceSupport](src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleServiceSupport.java):实现基础服务功能
|
|
||||||
|
|
||||||
#### 3.2.4 repository模块
|
- **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 响应封装类
|
||||||
- [SimpleRepository](src/main/java/com/lanyuanxiaoyao/service/template/repository/SimpleRepository.java):基础仓储接口
|
- [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):分页结果封装类
|
||||||
|
|
||||||
#### 3.2.5 helper模块
|
- **exception 包**:定义各种业务异常类
|
||||||
提供辅助工具类。主要包含:
|
|
||||||
- [ObjectHelper](src/main/java/com/lanyuanxiaoyao/service/template/helper/ObjectHelper.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 生成器
|
||||||
|
|
||||||
|
#### 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):包含基础字段的实体类
|
||||||
|
- [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 的基础实体
|
||||||
|
|
||||||
|
#### 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 服务实现
|
||||||
|
- [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):逻辑删除实体
|
||||||
|
|
||||||
|
#### 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
|
||||||
|
- [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):逻辑删除实体
|
||||||
|
|
||||||
### 3.3 测试模块结构
|
### 3.3 测试模块结构
|
||||||
测试模块包含完整的测试用例,覆盖 controller、entity、repository 和 service 各层。通过实际的业务实体(如 Employee、Company、Report)演示如何使用模板。
|
每个数据库实现模块都包含完整的测试用例,通过实际的业务实体(如 Employee、Company、Report)演示如何使用模板。
|
||||||
|
|
||||||
## 4. 核心功能
|
## 4. 核心功能
|
||||||
|
|
||||||
### 4.1 基础CRUD操作
|
### 4.1 基础 CRUD 操作
|
||||||
|
|
||||||
#### 4.1.1 创建(Create)
|
#### 4.1.1 创建 (Create)
|
||||||
支持通过 POST 请求创建实体对象。通过 save 接口实现,接收 SAVE_ITEM 类型的参数,通过 Mapper 转换为实体对象后保存。
|
支持通过 POST 请求创建实体对象。通过 save 接口实现,接收 SAVE_ITEM 类型的参数,通过 Mapper 转换为实体对象后保存。
|
||||||
|
|
||||||
#### 4.1.2 查询(Retrieve)
|
#### 4.1.2 查询 (Retrieve)
|
||||||
支持多种查询方式:
|
支持多种查询方式:
|
||||||
- 列表查询:获取所有实体对象
|
- 列表查询:获取所有实体对象
|
||||||
- 条件查询:根据指定条件查询实体对象
|
- 条件查询:根据指定条件查询实体对象
|
||||||
- 详情查询:根据 ID 获取单个实体对象
|
- 详情查询:根据 ID 获取单个实体对象
|
||||||
|
|
||||||
#### 4.1.3 更新(Update)
|
#### 4.1.3 更新 (Update)
|
||||||
支持通过 POST 请求更新实体对象。通过 save 接口实现,当传入包含 ID 的对象时执行更新操作。
|
支持通过 POST 请求更新实体对象。通过 save 接口实现,当传入包含 ID 的对象时执行更新操作。
|
||||||
|
|
||||||
#### 4.1.4 删除(Delete)
|
#### 4.1.4 删除 (Delete)
|
||||||
支持通过 GET 请求删除实体对象。通过 remove 接口实现,根据 ID 删除指定实体。
|
支持通过 GET 请求删除实体对象。通过 remove 接口实现,根据 ID 删除指定实体。
|
||||||
|
|
||||||
### 4.2 查询功能详解
|
### 4.2 查询功能详解
|
||||||
@@ -169,20 +296,26 @@ src/
|
|||||||
|
|
||||||
#### 4.2.2 条件查询
|
#### 4.2.2 条件查询
|
||||||
支持基于多种条件的复杂查询,通过 list 接口实现,支持以下查询条件:
|
支持基于多种条件的复杂查询,通过 list 接口实现,支持以下查询条件:
|
||||||
- nullEqual: 字段值为null的条件
|
- nullEqual: 字段值为 null 的条件
|
||||||
- notNullEqual: 字段值不为null的条件
|
- notNullEqual: 字段值不为 null 的条件
|
||||||
- empty: 字段值为空的条件
|
- empty: 字段值为空的条件
|
||||||
- notEmpty: 字段值不为空的条件
|
- notEmpty: 字段值不为空的条件
|
||||||
- equal: 字段值相等的条件
|
- equal: 字段值相等的条件
|
||||||
- notEqual: 字段值不相等的条件
|
- notEqual: 字段值不相等的条件
|
||||||
- like: 字段值模糊匹配的条件
|
- like: 字段值模糊匹配的条件
|
||||||
- notLike: 字段值不模糊匹配的条件
|
- notLike: 字段值不模糊匹配的条件
|
||||||
|
- contain: 字段包含指定字符串的条件
|
||||||
|
- notContain: 字段不包含指定字符串的条件
|
||||||
|
- startWith: 字段以指定字符串开头的条件
|
||||||
|
- notStartWith: 字段不以指定字符串开头的条件
|
||||||
|
- endWith: 字段以指定字符串结尾的条件
|
||||||
|
- notEndWith: 字段不以指定字符串结尾的条件
|
||||||
- great: 字段值大于的条件
|
- great: 字段值大于的条件
|
||||||
- less: 字段值小于的条件
|
- less: 字段值小于的条件
|
||||||
- greatEqual: 字段值大于等于的条件
|
- greatEqual: 字段值大于等于的条件
|
||||||
- lessEqual: 字段值小于等于的条件
|
- lessEqual: 字段值小于等于的条件
|
||||||
- in: 字段值在指定范围内的条件
|
- inside: 字段值在指定范围内的条件
|
||||||
- notIn: 字段值不在指定范围内的条件
|
- notInside: 字段值不在指定范围内的条件
|
||||||
- between: 字段值在指定区间内的条件
|
- between: 字段值在指定区间内的条件
|
||||||
- notBetween: 字段值不在指定区间内的条件
|
- notBetween: 字段值不在指定区间内的条件
|
||||||
|
|
||||||
@@ -198,19 +331,39 @@ src/
|
|||||||
定义通用的实体基类,包括 IdOnlyEntity 和 SimpleEntity。
|
定义通用的实体基类,包括 IdOnlyEntity 和 SimpleEntity。
|
||||||
|
|
||||||
#### 4.3.2 审计字段
|
#### 4.3.2 审计字段
|
||||||
包含创建时间(createdTime)和修改时间(modifiedTime)等审计字段,通过 Spring Data JPA 的审计功能自动维护。
|
包含创建时间 (createdTime) 和修改时间 (modifiedTime) 等审计字段,通过 Spring Data JPA 的审计功能自动维护(JPA 模块)。
|
||||||
|
|
||||||
#### 4.3.3 实体关系
|
#### 4.3.3 实体关系
|
||||||
支持常见的实体关系映射,如一对一、一对多、多对多等。
|
支持常见的实体关系映射,如一对一、一对多、多对多等。
|
||||||
|
|
||||||
|
#### 4.3.4 雪花 ID 生成器
|
||||||
|
支持分布式环境下的唯一 ID 生成,通过 SnowflakeIdGenerator 实现。
|
||||||
|
|
||||||
|
#### 4.3.5 逻辑删除
|
||||||
|
支持逻辑删除功能,通过 LogicDeleteEntity 实现(EQ 和 Xbatis 模块)。
|
||||||
|
|
||||||
|
### 4.4 统一响应格式
|
||||||
|
|
||||||
|
提供统一的 API 响应格式,包含以下字段:
|
||||||
|
- status: 响应状态码(0 表示成功,500 表示错误)
|
||||||
|
- message: 响应消息
|
||||||
|
- data: 响应数据
|
||||||
|
|
||||||
|
支持多种响应类型:
|
||||||
|
- 成功响应(无数据)
|
||||||
|
- 成功响应(带数据)
|
||||||
|
- 分页列表响应(包含 items 和 total)
|
||||||
|
- 详情响应
|
||||||
|
- 错误响应
|
||||||
|
|
||||||
## 5. 使用指南
|
## 5. 使用指南
|
||||||
|
|
||||||
### 5.1 环境准备
|
### 5.1 环境准备
|
||||||
|
|
||||||
#### 5.1.1 JDK安装
|
#### 5.1.1 JDK 安装
|
||||||
需要安装 JDK 17 或更高版本。
|
需要安装 JDK 17 或更高版本。
|
||||||
|
|
||||||
#### 5.1.2 Maven配置
|
#### 5.1.2 Maven 配置
|
||||||
需要配置 Maven 3.x 环境。
|
需要配置 Maven 3.x 环境。
|
||||||
|
|
||||||
### 5.2 项目构建
|
### 5.2 项目构建
|
||||||
@@ -228,7 +381,7 @@ src/
|
|||||||
|
|
||||||
#### 5.3.2 生产部署
|
#### 5.3.2 生产部署
|
||||||
可通过生成的 JAR 文件直接运行或部署到服务器,使用命令:
|
可通过生成的 JAR 文件直接运行或部署到服务器,使用命令:
|
||||||
`java -jar target/spring-boot-service-template-1.0-SNAPSHOT.jar`
|
`java -jar target/spring-boot-service-template-1.1.0-SNAPSHOT.jar`
|
||||||
|
|
||||||
## 6. 开发规范
|
## 6. 开发规范
|
||||||
|
|
||||||
@@ -252,21 +405,60 @@ RESTful API 设计规范,统一的响应格式。
|
|||||||
|
|
||||||
通过 Maven 依赖引入(推荐)
|
通过 Maven 依赖引入(推荐)
|
||||||
|
|
||||||
### 7.2 Maven 依赖引入方式
|
### 7.2 选择数据库实现方式
|
||||||
|
|
||||||
#### 7.2.1 添加依赖
|
项目提供三种数据库实现方式,根据您的需求选择:
|
||||||
|
|
||||||
|
- **JPA 模块**:适合传统的 Spring Data JPA 项目
|
||||||
|
- **Easy Query 模块**:适合需要类型安全查询的项目
|
||||||
|
- **Xbatis 模块**:适合需要 MyBatis 灵活性的项目
|
||||||
|
|
||||||
|
### 7.3 Maven 依赖引入方式
|
||||||
|
|
||||||
|
#### 7.3.1 添加基础依赖
|
||||||
|
|
||||||
在您的项目 pom.xml 文件中添加以下依赖:
|
在您的项目 pom.xml 文件中添加以下依赖:
|
||||||
|
|
||||||
```xml
|
```xml
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.lanyuanxiaoyao</groupId>
|
<groupId>com.lanyuanxiaoyao</groupId>
|
||||||
<artifactId>spring-boot-service-template</artifactId>
|
<artifactId>spring-boot-service-template-common</artifactId>
|
||||||
<version>1.0.0-SNAPSHOT</version>
|
<version>1.1.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 7.2.2 配置依赖管理
|
#### 7.3.2 添加数据库实现依赖
|
||||||
|
|
||||||
|
根据您选择的数据库实现方式,添加对应的依赖:
|
||||||
|
|
||||||
|
**JPA 实现**
|
||||||
|
```xml
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.lanyuanxiaoyao</groupId>
|
||||||
|
<artifactId>spring-boot-service-template-database-jpa</artifactId>
|
||||||
|
<version>1.1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Easy Query 实现**
|
||||||
|
```xml
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.lanyuanxiaoyao</groupId>
|
||||||
|
<artifactId>spring-boot-service-template-database-eq</artifactId>
|
||||||
|
<version>1.1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Xbatis 实现**
|
||||||
|
```xml
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.lanyuanxiaoyao</groupId>
|
||||||
|
<artifactId>spring-boot-service-template-database-xbatis</artifactId>
|
||||||
|
<version>1.1.0-SNAPSHOT</version>
|
||||||
|
</dependency>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 7.3.3 配置依赖管理
|
||||||
|
|
||||||
确保您的项目中包含以下依赖管理配置:
|
确保您的项目中包含以下依赖管理配置:
|
||||||
|
|
||||||
@@ -277,14 +469,14 @@ RESTful API 设计规范,统一的响应格式。
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-dependencies</artifactId>
|
<artifactId>spring-boot-dependencies</artifactId>
|
||||||
<version>${spring-boot.version}</version>
|
<version>4.0.0</version>
|
||||||
<type>pom</type>
|
<type>pom</type>
|
||||||
<scope>import</scope>
|
<scope>import</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.cloud</groupId>
|
<groupId>org.springframework.cloud</groupId>
|
||||||
<artifactId>spring-cloud-dependencies</artifactId>
|
<artifactId>spring-cloud-dependencies</artifactId>
|
||||||
<version>${spring-cloud.version}</version>
|
<version>2025.1.0</version>
|
||||||
<type>pom</type>
|
<type>pom</type>
|
||||||
<scope>import</scope>
|
<scope>import</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
@@ -292,33 +484,90 @@ RESTful API 设计规范,统一的响应格式。
|
|||||||
</dependencyManagement>
|
</dependencyManagement>
|
||||||
```
|
```
|
||||||
|
|
||||||
#### 7.2.3 配置编译插件
|
#### 7.3.4 配置编译插件
|
||||||
|
|
||||||
确保您的项目中包含以下编译插件配置:
|
确保您的项目中包含以下编译插件配置:
|
||||||
|
|
||||||
|
**JPA 模块编译插件**
|
||||||
```xml
|
```xml
|
||||||
<build>
|
<build>
|
||||||
<plugins>
|
<plugins>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
<version>3.14.0</version>
|
<version>3.14.1</version>
|
||||||
<configuration>
|
<configuration>
|
||||||
<annotationProcessorPaths>
|
<annotationProcessorPaths>
|
||||||
<path>
|
<path>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<version>1.18.36</version>
|
|
||||||
</path>
|
</path>
|
||||||
<path>
|
<path>
|
||||||
<groupId>org.hibernate</groupId>
|
<groupId>org.hibernate</groupId>
|
||||||
<artifactId>hibernate-jpamodelgen</artifactId>
|
<artifactId>hibernate-jpamodelgen</artifactId>
|
||||||
<version>6.6.3.Final</version>
|
<version>7.1.8.Final</version>
|
||||||
</path>
|
</path>
|
||||||
<path>
|
<path>
|
||||||
<groupId>io.github.openfeign.querydsl</groupId>
|
<groupId>io.github.openfeign.querydsl</groupId>
|
||||||
<artifactId>querydsl-jpa</artifactId>
|
<artifactId>querydsl-apt</artifactId>
|
||||||
<version>7.0</version>
|
<version>7.1</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>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Easy Query 模块编译插件**
|
||||||
|
```xml
|
||||||
|
<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>com.easy-query</groupId>
|
||||||
|
<artifactId>sql-processor</artifactId>
|
||||||
|
<version>3.1.68</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Xbatis 模块编译插件**
|
||||||
|
```xml
|
||||||
|
<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>
|
||||||
</annotationProcessorPaths>
|
</annotationProcessorPaths>
|
||||||
</configuration>
|
</configuration>
|
||||||
@@ -329,13 +578,23 @@ RESTful API 设计规范,统一的响应格式。
|
|||||||
|
|
||||||
### 7.4 创建业务模块
|
### 7.4 创建业务模块
|
||||||
|
|
||||||
创建业务模块的步骤如下:
|
创建业务模块的步骤如下(以 JPA 模块为例):
|
||||||
|
|
||||||
#### 7.4.1 创建实体类
|
#### 7.4.1 创建实体类
|
||||||
|
|
||||||
创建实体类并继承 [SimpleEntity](src/main/java/com/lanyuanxiaoyao/service/template/entity/SimpleEntity.java):
|
创建实体类并继承 SimpleEntity:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
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
|
@Entity
|
||||||
@Table(name = "employee")
|
@Table(name = "employee")
|
||||||
public class Employee extends SimpleEntity {
|
public class Employee extends SimpleEntity {
|
||||||
@@ -347,9 +606,12 @@ public class Employee extends SimpleEntity {
|
|||||||
|
|
||||||
#### 7.4.2 创建仓储接口
|
#### 7.4.2 创建仓储接口
|
||||||
|
|
||||||
创建仓储接口并继承 [SimpleRepository](src/main/java/com/lanyuanxiaoyao/service/template/repository/SimpleRepository.java):
|
创建仓储接口并继承 SimpleRepository:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
import com.lanyuanxiaoyao.service.template.database.jpa.repository.SimpleRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
@Repository
|
@Repository
|
||||||
public interface EmployeeRepository extends SimpleRepository<Employee> {
|
public interface EmployeeRepository extends SimpleRepository<Employee> {
|
||||||
// 自定义查询方法
|
// 自定义查询方法
|
||||||
@@ -358,9 +620,12 @@ public interface EmployeeRepository extends SimpleRepository<Employee> {
|
|||||||
|
|
||||||
#### 7.4.3 创建服务类
|
#### 7.4.3 创建服务类
|
||||||
|
|
||||||
创建服务类并继承 [SimpleServiceSupport](src/main/java/com/lanyuanxiaoyao/service/template/service/SimpleServiceSupport.java):
|
创建服务类并继承 SimpleServiceSupport:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
import com.lanyuanxiaoyao.service.template.database.jpa.service.SimpleServiceSupport;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class EmployeeService extends SimpleServiceSupport<Employee> {
|
public class EmployeeService extends SimpleServiceSupport<Employee> {
|
||||||
public EmployeeService(EmployeeRepository repository) {
|
public EmployeeService(EmployeeRepository repository) {
|
||||||
@@ -372,9 +637,13 @@ public class EmployeeService extends SimpleServiceSupport<Employee> {
|
|||||||
|
|
||||||
#### 7.4.4 创建控制器类
|
#### 7.4.4 创建控制器类
|
||||||
|
|
||||||
创建控制器类并继承 [SimpleControllerSupport](src/main/java/com/lanyuanxiaoyao/service/template/controller/SimpleControllerSupport.java):
|
创建控制器类并继承 SimpleControllerSupport:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
import com.lanyuanxiaoyao.service.template.database.jpa.controller.SimpleControllerSupport;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("employee")
|
@RequestMapping("employee")
|
||||||
public class EmployeeController extends SimpleControllerSupport<Employee, EmployeeSaveItem, EmployeeListItem, EmployeeDetailItem> {
|
public class EmployeeController extends SimpleControllerSupport<Employee, EmployeeSaveItem, EmployeeListItem, EmployeeDetailItem> {
|
||||||
@@ -385,16 +654,39 @@ public class EmployeeController extends SimpleControllerSupport<Employee, Employ
|
|||||||
@Override
|
@Override
|
||||||
protected Function<EmployeeSaveItem, Employee> saveItemMapper() {
|
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
|
@Override
|
||||||
protected Function<Employee, EmployeeListItem> listItemMapper() {
|
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
|
@Override
|
||||||
protected Function<Employee, EmployeeDetailItem> detailItemMapper() {
|
protected Function<Employee, EmployeeDetailItem> detailItemMapper() {
|
||||||
// 实现详情项转换逻辑
|
// 实现详情项转换逻辑
|
||||||
|
return employee -> {
|
||||||
|
EmployeeDetailItem item = new EmployeeDetailItem();
|
||||||
|
item.setId(employee.getId());
|
||||||
|
item.setName(employee.getName());
|
||||||
|
item.setAge(employee.getAge());
|
||||||
|
item.setCreatedTime(employee.getCreatedTime());
|
||||||
|
item.setModifiedTime(employee.getModifiedTime());
|
||||||
|
return item;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@@ -405,6 +697,13 @@ public class EmployeeController extends SimpleControllerSupport<Employee, Employ
|
|||||||
|
|
||||||
```java
|
```java
|
||||||
// 保存项
|
// 保存项
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import lombok.experimental.FieldNameConstants;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@FieldNameConstants
|
||||||
public class EmployeeSaveItem {
|
public class EmployeeSaveItem {
|
||||||
private Long id;
|
private Long id;
|
||||||
private String name;
|
private String name;
|
||||||
@@ -413,6 +712,9 @@ public class EmployeeSaveItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 列表项
|
// 列表项
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@FieldNameConstants
|
||||||
public class EmployeeListItem {
|
public class EmployeeListItem {
|
||||||
private Long id;
|
private Long id;
|
||||||
private String name;
|
private String name;
|
||||||
@@ -421,6 +723,9 @@ public class EmployeeListItem {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 详情项
|
// 详情项
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
@FieldNameConstants
|
||||||
public class EmployeeDetailItem {
|
public class EmployeeDetailItem {
|
||||||
private Long id;
|
private Long id;
|
||||||
private String name;
|
private String name;
|
||||||
@@ -431,11 +736,15 @@ public class EmployeeDetailItem {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7.5 配置启用 JPA 审计
|
### 7.5 配置启用 JPA 审计(JPA 模块)
|
||||||
|
|
||||||
在您的主应用类上添加 [@EnableJpaAuditing](https://docs.spring.io/spring-data/jpa/docs/current/api/org/springframework/data/jpa/repository/config/EnableJpaAuditing.html) 注解以启用 JPA 审计功能:
|
在您的主应用类上添加 @EnableJpaAuditing 注解以启用 JPA 审计功能:
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||||
|
|
||||||
@SpringBootApplication
|
@SpringBootApplication
|
||||||
@EnableJpaAuditing
|
@EnableJpaAuditing
|
||||||
public class YourApplication {
|
public class YourApplication {
|
||||||
@@ -445,7 +754,7 @@ public class YourApplication {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7.6 配置 Fenix
|
### 7.6 配置 Fenix(JPA 模块)
|
||||||
|
|
||||||
在您的 application.yml 或 application.properties 文件中添加 Fenix 配置:
|
在您的 application.yml 或 application.properties 文件中添加 Fenix 配置:
|
||||||
|
|
||||||
@@ -457,7 +766,20 @@ fenix:
|
|||||||
output-format: console
|
output-format: console
|
||||||
```
|
```
|
||||||
|
|
||||||
### 7.7 测试集成效果
|
### 7.7 配置数据源
|
||||||
|
|
||||||
|
在您的 application.yml 或 application.properties 文件中配置数据源:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
spring:
|
||||||
|
datasource:
|
||||||
|
url: jdbc:mysql://localhost:3306/your_database
|
||||||
|
username: your_username
|
||||||
|
password: your_password
|
||||||
|
driver-class-name: com.mysql.cj.jdbc.Driver
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7.8 测试集成效果
|
||||||
|
|
||||||
完成以上步骤后,您可以运行您的应用程序并测试以下功能:
|
完成以上步骤后,您可以运行您的应用程序并测试以下功能:
|
||||||
|
|
||||||
@@ -467,3 +789,28 @@ fenix:
|
|||||||
4. 删除实体:GET /employee/remove/{id}
|
4. 删除实体:GET /employee/remove/{id}
|
||||||
|
|
||||||
通过以上步骤,您就可以成功在现有项目中集成 Spring Boot Service Template 的能力,快速实现 CRUD 功能。
|
通过以上步骤,您就可以成功在现有项目中集成 Spring Boot Service Template 的能力,快速实现 CRUD 功能。
|
||||||
|
|
||||||
|
### 7.9 不同数据库实现方式的差异
|
||||||
|
|
||||||
|
#### 7.9.1 JPA 模块特点
|
||||||
|
- 基于 Spring Data JPA,提供完整的 JPA 功能
|
||||||
|
- 支持 QueryDSL 类型安全查询
|
||||||
|
- 支持 Fenix 动态查询
|
||||||
|
- 自动审计功能(创建时间、修改时间)
|
||||||
|
- 需要配置 @EnableJpaAuditing
|
||||||
|
|
||||||
|
#### 7.9.2 Easy Query 模块特点
|
||||||
|
- 基于 Easy Query,提供类型安全的查询构建
|
||||||
|
- 支持逻辑删除
|
||||||
|
- 支持雪花 ID 生成
|
||||||
|
- 查询性能优异
|
||||||
|
- 不需要复杂的注解配置
|
||||||
|
|
||||||
|
#### 7.9.3 Xbatis 模块特点
|
||||||
|
- 基于 MyBatis,提供灵活的 SQL 控制
|
||||||
|
- 支持逻辑删除
|
||||||
|
- 支持雪花 ID 生成
|
||||||
|
- 适合复杂查询场景
|
||||||
|
- 需要编写 Mapper XML
|
||||||
|
|
||||||
|
根据您的项目需求和技术栈,选择最适合的数据库实现方式。
|
||||||
|
|||||||
@@ -64,16 +64,19 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity & ProxyEn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
@Override
|
@Override
|
||||||
public Long count() {
|
public Long count() {
|
||||||
return entityQuery.queryable(target).count();
|
return entityQuery.queryable(target).count();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
@Override
|
@Override
|
||||||
public List<ENTITY> list() {
|
public List<ENTITY> list() {
|
||||||
return entityQuery.queryable(target).toList();
|
return entityQuery.queryable(target).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
@Override
|
@Override
|
||||||
public List<ENTITY> list(Set<Long> ids) {
|
public List<ENTITY> list(Set<Long> ids) {
|
||||||
if (ObjectHelper.isEmpty(ids)) {
|
if (ObjectHelper.isEmpty(ids)) {
|
||||||
@@ -87,6 +90,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity & ProxyEn
|
|||||||
protected void commonPredicates(PROXY proxy) {
|
protected void commonPredicates(PROXY proxy) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
@Override
|
@Override
|
||||||
public Page<ENTITY> list(Query query) {
|
public Page<ENTITY> list(Query query) {
|
||||||
var index = DEFAULT_PAGE_INDEX;
|
var index = DEFAULT_PAGE_INDEX;
|
||||||
@@ -115,12 +119,14 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity & ProxyEn
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Named("detail")
|
@Named("detail")
|
||||||
|
@Transactional(readOnly = true)
|
||||||
@Override
|
@Override
|
||||||
public ENTITY detail(Long id) {
|
public ENTITY detail(Long id) {
|
||||||
return detailOptional(id).orElse(null);
|
return detailOptional(id).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Named("detailOrThrow")
|
@Named("detailOrThrow")
|
||||||
|
@Transactional(readOnly = true)
|
||||||
@Override
|
@Override
|
||||||
public ENTITY detailOrThrow(Long id) {
|
public ENTITY detailOrThrow(Long id) {
|
||||||
return detailOptional(id).orElseThrow(() -> new IdNotFoundException(id));
|
return detailOptional(id).orElseThrow(() -> new IdNotFoundException(id));
|
||||||
|
|||||||
@@ -94,6 +94,10 @@
|
|||||||
</compilerArgs>
|
</compilerArgs>
|
||||||
</configuration>
|
</configuration>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
</project>
|
</project>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
package com.lanyuanxiaoyao.service.template.database.jpa.configuration;
|
||||||
|
|
||||||
|
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class QueryDSLConfiguration {
|
||||||
|
@Bean
|
||||||
|
public JPAQueryFactory jpaQueryFactory(EntityManager manager) {
|
||||||
|
return new JPAQueryFactory(manager);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -77,16 +77,6 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
|||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存实体对象
|
|
||||||
* <p>
|
|
||||||
* 使用saveOrUpdateByNotNullProperties方法保存实体,只更新非空字段。
|
|
||||||
* 该方法具有事务性,遇到任何异常都会回滚。
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param entity 需要保存的实体对象
|
|
||||||
* @return 返回保存后的实体ID
|
|
||||||
*/
|
|
||||||
@Transactional(rollbackFor = Throwable.class)
|
@Transactional(rollbackFor = Throwable.class)
|
||||||
@Override
|
@Override
|
||||||
public Long save(ENTITY entity) {
|
public Long save(ENTITY entity) {
|
||||||
@@ -94,56 +84,25 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
|||||||
return entity.getId();
|
return entity.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量保存实体对象集合
|
|
||||||
* <p>
|
|
||||||
* 使用saveOrUpdateAllByNotNullProperties方法,只更新非空字段。
|
|
||||||
* 该方法具有事务性,遇到任何异常都会回滚。
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param entities 需要保存的实体对象集合
|
|
||||||
*/
|
|
||||||
@Transactional(rollbackFor = Throwable.class)
|
@Transactional(rollbackFor = Throwable.class)
|
||||||
@Override
|
@Override
|
||||||
public void save(Iterable<ENTITY> entities) {
|
public void save(Iterable<ENTITY> entities) {
|
||||||
repository.saveOrUpdateAllByNotNullProperties(entities);
|
repository.saveOrUpdateAllByNotNullProperties(entities);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Transactional(readOnly = true)
|
||||||
* 统计符合条件的实体数量
|
|
||||||
* <p>
|
|
||||||
* 根据listPredicate方法构建的条件统计实体数量。
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @return 返回符合条件的实体数量
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Long count() {
|
public Long count() {
|
||||||
return repository.count(this::commonPredicates);
|
return repository.count(this::commonPredicates);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Transactional(readOnly = true)
|
||||||
* 获取所有符合条件的实体列表
|
|
||||||
* <p>
|
|
||||||
* 根据listPredicate方法构建的条件查询所有实体。
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @return 返回符合条件的实体列表
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public List<ENTITY> list() {
|
public List<ENTITY> list() {
|
||||||
return repository.findAll(this::commonPredicates);
|
return repository.findAll(this::commonPredicates);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Transactional(readOnly = true)
|
||||||
* 根据ID集合获取实体列表
|
|
||||||
* <p>
|
|
||||||
* 根据提供的ID集合查询对应的实体列表,并结合listPredicate方法构建的条件。
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param ids ID集合
|
|
||||||
* @return 返回ID集合对应的实体列表
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public List<ENTITY> list(Set<Long> ids) {
|
public List<ENTITY> list(Set<Long> ids) {
|
||||||
if (ObjectHelper.isEmpty(ids)) {
|
if (ObjectHelper.isEmpty(ids)) {
|
||||||
@@ -164,16 +123,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Transactional(readOnly = true)
|
||||||
* 根据查询条件分页获取实体列表
|
|
||||||
* <p>
|
|
||||||
* 支持复杂的查询条件和分页功能。
|
|
||||||
* 默认分页参数:第1页,每页10条记录,按创建时间降序排列。
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param listQuery 查询条件对象
|
|
||||||
* @return 返回分页查询结果
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Page<ENTITY> list(Query listQuery) {
|
public Page<ENTITY> list(Query listQuery) {
|
||||||
var pageRequest = PageRequest.of(DEFAULT_PAGE_INDEX - 1, DEFAULT_PAGE_SIZE, Sort.by(SimpleEntity.Fields.createdTime).descending());
|
var pageRequest = PageRequest.of(DEFAULT_PAGE_INDEX - 1, DEFAULT_PAGE_SIZE, Sort.by(SimpleEntity.Fields.createdTime).descending());
|
||||||
@@ -232,46 +182,20 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据ID获取实体详情
|
|
||||||
* <p>
|
|
||||||
* 如果实体不存在则返回null。
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param id 实体ID
|
|
||||||
* @return 返回实体详情,不存在时返回null
|
|
||||||
*/
|
|
||||||
@Named("detail")
|
@Named("detail")
|
||||||
|
@Transactional(readOnly = true)
|
||||||
@Override
|
@Override
|
||||||
public ENTITY detail(Long id) {
|
public ENTITY detail(Long id) {
|
||||||
return detailOptional(id).orElse(null);
|
return detailOptional(id).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据ID获取实体详情,不存在时抛出异常
|
|
||||||
* <p>
|
|
||||||
* 如果实体不存在则抛出IdNotFoundException异常。
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param id 实体ID
|
|
||||||
* @return 返回实体详情
|
|
||||||
* @throws IdNotFoundException 当实体不存在时抛出
|
|
||||||
*/
|
|
||||||
@Named("detailOrThrow")
|
@Named("detailOrThrow")
|
||||||
|
@Transactional(readOnly = true)
|
||||||
@Override
|
@Override
|
||||||
public ENTITY detailOrThrow(Long id) {
|
public ENTITY detailOrThrow(Long id) {
|
||||||
return detailOptional(id).orElseThrow(() -> new IdNotFoundException(id));
|
return detailOptional(id).orElseThrow(() -> new IdNotFoundException(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据ID删除实体
|
|
||||||
* <p>
|
|
||||||
* 具有事务性,遇到任何异常都会回滚。
|
|
||||||
* 如果ID为空则不执行任何操作。
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param id 实体主键ID
|
|
||||||
*/
|
|
||||||
@Transactional(rollbackFor = Throwable.class)
|
@Transactional(rollbackFor = Throwable.class)
|
||||||
@Override
|
@Override
|
||||||
public void remove(Long id) {
|
public void remove(Long id) {
|
||||||
@@ -280,16 +204,6 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据ID集合批量删除实体
|
|
||||||
* <p>
|
|
||||||
* 使用deleteAllById方法根据ID集合批量删除实体。
|
|
||||||
* 该方法具有事务性,遇到任何异常都会回滚。
|
|
||||||
* 如果ID集合为空则不执行任何操作。
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @param ids 实体主键ID集合
|
|
||||||
*/
|
|
||||||
@Transactional(rollbackFor = Throwable.class)
|
@Transactional(rollbackFor = Throwable.class)
|
||||||
@Override
|
@Override
|
||||||
public void remove(Set<Long> ids) {
|
public void remove(Set<Long> ids) {
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ import com.lanyuanxiaoyao.service.template.database.jpa.repository.EmployeeRepos
|
|||||||
import com.lanyuanxiaoyao.service.template.database.jpa.repository.ReportRepository;
|
import com.lanyuanxiaoyao.service.template.database.jpa.repository.ReportRepository;
|
||||||
import com.querydsl.core.types.dsl.CaseBuilder;
|
import com.querydsl.core.types.dsl.CaseBuilder;
|
||||||
import com.querydsl.jpa.impl.JPAQueryFactory;
|
import com.querydsl.jpa.impl.JPAQueryFactory;
|
||||||
|
import jakarta.persistence.EntityManager;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.hibernate.Session;
|
|
||||||
import org.springframework.boot.SpringApplication;
|
import org.springframework.boot.SpringApplication;
|
||||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
import org.springframework.boot.context.event.ApplicationReadyEvent;
|
||||||
@@ -44,7 +44,8 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
private final CompanyRepository companyRepository;
|
private final CompanyRepository companyRepository;
|
||||||
private final EmployeeRepository employeeRepository;
|
private final EmployeeRepository employeeRepository;
|
||||||
private final ReportRepository reportRepository;
|
private final ReportRepository reportRepository;
|
||||||
private final Session session;
|
private final EntityManager manager;
|
||||||
|
private final JPAQueryFactory factory;
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(TestApplication.class, args);
|
SpringApplication.run(TestApplication.class, args);
|
||||||
@@ -55,7 +56,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
testCrud();
|
testCrud();
|
||||||
testDelete();
|
testDelete();
|
||||||
testQuery();
|
testQuery();
|
||||||
testNative();
|
testMethodQuery();
|
||||||
|
|
||||||
System.exit(0);
|
System.exit(0);
|
||||||
}
|
}
|
||||||
@@ -88,8 +89,6 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void testQuery() {
|
private void testQuery() {
|
||||||
var factory = new JPAQueryFactory(session);
|
|
||||||
|
|
||||||
formatLog("准备 Specification 查询的测试数据");
|
formatLog("准备 Specification 查询的测试数据");
|
||||||
var company1 = companyRepository.save(Company.builder().name("TechCorp").members(100).build());
|
var company1 = companyRepository.save(Company.builder().name("TechCorp").members(100).build());
|
||||||
var company2 = companyRepository.save(Company.builder().name("DataInc").members(50).build());
|
var company2 = companyRepository.save(Company.builder().name("DataInc").members(50).build());
|
||||||
@@ -192,7 +191,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(result1_querydsl.size() == 1, "基本比较操作符查询失败 (%d)".formatted(result1_querydsl.size()));
|
Assert.isTrue(result1_querydsl.size() == 1, "基本比较操作符查询失败 (%d)".formatted(result1_querydsl.size()));
|
||||||
|
|
||||||
formatLog("1. 基本比较操作符查询 HQL");
|
formatLog("1. 基本比较操作符查询 HQL");
|
||||||
var result1_hql = session.createQuery(
|
var result1_hql = manager.createQuery(
|
||||||
"""
|
"""
|
||||||
from Employee employee
|
from Employee employee
|
||||||
where employee.name = 'Bob'
|
where employee.name = 'Bob'
|
||||||
@@ -203,7 +202,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
and employee.salary <= 45000.00
|
and employee.salary <= 45000.00
|
||||||
""",
|
""",
|
||||||
Employee.class
|
Employee.class
|
||||||
).list();
|
).getResultList();
|
||||||
Assert.isTrue(result1_hql.size() == 1, "基本比较操作符查询失败 (%d)".formatted(result1_hql.size()));
|
Assert.isTrue(result1_hql.size() == 1, "基本比较操作符查询失败 (%d)".formatted(result1_hql.size()));
|
||||||
|
|
||||||
formatLog("2. 区间和集合操作符查询 JPA");
|
formatLog("2. 区间和集合操作符查询 JPA");
|
||||||
@@ -235,13 +234,12 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
.and(QEmployee.employee.age.between(40, 50).not())
|
.and(QEmployee.employee.age.between(40, 50).not())
|
||||||
.and(QEmployee.employee.age.in(25, 30, 35))
|
.and(QEmployee.employee.age.in(25, 30, 35))
|
||||||
.and(QEmployee.employee.role.in(Employee.Role.USER, Employee.Role.ADMIN))
|
.and(QEmployee.employee.role.in(Employee.Role.USER, Employee.Role.ADMIN))
|
||||||
.and(QEmployee.employee.name.in("Charlie", "David").not())
|
|
||||||
.and(QEmployee.employee.name.in(List.of("Charlie", "David")).not())
|
.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, "区间和集合操作符查询失败 (%d)".formatted(result2_querydsl.size()));
|
||||||
|
|
||||||
formatLog("2. 区间和集合操作符查询 HQL");
|
formatLog("2. 区间和集合操作符查询 HQL");
|
||||||
var result2_hql = session.createQuery(
|
var result2_hql = manager.createQuery(
|
||||||
"""
|
"""
|
||||||
from Employee employee
|
from Employee employee
|
||||||
where employee.age between 25 and 30
|
where employee.age between 25 and 30
|
||||||
@@ -251,7 +249,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
and employee.name not in ('Charlie', 'David')
|
and employee.name not in ('Charlie', 'David')
|
||||||
""",
|
""",
|
||||||
Employee.class
|
Employee.class
|
||||||
).list();
|
).getResultList();
|
||||||
Assert.isTrue(result2_hql.size() == 2, "区间和集合操作符查询失败 (%d)".formatted(result2_hql.size()));
|
Assert.isTrue(result2_hql.size() == 2, "区间和集合操作符查询失败 (%d)".formatted(result2_hql.size()));
|
||||||
|
|
||||||
formatLog("3. 字符串操作符查询 JPA");
|
formatLog("3. 字符串操作符查询 JPA");
|
||||||
@@ -268,11 +266,12 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(result3_jpa.size() == 1, "字符串操作符查询失败 (%d)".formatted(result3_jpa.size()));
|
Assert.isTrue(result3_jpa.size() == 1, "字符串操作符查询失败 (%d)".formatted(result3_jpa.size()));
|
||||||
|
|
||||||
formatLog("3. 字符串操作符查询 Fenix");
|
formatLog("3. 字符串操作符查询 Fenix");
|
||||||
log.info("Fenix框架当前版本不支持以下字符串操作符:");
|
log.info("""
|
||||||
log.info(" - cb.length() - 字符串长度函数");
|
Fenix框架当前版本不支持以下字符串操作符:
|
||||||
log.info(" - cb.substring() - 子字符串提取函数");
|
- cb.length() - 字符串长度函数
|
||||||
log.info(" - cb.lower() / cb.upper() - 大小写转换函数 (不支持直接在条件中使用)");
|
- cb.substring() - 子字符串提取函数
|
||||||
log.info("Fenix支持的部分实现:");
|
- cb.lower() / cb.upper() - 大小写转换函数 (不支持直接在条件中使用)
|
||||||
|
Fenix支持的部分实现:""");
|
||||||
var result3_fenix = employeeRepository.findAll(
|
var result3_fenix = employeeRepository.findAll(
|
||||||
builder -> builder.andStartsWith(Employee.Fields.name, "A")
|
builder -> builder.andStartsWith(Employee.Fields.name, "A")
|
||||||
.andNotStartsWith(Employee.Fields.name, "C")
|
.andNotStartsWith(Employee.Fields.name, "C")
|
||||||
@@ -293,7 +292,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(result3_querydsl.size() == 1, "字符串操作符查询失败 (%d)".formatted(result3_querydsl.size()));
|
Assert.isTrue(result3_querydsl.size() == 1, "字符串操作符查询失败 (%d)".formatted(result3_querydsl.size()));
|
||||||
|
|
||||||
formatLog("3. 字符串操作符查询 HQL");
|
formatLog("3. 字符串操作符查询 HQL");
|
||||||
var result3_hql = session.createQuery(
|
var result3_hql = manager.createQuery(
|
||||||
"""
|
"""
|
||||||
from Employee employee
|
from Employee employee
|
||||||
where employee.name like 'A%'
|
where employee.name like 'A%'
|
||||||
@@ -305,7 +304,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
and substring(employee.name, 1, 3) = 'Ali'
|
and substring(employee.name, 1, 3) = 'Ali'
|
||||||
""",
|
""",
|
||||||
Employee.class
|
Employee.class
|
||||||
).list();
|
).getResultList();
|
||||||
Assert.isTrue(result3_hql.size() == 1, "字符串操作符查询失败 (%d)".formatted(result3_hql.size()));
|
Assert.isTrue(result3_hql.size() == 1, "字符串操作符查询失败 (%d)".formatted(result3_hql.size()));
|
||||||
|
|
||||||
formatLog("4. NULL 和布尔操作符查询 JPA");
|
formatLog("4. NULL 和布尔操作符查询 JPA");
|
||||||
@@ -318,9 +317,6 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(!result4_jpa.isEmpty(), "NULL 和布尔操作符查询失败 (%d)".formatted(result4_jpa.size()));
|
Assert.isTrue(!result4_jpa.isEmpty(), "NULL 和布尔操作符查询失败 (%d)".formatted(result4_jpa.size()));
|
||||||
|
|
||||||
formatLog("4. NULL 和布尔操作符查询 Fenix");
|
formatLog("4. NULL 和布尔操作符查询 Fenix");
|
||||||
log.info("Fenix框架当前版本不支持以下布尔操作符:");
|
|
||||||
log.info(" - cb.isTrue() / cb.isFalse() - 专用布尔判断函数");
|
|
||||||
log.info("Fenix通过equals/notEquals处理布尔字段");
|
|
||||||
var result4_fenix = employeeRepository.findAll(
|
var result4_fenix = employeeRepository.findAll(
|
||||||
builder -> builder.andEquals(Employee.Fields.active, true)
|
builder -> builder.andEquals(Employee.Fields.active, true)
|
||||||
.andIsNotNull(Employee.Fields.bonus)
|
.andIsNotNull(Employee.Fields.bonus)
|
||||||
@@ -338,7 +334,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(!result4_querydsl.isEmpty(), "NULL 和布尔操作符查询失败 (%d)".formatted(result4_querydsl.size()));
|
Assert.isTrue(!result4_querydsl.isEmpty(), "NULL 和布尔操作符查询失败 (%d)".formatted(result4_querydsl.size()));
|
||||||
|
|
||||||
formatLog("4. NULL 和布尔操作符查询 HQL");
|
formatLog("4. NULL 和布尔操作符查询 HQL");
|
||||||
var result4_hql = session.createQuery(
|
var result4_hql = manager.createQuery(
|
||||||
"""
|
"""
|
||||||
from Employee employee
|
from Employee employee
|
||||||
where employee.active is true
|
where employee.active is true
|
||||||
@@ -346,7 +342,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
and employee.code not in ('E999')
|
and employee.code not in ('E999')
|
||||||
""",
|
""",
|
||||||
Employee.class
|
Employee.class
|
||||||
).list();
|
).getResultList();
|
||||||
Assert.isTrue(!result4_hql.isEmpty(), "NULL 和布尔操作符查询失败 (%d)".formatted(result4_hql.size()));
|
Assert.isTrue(!result4_hql.isEmpty(), "NULL 和布尔操作符查询失败 (%d)".formatted(result4_hql.size()));
|
||||||
|
|
||||||
formatLog("5. 集合操作符查询 JPA");
|
formatLog("5. 集合操作符查询 JPA");
|
||||||
@@ -362,11 +358,12 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(result5_jpa.size() == 2, "集合操作符查询失败 (%d)".formatted(result5_jpa.size()));
|
Assert.isTrue(result5_jpa.size() == 2, "集合操作符查询失败 (%d)".formatted(result5_jpa.size()));
|
||||||
|
|
||||||
formatLog("5. 集合操作符查询 Fenix");
|
formatLog("5. 集合操作符查询 Fenix");
|
||||||
log.info("Fenix框架当前版本不支持以下集合操作符:");
|
log.info("""
|
||||||
log.info(" - cb.isNotEmpty() / cb.isEmpty() - 集合非空/空判断");
|
Fenix框架当前版本不支持以下集合操作符:
|
||||||
log.info(" - cb.isMember() / cb.isNotMember() - 集合成员判断");
|
- cb.isNotEmpty() / cb.isEmpty() - 集合非空/空判断
|
||||||
log.info(" - cb.size() - 集合大小函数");
|
- cb.isMember() / cb.isNotMember() - 集合成员判断
|
||||||
log.info("这些集合操作在JPA Criteria中需要复杂的join处理,Fenix当前不支持");
|
- cb.size() - 集合大小函数
|
||||||
|
这些集合操作在JPA Criteria中需要复杂的join处理,Fenix当前不支持""");
|
||||||
|
|
||||||
formatLog("5. 集合操作符查询 QueryDSL");
|
formatLog("5. 集合操作符查询 QueryDSL");
|
||||||
var result5_querydsl = employeeRepository.findAll(
|
var result5_querydsl = employeeRepository.findAll(
|
||||||
@@ -380,7 +377,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(result5_querydsl.size() == 2, "集合操作符查询失败 (%d)".formatted(result5_querydsl.size()));
|
Assert.isTrue(result5_querydsl.size() == 2, "集合操作符查询失败 (%d)".formatted(result5_querydsl.size()));
|
||||||
|
|
||||||
formatLog("5. 集合操作符查询 HQL");
|
formatLog("5. 集合操作符查询 HQL");
|
||||||
var result5_hql = session.createQuery(
|
var result5_hql = manager.createQuery(
|
||||||
"""
|
"""
|
||||||
from Employee employee
|
from Employee employee
|
||||||
where employee.skills is not empty
|
where employee.skills is not empty
|
||||||
@@ -391,7 +388,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
and size(employee.skills) < 4
|
and size(employee.skills) < 4
|
||||||
""",
|
""",
|
||||||
Employee.class
|
Employee.class
|
||||||
).list();
|
).getResultList();
|
||||||
Assert.isTrue(result5_hql.size() == 2, "集合操作符查询失败 (%d)".formatted(result5_hql.size()));
|
Assert.isTrue(result5_hql.size() == 2, "集合操作符查询失败 (%d)".formatted(result5_hql.size()));
|
||||||
|
|
||||||
formatLog("6. 逻辑操作符查询 JPA");
|
formatLog("6. 逻辑操作符查询 JPA");
|
||||||
@@ -411,8 +408,9 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(result6_jpa.size() == 2, "逻辑操作符查询失败 (%d)".formatted(result6_jpa.size()));
|
Assert.isTrue(result6_jpa.size() == 2, "逻辑操作符查询失败 (%d)".formatted(result6_jpa.size()));
|
||||||
|
|
||||||
formatLog("6. 逻辑操作符查询 Fenix");
|
formatLog("6. 逻辑操作符查询 Fenix");
|
||||||
log.info("Fenix框架当前版本不支持复杂的嵌套OR和NOT组合逻辑");
|
log.info("""
|
||||||
log.info("Fenix支持简单的orEquals,但不支持嵌套的or + not组合");
|
Fenix框架当前版本不支持复杂的嵌套OR和NOT组合逻辑
|
||||||
|
Fenix支持简单的orEquals,但不支持嵌套的or + not组合""");
|
||||||
var result6_fenix = employeeRepository.findAll(
|
var result6_fenix = employeeRepository.findAll(
|
||||||
builder -> builder.orEquals(Employee.Fields.name, "Alice")
|
builder -> builder.orEquals(Employee.Fields.name, "Alice")
|
||||||
.orEquals(Employee.Fields.name, "Bob")
|
.orEquals(Employee.Fields.name, "Bob")
|
||||||
@@ -429,14 +427,14 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(result6_querydsl.size() == 2, "逻辑操作符查询失败 (%d)".formatted(result6_querydsl.size()));
|
Assert.isTrue(result6_querydsl.size() == 2, "逻辑操作符查询失败 (%d)".formatted(result6_querydsl.size()));
|
||||||
|
|
||||||
formatLog("6. 逻辑操作符查询 HQL");
|
formatLog("6. 逻辑操作符查询 HQL");
|
||||||
var result6_hql = session.createQuery(
|
var result6_hql = manager.createQuery(
|
||||||
"""
|
"""
|
||||||
from Employee employee
|
from Employee employee
|
||||||
where (employee.name = 'Alice' or employee.name = 'Bob')
|
where (employee.name = 'Alice' or employee.name = 'Bob')
|
||||||
and not (employee.name = 'Charlie' or employee.name = 'David')
|
and not (employee.name = 'Charlie' or employee.name = 'David')
|
||||||
""",
|
""",
|
||||||
Employee.class
|
Employee.class
|
||||||
).list();
|
).getResultList();
|
||||||
Assert.isTrue(result6_hql.size() == 2, "逻辑操作符查询失败 (%d)".formatted(result6_hql.size()));
|
Assert.isTrue(result6_hql.size() == 2, "逻辑操作符查询失败 (%d)".formatted(result6_hql.size()));
|
||||||
|
|
||||||
formatLog("7. Specification 链式调用查询 JPA");
|
formatLog("7. Specification 链式调用查询 JPA");
|
||||||
@@ -472,7 +470,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(result7_querydsl.size() == 2, "Specification 链式调用失败 (%d)".formatted(result7_querydsl.size()));
|
Assert.isTrue(result7_querydsl.size() == 2, "Specification 链式调用失败 (%d)".formatted(result7_querydsl.size()));
|
||||||
|
|
||||||
formatLog("7. Specification 链式调用查询 HQL");
|
formatLog("7. Specification 链式调用查询 HQL");
|
||||||
var result7_hql = session.createQuery(
|
var result7_hql = manager.createQuery(
|
||||||
"""
|
"""
|
||||||
from Employee employee
|
from Employee employee
|
||||||
where employee.active is true
|
where employee.active is true
|
||||||
@@ -482,7 +480,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
or employee.name = 'Charlie'
|
or employee.name = 'Charlie'
|
||||||
""",
|
""",
|
||||||
Employee.class
|
Employee.class
|
||||||
).list();
|
).getResultList();
|
||||||
Assert.isTrue(result7_hql.size() == 2, "Specification 链式调用失败 (%d)".formatted(result7_hql.size()));
|
Assert.isTrue(result7_hql.size() == 2, "Specification 链式调用失败 (%d)".formatted(result7_hql.size()));
|
||||||
|
|
||||||
formatLog("8. Join 操作查询 JPA");
|
formatLog("8. Join 操作查询 JPA");
|
||||||
@@ -503,14 +501,15 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(result8_jpa.size() == 1, "Join 操作查询失败 (%d)".formatted(result8_jpa.size()));
|
Assert.isTrue(result8_jpa.size() == 1, "Join 操作查询失败 (%d)".formatted(result8_jpa.size()));
|
||||||
|
|
||||||
formatLog("8. Join 操作查询 Fenix");
|
formatLog("8. Join 操作查询 Fenix");
|
||||||
log.info("Fenix框架当前版本不支持显式的join操作:");
|
log.info("""
|
||||||
log.info(" - root.join() - 显式关联查询");
|
Fenix框架当前版本不支持显式的join操作:
|
||||||
log.info(" - root.fetch() - 显式抓取查询");
|
- root.join() - 显式关联查询
|
||||||
log.info(" - Map join (cb.equal(root.join().value(), ...))");
|
- root.fetch() - 显式抓取查询
|
||||||
log.info("Fenix主要用于单表条件查询,复杂join操作建议使用JPA Specification原生方式或QueryDSL");
|
- Map join (cb.equal(root.join().value(), ...))
|
||||||
log.info("可以通过doAny使用原生CriteriaBuilder实现join操作");
|
Fenix主要用于单表条件查询,复杂join操作建议使用JPA Specification原生方式或QueryDSL
|
||||||
log.info("注意:由于类型系统的限制,doAny中使用join可能会有类型推断问题");
|
可以通过doAny使用原生CriteriaBuilder实现join操作
|
||||||
log.info("建议:对于join等复杂查询,直接使用JPA Specification原生方式");
|
注意:由于类型系统的限制,doAny中使用join可能会有类型推断问题
|
||||||
|
建议:对于join等复杂查询,直接使用JPA Specification原生方式""");
|
||||||
|
|
||||||
formatLog("8. Join 操作查询 QueryDSL");
|
formatLog("8. Join 操作查询 QueryDSL");
|
||||||
var result8_querydsl = employeeRepository.findAll(
|
var result8_querydsl = employeeRepository.findAll(
|
||||||
@@ -524,7 +523,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(result8_querydsl.size() == 1, "Join 操作查询失败 (%d)".formatted(result8_querydsl.size()));
|
Assert.isTrue(result8_querydsl.size() == 1, "Join 操作查询失败 (%d)".formatted(result8_querydsl.size()));
|
||||||
|
|
||||||
formatLog("8. Join 操作查询 HQL");
|
formatLog("8. Join 操作查询 HQL");
|
||||||
var result8_hql = session.createQuery(
|
var result8_hql = manager.createQuery(
|
||||||
"""
|
"""
|
||||||
from Employee employee
|
from Employee employee
|
||||||
join employee.company as company
|
join employee.company as company
|
||||||
@@ -538,7 +537,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
and value(prop) != 'Junior'
|
and value(prop) != 'Junior'
|
||||||
""",
|
""",
|
||||||
Employee.class
|
Employee.class
|
||||||
).list();
|
).getResultList();
|
||||||
Assert.isTrue(result8_hql.size() == 1, "Join 操作查询失败 (%d)".formatted(result8_hql.size()));
|
Assert.isTrue(result8_hql.size() == 1, "Join 操作查询失败 (%d)".formatted(result8_hql.size()));
|
||||||
|
|
||||||
formatLog("9. 子查询和聚合函数查询 JPA");
|
formatLog("9. 子查询和聚合函数查询 JPA");
|
||||||
@@ -570,13 +569,14 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(result9_jpa.isEmpty(), "子查询(聚合函数)+ 数学运算失败 (%d)".formatted(result9_jpa.size()));
|
Assert.isTrue(result9_jpa.isEmpty(), "子查询(聚合函数)+ 数学运算失败 (%d)".formatted(result9_jpa.size()));
|
||||||
|
|
||||||
formatLog("9. 子查询和聚合函数查询 Fenix");
|
formatLog("9. 子查询和聚合函数查询 Fenix");
|
||||||
log.info("Fenix框架当前版本不支持以下高级查询特性:");
|
log.info("""
|
||||||
log.info(" - query.subquery() - 子查询");
|
Fenix框架当前版本不支持以下高级查询特性:
|
||||||
log.info(" - cb.avg(), cb.count(), cb.sum() - 聚合函数");
|
- query.subquery() - 子查询
|
||||||
log.info(" - cb.coalesce() - 空值替换函数");
|
- cb.avg(), cb.count(), cb.sum() - 聚合函数
|
||||||
log.info(" - cb.sum() - 数值加法运算");
|
- cb.coalesce() - 空值替换函数
|
||||||
log.info("这些是SQL级别的复杂查询,Fenix主要用于动态条件构建");
|
- cb.sum() - 数值加法运算
|
||||||
log.info("可以通过doAny使用原生CriteriaBuilder实现部分聚合操作");
|
这些是SQL级别的复杂查询,Fenix主要用于动态条件构建
|
||||||
|
可以通过doAny使用原生CriteriaBuilder实现部分聚合操作""");
|
||||||
|
|
||||||
formatLog("9. 子查询和聚合函数查询 QueryDSL");
|
formatLog("9. 子查询和聚合函数查询 QueryDSL");
|
||||||
var avgQuery = factory.select(QEmployee.employee.salary.avg());
|
var avgQuery = factory.select(QEmployee.employee.salary.avg());
|
||||||
@@ -594,7 +594,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(result9_querydsl.isEmpty(), "子查询(聚合函数)+ 数学运算失败 (%d)".formatted(result9_querydsl.size()));
|
Assert.isTrue(result9_querydsl.isEmpty(), "子查询(聚合函数)+ 数学运算失败 (%d)".formatted(result9_querydsl.size()));
|
||||||
|
|
||||||
formatLog("9. 子查询和聚合函数查询 HQL");
|
formatLog("9. 子查询和聚合函数查询 HQL");
|
||||||
var result9_hql = session.createQuery(
|
var result9_hql = manager.createQuery(
|
||||||
"""
|
"""
|
||||||
from Employee employee
|
from Employee employee
|
||||||
where employee.salary > (select avg(e.salary) from Employee e)
|
where employee.salary > (select avg(e.salary) from Employee e)
|
||||||
@@ -607,7 +607,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
and employee.role != com.lanyuanxiaoyao.service.template.database.jpa.entity.Employee.Role.USER
|
and employee.role != com.lanyuanxiaoyao.service.template.database.jpa.entity.Employee.Role.USER
|
||||||
""",
|
""",
|
||||||
Employee.class
|
Employee.class
|
||||||
).list();
|
).getResultList();
|
||||||
Assert.isTrue(result9_hql.isEmpty(), "子查询(聚合函数)+ 数学运算失败 (%d)".formatted(result9_hql.size()));
|
Assert.isTrue(result9_hql.isEmpty(), "子查询(聚合函数)+ 数学运算失败 (%d)".formatted(result9_hql.size()));
|
||||||
|
|
||||||
formatLog("10. 排序查询 JPA");
|
formatLog("10. 排序查询 JPA");
|
||||||
@@ -627,8 +627,9 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(result10_jpa.size() == 3, "排序查询失败 (%d)".formatted(result10_jpa.size()));
|
Assert.isTrue(result10_jpa.size() == 3, "排序查询失败 (%d)".formatted(result10_jpa.size()));
|
||||||
|
|
||||||
formatLog("10. 排序查询 Fenix");
|
formatLog("10. 排序查询 Fenix");
|
||||||
log.info("Fenix框架使用Spring Data JPA原生的Sort对象进行排序");
|
log.info("""
|
||||||
log.info("Fenix构建查询条件,Sort对象通过repository.findAll()的第二个参数传入");
|
Fenix框架使用Spring Data JPA原生的Sort对象进行排序
|
||||||
|
Fenix构建查询条件,Sort对象通过repository.findAll()的第二个参数传入""");
|
||||||
var result10_fenix = employeeRepository.findAll(
|
var result10_fenix = employeeRepository.findAll(
|
||||||
builder -> builder.andEquals(Employee.Fields.active, true)
|
builder -> builder.andEquals(Employee.Fields.active, true)
|
||||||
.andNotEquals(Employee.Fields.role, Employee.Role.ADMIN)
|
.andNotEquals(Employee.Fields.role, Employee.Role.ADMIN)
|
||||||
@@ -654,7 +655,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(result10_querydsl.size() == 3, "排序查询失败 (%d)".formatted(result10_querydsl.size()));
|
Assert.isTrue(result10_querydsl.size() == 3, "排序查询失败 (%d)".formatted(result10_querydsl.size()));
|
||||||
|
|
||||||
formatLog("10. 排序查询 HQL");
|
formatLog("10. 排序查询 HQL");
|
||||||
var result10_hql = session.createQuery(
|
var result10_hql = manager.createQuery(
|
||||||
"""
|
"""
|
||||||
from Employee employee
|
from Employee employee
|
||||||
where employee.active is true
|
where employee.active is true
|
||||||
@@ -664,7 +665,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
order by employee.age desc, employee.name asc
|
order by employee.age desc, employee.name asc
|
||||||
""",
|
""",
|
||||||
Employee.class
|
Employee.class
|
||||||
).list();
|
).getResultList();
|
||||||
Assert.isTrue(result10_hql.size() == 3, "排序查询失败 (%d)".formatted(result10_hql.size()));
|
Assert.isTrue(result10_hql.size() == 3, "排序查询失败 (%d)".formatted(result10_hql.size()));
|
||||||
|
|
||||||
formatLog("11. 分页查询 JPA");
|
formatLog("11. 分页查询 JPA");
|
||||||
@@ -682,8 +683,9 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(page11_jpa.getTotalElements() == 3, "总元素数不正确 (%d)".formatted(page11_jpa.getTotalElements()));
|
Assert.isTrue(page11_jpa.getTotalElements() == 3, "总元素数不正确 (%d)".formatted(page11_jpa.getTotalElements()));
|
||||||
|
|
||||||
formatLog("11. 分页查询 Fenix");
|
formatLog("11. 分页查询 Fenix");
|
||||||
log.info("Fenix框架使用Spring Data JPA原生的Pageable对象进行分页");
|
log.info("""
|
||||||
log.info("Fenix构建查询条件,Pageable对象通过repository.findAll()的第二个参数传入");
|
Fenix框架使用Spring Data JPA原生的Pageable对象进行分页
|
||||||
|
Fenix构建查询条件,Pageable对象通过repository.findAll()的第二个参数传入""");
|
||||||
var page11_fenix = employeeRepository.findAll(
|
var page11_fenix = employeeRepository.findAll(
|
||||||
builder -> builder.andEquals(Employee.Fields.active, true)
|
builder -> builder.andEquals(Employee.Fields.active, true)
|
||||||
.andNotEquals(Employee.Fields.role, Employee.Role.ADMIN)
|
.andNotEquals(Employee.Fields.role, Employee.Role.ADMIN)
|
||||||
@@ -696,10 +698,11 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(page11_fenix.getTotalElements() == 3, "总元素数不正确 (%d)".formatted(page11_fenix.getTotalElements()));
|
Assert.isTrue(page11_fenix.getTotalElements() == 3, "总元素数不正确 (%d)".formatted(page11_fenix.getTotalElements()));
|
||||||
|
|
||||||
formatLog("11. 分页查询 QueryDSL");
|
formatLog("11. 分页查询 QueryDSL");
|
||||||
log.info("QueryDSL支持分页查询:");
|
log.info("""
|
||||||
log.info(" - offset() - 跳过记录数");
|
QueryDSL支持分页查询:
|
||||||
log.info(" - limit() - 限制记录数");
|
- offset() - 跳过记录数
|
||||||
log.info(" - 也可以结合Spring Data JPA的Pageable对象");
|
- limit() - 限制记录数
|
||||||
|
- 也可以结合Spring Data JPA的Pageable对象""");
|
||||||
var page11_querydsl = employeeRepository.findAll(
|
var page11_querydsl = employeeRepository.findAll(
|
||||||
QEmployee.employee.active.isTrue()
|
QEmployee.employee.active.isTrue()
|
||||||
.and(QEmployee.employee.role.ne(Employee.Role.ADMIN))
|
.and(QEmployee.employee.role.ne(Employee.Role.ADMIN))
|
||||||
@@ -711,7 +714,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(page11_querydsl.getTotalElements() == 3, "总元素数不正确 (%d)".formatted(page11_querydsl.getTotalElements()));
|
Assert.isTrue(page11_querydsl.getTotalElements() == 3, "总元素数不正确 (%d)".formatted(page11_querydsl.getTotalElements()));
|
||||||
|
|
||||||
formatLog("11. 分页查询 HQL");
|
formatLog("11. 分页查询 HQL");
|
||||||
var page11_hql = session.createQuery(
|
var page11_hql = manager.createQuery(
|
||||||
"""
|
"""
|
||||||
from Employee employee
|
from Employee employee
|
||||||
where employee.active is true
|
where employee.active is true
|
||||||
@@ -721,8 +724,8 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
order by employee.age
|
order by employee.age
|
||||||
""",
|
""",
|
||||||
Employee.class
|
Employee.class
|
||||||
).setFirstResult(0).setMaxResults(2).list();
|
).setFirstResult(0).setMaxResults(2).getResultList();
|
||||||
var total11_hql = session.createQuery(
|
var total11_hql = manager.createQuery(
|
||||||
"""
|
"""
|
||||||
from Employee employee
|
from Employee employee
|
||||||
where employee.active is true
|
where employee.active is true
|
||||||
@@ -731,7 +734,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
and employee.salary < 60000.00
|
and employee.salary < 60000.00
|
||||||
""",
|
""",
|
||||||
Employee.class
|
Employee.class
|
||||||
).list().size();
|
).getResultList().size();
|
||||||
Assert.isTrue(page11_hql.size() == 2, "分页大小不正确 (%d)".formatted(page11_hql.size()));
|
Assert.isTrue(page11_hql.size() == 2, "分页大小不正确 (%d)".formatted(page11_hql.size()));
|
||||||
Assert.isTrue(total11_hql == 3, "总元素数不正确 (%d)".formatted(total11_hql));
|
Assert.isTrue(total11_hql == 3, "总元素数不正确 (%d)".formatted(total11_hql));
|
||||||
|
|
||||||
@@ -756,14 +759,15 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(result12_jpa.size() == 2, "CASE WHEN 查询失败 (%d)".formatted(result12_jpa.size()));
|
Assert.isTrue(result12_jpa.size() == 2, "CASE WHEN 查询失败 (%d)".formatted(result12_jpa.size()));
|
||||||
|
|
||||||
formatLog("12. CASE WHEN 条件表达式查询 Fenix");
|
formatLog("12. CASE WHEN 条件表达式查询 Fenix");
|
||||||
log.info("Fenix框架当前版本不支持CASE WHEN条件表达式:");
|
log.info("""
|
||||||
log.info(" - cb.selectCase() - SQL CASE WHEN表达式");
|
Fenix框架当前版本不支持CASE WHEN条件表达式:
|
||||||
log.info(" - .when() - CASE WHEN条件分支");
|
- cb.selectCase() - SQL CASE WHEN表达式
|
||||||
log.info(" - .otherwise() - CASE ELSE分支");
|
- .when() - CASE WHEN条件分支
|
||||||
log.info("CASE WHEN是SQL级别的条件表达式,Fenix主要用于动态条件构建");
|
- .otherwise() - CASE ELSE分支
|
||||||
log.info("可以通过doAny使用原生CriteriaBuilder实现CASE WHEN操作");
|
CASE WHEN是SQL级别的条件表达式,Fenix主要用于动态条件构建
|
||||||
log.info("注意:由于类型系统的限制,doAny中使用CriteriaBuilder的复杂表达式可能会有类型推断问题");
|
可以通过doAny使用原生CriteriaBuilder实现CASE WHEN操作
|
||||||
log.info("建议:对于CASE WHEN等复杂查询,直接使用JPA Specification原生方式");
|
注意:由于类型系统的限制,doAny中使用CriteriaBuilder的复杂表达式可能会有类型推断问题
|
||||||
|
建议:对于CASE WHEN等复杂查询,直接使用JPA Specification原生方式""");
|
||||||
var result12_fenix = employeeRepository.findAll(builder -> {
|
var result12_fenix = employeeRepository.findAll(builder -> {
|
||||||
return builder.doAny(null, null, (cb, root, fieldName, value) -> {
|
return builder.doAny(null, null, (cb, root, fieldName, value) -> {
|
||||||
// 使用原生JPA Criteria实现CASE WHEN(简化版本)
|
// 使用原生JPA Criteria实现CASE WHEN(简化版本)
|
||||||
@@ -790,7 +794,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
Assert.isTrue(result12_querydsl.size() == 2, "CASE WHEN 查询失败 (%d)".formatted(result12_querydsl.size()));
|
Assert.isTrue(result12_querydsl.size() == 2, "CASE WHEN 查询失败 (%d)".formatted(result12_querydsl.size()));
|
||||||
|
|
||||||
formatLog("12. CASE WHEN 条件表达式查询 HQL");
|
formatLog("12. CASE WHEN 条件表达式查询 HQL");
|
||||||
var result12_hql = session.createQuery(
|
var result12_hql = manager.createQuery(
|
||||||
"""
|
"""
|
||||||
from Employee employee
|
from Employee employee
|
||||||
where (case when employee.age > 30 then 'Senior'
|
where (case when employee.age > 30 then 'Senior'
|
||||||
@@ -803,7 +807,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
end) != 'Junior'
|
end) != 'Junior'
|
||||||
""",
|
""",
|
||||||
Employee.class
|
Employee.class
|
||||||
).list();
|
).getResultList();
|
||||||
Assert.isTrue(result12_hql.size() == 2, "CASE WHEN 查询失败 (%d)".formatted(result12_hql.size()));
|
Assert.isTrue(result12_hql.size() == 2, "CASE WHEN 查询失败 (%d)".formatted(result12_hql.size()));
|
||||||
|
|
||||||
formatLog("13. 综合多条件查询 JPA");
|
formatLog("13. 综合多条件查询 JPA");
|
||||||
@@ -838,13 +842,14 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
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"), "综合多条件查询失败 (%d)".formatted(result13_jpa.size()));
|
||||||
|
|
||||||
formatLog("13. 综合多条件查询 Fenix");
|
formatLog("13. 综合多条件查询 Fenix");
|
||||||
log.info("Fenix框架不支持综合多条件查询中的复杂join和集合操作:");
|
log.info("""
|
||||||
log.info(" - 显式join操作(company, skills, properties)");
|
Fenix框架不支持综合多条件查询中的复杂join和集合操作:
|
||||||
log.info(" - 集合isEmpty/isNotEmpty/isMember操作");
|
- 显式join操作(company, skills, properties)
|
||||||
log.info(" - Map join操作");
|
- 集合isEmpty/isNotEmpty/isMember操作
|
||||||
log.info(" - 嵌入式对象查询(address.city)");
|
- Map join操作
|
||||||
log.info("Fenix主要支持简单的单表字段查询");
|
- 嵌入式对象查询(address.city)
|
||||||
log.info("可以通过doAny使用原生CriteriaBuilder实现复杂综合查询");
|
Fenix主要支持简单的单表字段查询
|
||||||
|
可以通过doAny使用原生CriteriaBuilder实现复杂综合查询""");
|
||||||
|
|
||||||
formatLog("13. 综合多条件查询 QueryDSL");
|
formatLog("13. 综合多条件查询 QueryDSL");
|
||||||
var result13_querydsl = employeeRepository.findAll(
|
var result13_querydsl = employeeRepository.findAll(
|
||||||
@@ -874,7 +879,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
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"), "综合多条件查询失败 (%d)".formatted(result13_querydsl.size()));
|
||||||
|
|
||||||
formatLog("13. 综合多条件查询 HQL");
|
formatLog("13. 综合多条件查询 HQL");
|
||||||
var result13_hql = session.createQuery(
|
var result13_hql = manager.createQuery(
|
||||||
"""
|
"""
|
||||||
select distinct employee
|
select distinct employee
|
||||||
from Employee employee
|
from Employee employee
|
||||||
@@ -897,7 +902,7 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
and employee.role != com.lanyuanxiaoyao.service.template.database.jpa.entity.Employee.Role.USER
|
and employee.role != com.lanyuanxiaoyao.service.template.database.jpa.entity.Employee.Role.USER
|
||||||
""",
|
""",
|
||||||
Employee.class
|
Employee.class
|
||||||
).list();
|
).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"), "综合多条件查询失败 (%d)".formatted(result13_hql.size()));
|
||||||
|
|
||||||
formatLog("清理测试数据");
|
formatLog("清理测试数据");
|
||||||
@@ -905,7 +910,383 @@ public class TestApplication extends AbstractTestApplication {
|
|||||||
companyRepository.deleteAllInBatch();
|
companyRepository.deleteAllInBatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testNative() {
|
private void testMethodQuery() {
|
||||||
|
formatLog("准备 Query Method 测试数据");
|
||||||
|
var company1 = companyRepository.save(Company.builder().name("TechCorp").members(100).build());
|
||||||
|
var company2 = companyRepository.save(Company.builder().name("DataInc").members(50).build());
|
||||||
|
var company3 = companyRepository.save(Company.builder().name("CloudSys").members(150).build());
|
||||||
|
|
||||||
|
var skill1 = Skill.builder().name("Java").description("Java 编程语言").build();
|
||||||
|
var skill2 = Skill.builder().name("Python").description("Python 编程语言").build();
|
||||||
|
var skill3 = Skill.builder().name("Spring").description("Spring 框架").build();
|
||||||
|
var skill4 = Skill.builder().name("MySQL").description("MySQL 数据库").build();
|
||||||
|
|
||||||
|
var emp1 = employeeRepository.save(Employee.builder()
|
||||||
|
.name("Alice").age(30).role(Employee.Role.ADMIN).code("E001")
|
||||||
|
.salary(new BigDecimal("50000.00")).bonus(new BigDecimal("5000.00"))
|
||||||
|
.active(true).company(company1)
|
||||||
|
.address(Address.builder().street("Main St").city("Beijing").state("Beijing").zipCode("100000").country("China").build())
|
||||||
|
.skills(Set.of(skill1, skill3))
|
||||||
|
.hobbies(List.of("Reading", "Swimming"))
|
||||||
|
.properties(Map.of("department", "Engineering", "level", "Senior"))
|
||||||
|
.connections(Map.of(Employee.ConnectionType.EMAIL, "alice@example.com"))
|
||||||
|
.build());
|
||||||
|
|
||||||
|
var emp2 = employeeRepository.save(Employee.builder()
|
||||||
|
.name("Bob").age(25).role(Employee.Role.USER).code("E002")
|
||||||
|
.salary(new BigDecimal("40000.00")).bonus(new BigDecimal("4000.00"))
|
||||||
|
.active(true).company(company2)
|
||||||
|
.address(Address.builder().street("Oak Ave").city("Shanghai").state("Shanghai").zipCode("200000").country("China").build())
|
||||||
|
.skills(Set.of(skill2))
|
||||||
|
.hobbies(List.of("Gaming"))
|
||||||
|
.properties(Map.of("department", "Marketing", "level", "Junior"))
|
||||||
|
.connections(Map.of(Employee.ConnectionType.PHONE, "1234567890"))
|
||||||
|
.build());
|
||||||
|
|
||||||
|
var emp3 = employeeRepository.save(Employee.builder()
|
||||||
|
.name("Charlie").age(35).role(Employee.Role.ADMIN).code("E003")
|
||||||
|
.salary(new BigDecimal("60000.00")).bonus(new BigDecimal("6000.00"))
|
||||||
|
.active(false).company(company1)
|
||||||
|
.address(Address.builder().street("Pine Rd").city("Shenzhen").state("Guangdong").zipCode("518000").country("China").build())
|
||||||
|
.skills(Set.of(skill1, skill2, skill3))
|
||||||
|
.hobbies(List.of("Reading", "Gaming", "Music"))
|
||||||
|
.properties(Map.of("department", "Engineering", "level", "Lead"))
|
||||||
|
.connections(Map.of(Employee.ConnectionType.EMAIL, "charlie@example.com", Employee.ConnectionType.PHONE, "0987654321"))
|
||||||
|
.build());
|
||||||
|
|
||||||
|
var emp4 = employeeRepository.save(Employee.builder()
|
||||||
|
.name("David").age(28).role(Employee.Role.USER).code("E004")
|
||||||
|
.salary(new BigDecimal("45000.00")).bonus(null)
|
||||||
|
.active(true).company(company3)
|
||||||
|
.address(Address.builder().street("Elm Ln").city("Guangzhou").state("Guangdong").zipCode("510000").country("China").build())
|
||||||
|
.skills(Set.of(skill4))
|
||||||
|
.hobbies(List.of())
|
||||||
|
.properties(Map.of())
|
||||||
|
.connections(Map.of())
|
||||||
|
.build());
|
||||||
|
|
||||||
|
var emp5 = employeeRepository.save(Employee.builder()
|
||||||
|
.name("Alice Smith").age(32).role(Employee.Role.USER).code("E005")
|
||||||
|
.salary(new BigDecimal("55000.00")).bonus(new BigDecimal("5500.00"))
|
||||||
|
.active(true).company(company2)
|
||||||
|
.address(Address.builder().street("Maple Dr").city("Hangzhou").state("Zhejiang").zipCode("310000").country("China").build())
|
||||||
|
.skills(Set.of(skill1, skill4))
|
||||||
|
.hobbies(List.of("Swimming", "Music"))
|
||||||
|
.properties(Map.of("department", "Sales", "level", "Middle"))
|
||||||
|
.connections(Map.of(Employee.ConnectionType.EMAIL, "alicesmith@example.com"))
|
||||||
|
.build());
|
||||||
|
|
||||||
|
// ==================== 1. 基本字段查询测试 ====================
|
||||||
|
|
||||||
|
formatLog("1.1 单字段精确匹配查询");
|
||||||
|
var r1_1 = employeeRepository.findByName("Bob");
|
||||||
|
Assert.isTrue(!r1_1.isEmpty(), "findByName failed");
|
||||||
|
Assert.isTrue(r1_1.get(0).getName().equals("Bob"), "findByName 结果不正确");
|
||||||
|
|
||||||
|
var r1_2 = employeeRepository.findByCode("E003");
|
||||||
|
Assert.isTrue(r1_2.isPresent(), "findByCode failed");
|
||||||
|
Assert.isTrue(r1_2.orElseThrow().getCode().equals("E003"), "findByCode 结果不正确");
|
||||||
|
|
||||||
|
var r1_3 = employeeRepository.findByRole(Employee.Role.ADMIN);
|
||||||
|
Assert.isTrue(r1_3.size() == 2, "findByRole 结果数量应为2,实际:" + r1_3.size());
|
||||||
|
|
||||||
|
formatLog("1.2 布尔值查询");
|
||||||
|
var r1_4 = employeeRepository.findByActiveTrue();
|
||||||
|
Assert.isTrue(r1_4.size() == 4, "findByActiveTrue 结果数量应为4,实际:" + r1_4.size());
|
||||||
|
|
||||||
|
var r1_5 = employeeRepository.findByActiveFalse();
|
||||||
|
Assert.isTrue(r1_5.size() == 1, "findByActiveFalse 结果数量应为1,实际:" + r1_5.size());
|
||||||
|
|
||||||
|
var r1_6 = employeeRepository.countByActiveTrue();
|
||||||
|
Assert.isTrue(r1_6 == 4, "countByActiveTrue 结果应为4,实际:" + r1_6);
|
||||||
|
|
||||||
|
// ==================== 2. 比较运算符查询测试 ====================
|
||||||
|
|
||||||
|
formatLog("2.1 大于/小于/等于/不等于查询");
|
||||||
|
var r2_1 = employeeRepository.findByAgeGreaterThan(30);
|
||||||
|
Assert.isTrue(r2_1.size() == 2, "findByAgeGreaterThan(30) 结果数量应为2,实际:" + r2_1.size());
|
||||||
|
|
||||||
|
var r2_2 = employeeRepository.findByAgeLessThan(30);
|
||||||
|
Assert.isTrue(r2_2.size() == 2, "findByAgeLessThan(30) 结果数量应为2,实际:" + r2_2.size());
|
||||||
|
|
||||||
|
var r2_3 = employeeRepository.findByAgeGreaterThanEqual(30);
|
||||||
|
Assert.isTrue(r2_3.size() == 3, "findByAgeGreaterThanEqual(30) 结果数量应为3,实际:" + r2_3.size());
|
||||||
|
|
||||||
|
var r2_4 = employeeRepository.findByAgeLessThanEqual(30);
|
||||||
|
Assert.isTrue(r2_4.size() == 3, "findByAgeLessThanEqual(30) 结果数量应为3,实际:" + r2_4.size());
|
||||||
|
|
||||||
|
var r2_5 = employeeRepository.findByAgeBetween(25, 35);
|
||||||
|
Assert.isTrue(r2_5.size() == 4, "findByAgeBetween(25,35) 结果数量应为4,实际:" + r2_5.size());
|
||||||
|
|
||||||
|
formatLog("2.2 薪资范围查询");
|
||||||
|
var r2_6 = employeeRepository.findBySalaryGreaterThan(new BigDecimal("50000.00"));
|
||||||
|
Assert.isTrue(r2_6.size() == 3, "findBySalaryGreaterThan 结果数量应为3,实际:" + r2_6.size());
|
||||||
|
|
||||||
|
var r2_7 = employeeRepository.findBySalaryLessThan(new BigDecimal("50000.00"));
|
||||||
|
Assert.isTrue(r2_7.size() == 2, "findBySalaryLessThan 结果数量应为2,实际:" + r2_7.size());
|
||||||
|
|
||||||
|
var r2_8 = employeeRepository.findBySalaryBetween(new BigDecimal("40000.00"), new BigDecimal("55000.00"));
|
||||||
|
Assert.isTrue(r2_8.size() == 4, "findBySalaryBetween 结果数量应为4,实际:" + r2_8.size());
|
||||||
|
|
||||||
|
// ==================== 3. 字符串匹配查询测试 ====================
|
||||||
|
|
||||||
|
formatLog("3.1 字符串包含/开头/结尾查询");
|
||||||
|
var r3_1 = employeeRepository.findByNameContaining("Alice");
|
||||||
|
Assert.isTrue(r3_1.size() == 2, "findByNameContaining('Alice') 结果数量应为2,实际:" + r3_1.size());
|
||||||
|
|
||||||
|
var r3_2 = employeeRepository.findByNameStartingWith("A");
|
||||||
|
Assert.isTrue(r3_2.size() == 2, "findByNameStartingWith('A') 结果数量应为2,实际:" + r3_2.size());
|
||||||
|
|
||||||
|
var r3_3 = employeeRepository.findByNameEndingWith("e");
|
||||||
|
Assert.isTrue(r3_3.size() == 3, "findByNameEndingWith('e') 结果数量应为3,实际:" + r3_3.size());
|
||||||
|
|
||||||
|
var r3_4 = employeeRepository.findByNameLike("%ar%");
|
||||||
|
Assert.isTrue(r3_4.size() == 1, "findByNameLike('%ar%') 结果数量应为1,实际:" + r3_4.size());
|
||||||
|
|
||||||
|
formatLog("3.2 忽略大小写查询");
|
||||||
|
var r3_5 = employeeRepository.findByNameContainingIgnoreCase("ALICE");
|
||||||
|
Assert.isTrue(r3_5.size() == 2, "findByNameContainingIgnoreCase('ALICE') 结果数量应为2,实际:" + r3_5.size());
|
||||||
|
|
||||||
|
var r3_6 = employeeRepository.findByNameStartingWithIgnoreCase("B");
|
||||||
|
Assert.isTrue(r3_6.size() == 1, "findByNameStartingWithIgnoreCase('B') 结果数量应为1,实际:" + r3_6.size());
|
||||||
|
|
||||||
|
var r3_7 = employeeRepository.findByNameIgnoreCase("BOB");
|
||||||
|
Assert.notNull(r3_7, "findByNameIgnoreCase('BOB') 返回null");
|
||||||
|
|
||||||
|
// ==================== 4. NULL值和空值查询测试 ====================
|
||||||
|
|
||||||
|
formatLog("4.1 NULL值查询");
|
||||||
|
var r4_1 = employeeRepository.findByBonusIsNull();
|
||||||
|
Assert.isTrue(r4_1.size() == 1, "findByBonusIsNull 结果数量应为1,实际:" + r4_1.size());
|
||||||
|
Assert.isTrue(r4_1.get(0).getName().equals("David"), "findByBonusIsNull 结果应为David");
|
||||||
|
|
||||||
|
var r4_2 = employeeRepository.findByBonusIsNotNull();
|
||||||
|
Assert.isTrue(r4_2.size() == 4, "findByBonusIsNotNull 结果数量应为4,实际:" + r4_2.size());
|
||||||
|
|
||||||
|
var r4_3 = employeeRepository.findByResumeIsNull();
|
||||||
|
Assert.isTrue(r4_3.size() == 5, "findByResumeIsNull 结果数量应为5,实际:" + r4_3.size());
|
||||||
|
|
||||||
|
formatLog("4.2 空集合查询");
|
||||||
|
var r4_4 = employeeRepository.findByHobbiesEmpty();
|
||||||
|
Assert.isTrue(r4_4.size() == 1, "findByHobbiesEmpty 结果数量应为1,实际:" + r4_4.size());
|
||||||
|
|
||||||
|
var r4_5 = employeeRepository.findByHobbiesIsNotEmpty();
|
||||||
|
Assert.isTrue(r4_5.size() == 4, "findByHobbiesIsNotEmpty 结果数量应为4,实际:" + r4_5.size());
|
||||||
|
|
||||||
|
// ==================== 5. 集合成员查询测试 ====================
|
||||||
|
|
||||||
|
formatLog("5.1 IN/NOT IN 查询");
|
||||||
|
var r5_1 = employeeRepository.findByRoleIn(Set.of(Employee.Role.ADMIN));
|
||||||
|
Assert.isTrue(r5_1.size() == 2, "findByRoleIn 结果数量应为2,实际:" + r5_1.size());
|
||||||
|
|
||||||
|
var r5_2 = employeeRepository.findByRoleNotIn(Set.of(Employee.Role.ADMIN));
|
||||||
|
Assert.isTrue(r5_2.size() == 3, "findByRoleNotIn 结果数量应为3,实际:" + r5_2.size());
|
||||||
|
|
||||||
|
var r5_3 = employeeRepository.findByNameIn(List.of("Alice", "Bob"));
|
||||||
|
Assert.isTrue(r5_3.size() == 2, "findByNameIn 结果数量应为2,实际:" + r5_3.size());
|
||||||
|
|
||||||
|
var r5_4 = employeeRepository.findByNameNotIn(List.of("Charlie", "David"));
|
||||||
|
Assert.isTrue(r5_4.size() == 3, "findByNameNotIn 结果数量应为3,实际:" + r5_4.size());
|
||||||
|
|
||||||
|
var r5_5 = employeeRepository.findByAgeIn(List.of(25, 30, 35));
|
||||||
|
Assert.isTrue(r5_5.size() == 3, "findByAgeIn 结果数量应为3,实际:" + r5_5.size());
|
||||||
|
|
||||||
|
// ==================== 6. 逻辑运算查询测试 ====================
|
||||||
|
|
||||||
|
formatLog("6.1 AND 组合查询");
|
||||||
|
var r6_1 = employeeRepository.findByNameAndRole("Alice", Employee.Role.ADMIN);
|
||||||
|
Assert.isTrue(r6_1.size() == 1, "findByNameAndRole 结果数量应为1,实际:" + r6_1.size());
|
||||||
|
|
||||||
|
var r6_2 = employeeRepository.findByAgeAndActive(30, true);
|
||||||
|
Assert.isTrue(r6_2.size() == 1, "findByAgeAndActive 结果数量应为1,实际:" + r6_2.size());
|
||||||
|
|
||||||
|
var r6_3 = employeeRepository.findByNameAndSalaryGreaterThan("Alice", new BigDecimal("40000.00"));
|
||||||
|
Assert.isTrue(r6_3.size() == 1, "findByNameAndSalaryGreaterThan 结果数量应为1,实际:" + r6_3.size());
|
||||||
|
|
||||||
|
formatLog("6.2 OR 组合查询");
|
||||||
|
var r6_4 = employeeRepository.findByNameOrRole("Alice", Employee.Role.USER);
|
||||||
|
Assert.isTrue(r6_4.size() == 4, "findByNameOrRole 结果数量应为4,实际:" + r6_4.size());
|
||||||
|
|
||||||
|
var r6_5 = employeeRepository.findByAgeLessThanOrSalaryGreaterThan(30, new BigDecimal("55000.00"));
|
||||||
|
Assert.isTrue(r6_5.size() == 3, "findByAgeLessThanOrSalaryGreaterThan 结果数量应为3,实际:" + r6_5.size());
|
||||||
|
|
||||||
|
var r6_6 = employeeRepository.findByNameOrCode("Alice", "E003");
|
||||||
|
Assert.isTrue(r6_6.size() == 2, "findByNameOrCode 结果数量应为2,实际:" + r6_6.size());
|
||||||
|
|
||||||
|
formatLog("6.3 NOT 查询");
|
||||||
|
var r6_7 = employeeRepository.findByRoleNot(Employee.Role.ADMIN);
|
||||||
|
Assert.isTrue(r6_7.size() == 3, "findByRoleNot 结果数量应为3,实际:" + r6_7.size());
|
||||||
|
|
||||||
|
var r6_8 = employeeRepository.findByNameNot("Alice");
|
||||||
|
Assert.isTrue(r6_8.size() == 4, "findByNameNot 结果数量应为4,实际:" + r6_8.size());
|
||||||
|
|
||||||
|
// ==================== 7. 排序查询测试 ====================
|
||||||
|
|
||||||
|
formatLog("7.1 单字段排序查询");
|
||||||
|
var r7_1 = employeeRepository.findByActiveTrueOrderByAgeAsc();
|
||||||
|
Assert.isTrue(r7_1.size() == 4, "findByActiveTrueOrderByAgeAsc 结果数量应为4");
|
||||||
|
Assert.isTrue(r7_1.get(0).getAge() == 25, "findByActiveTrueOrderByAgeAsc 第一条年龄应为25");
|
||||||
|
|
||||||
|
var r7_2 = employeeRepository.findByActiveTrueOrderByAgeDesc();
|
||||||
|
Assert.isTrue(r7_2.size() == 4, "findByActiveTrueOrderByAgeDesc 结果数量应为4");
|
||||||
|
Assert.isTrue(r7_2.get(0).getAge() == 32, "findByActiveTrueOrderByAgeDesc 第一条年龄应为32");
|
||||||
|
|
||||||
|
var r7_3 = employeeRepository.findByActiveTrueOrderBySalaryDesc();
|
||||||
|
Assert.isTrue(r7_3.size() == 4, "findByActiveTrueOrderBySalaryDesc 结果数量应为4");
|
||||||
|
Assert.isTrue(r7_3.get(0).getSalary().compareTo(new BigDecimal("55000.00")) == 0, "findByActiveTrueOrderBySalaryDesc 第一条薪资应为55000");
|
||||||
|
|
||||||
|
formatLog("7.2 多字段排序查询");
|
||||||
|
var r7_4 = employeeRepository.findByRoleOrderByAgeDescNameAsc(Employee.Role.ADMIN);
|
||||||
|
Assert.isTrue(r7_4.size() == 2, "findByRoleOrderByAgeDescNameAsc 结果数量应为2");
|
||||||
|
|
||||||
|
// ==================== 8. 分页查询测试 ====================
|
||||||
|
|
||||||
|
formatLog("8.1 分页查询");
|
||||||
|
var r8_1 = employeeRepository.findByActiveTrue(PageRequest.of(0, 2));
|
||||||
|
Assert.isTrue(r8_1.getContent().size() == 2, "分页结果数量应为2,实际:" + r8_1.getContent().size());
|
||||||
|
Assert.isTrue(r8_1.getTotalElements() == 4, "总元素数应为4,实际:" + r8_1.getTotalElements());
|
||||||
|
|
||||||
|
var r8_2 = employeeRepository.findByRole(Employee.Role.USER, PageRequest.of(0, 10));
|
||||||
|
Assert.isTrue(r8_2.getContent().size() == 3, "findByRole 分页结果数量应为3,实际:" + r8_2.getContent().size());
|
||||||
|
|
||||||
|
var r8_3 = employeeRepository.findBySalaryGreaterThan(new BigDecimal("45000.00"), PageRequest.of(0, 2));
|
||||||
|
Assert.isTrue(r8_3.getContent().size() == 2, "findBySalaryGreaterThan 分页结果数量应为2");
|
||||||
|
|
||||||
|
// ==================== 9. 关联查询测试 ====================
|
||||||
|
|
||||||
|
formatLog("9.1 公司关联查询");
|
||||||
|
var r9_1 = employeeRepository.findByCompany(company1);
|
||||||
|
Assert.isTrue(r9_1.size() == 2, "findByCompany 结果数量应为2,实际:" + r9_1.size());
|
||||||
|
|
||||||
|
var r9_2 = employeeRepository.findByCompanyName("TechCorp");
|
||||||
|
Assert.isTrue(r9_2.size() == 2, "findByCompanyName 结果数量应为2,实际:" + r9_2.size());
|
||||||
|
|
||||||
|
var r9_3 = employeeRepository.findByCompanyNameContaining("Corp");
|
||||||
|
Assert.isTrue(r9_3.size() == 2, "findByCompanyNameContaining 结果数量应为2,实际:" + r9_3.size());
|
||||||
|
|
||||||
|
var r9_4 = employeeRepository.findByCompanyMembersGreaterThan(75);
|
||||||
|
Assert.isTrue(r9_4.size() == 2, "findByCompanyMembersGreaterThan 结果数量应为2,实际:" + r9_4.size());
|
||||||
|
|
||||||
|
formatLog("9.2 技能关联查询");
|
||||||
|
var r9_5 = employeeRepository.findBySkillsContaining(skill1);
|
||||||
|
Assert.isTrue(r9_5.size() == 3, "findBySkillsContaining 结果数量应为3,实际:" + r9_5.size());
|
||||||
|
|
||||||
|
var r9_6 = employeeRepository.findBySkillsName("Java");
|
||||||
|
Assert.isTrue(r9_6.size() == 3, "findBySkillsName 结果数量应为3,实际:" + r9_6.size());
|
||||||
|
|
||||||
|
var r9_7 = employeeRepository.findBySkillsNameContaining("Pyt");
|
||||||
|
Assert.isTrue(r9_7.size() == 2, "findBySkillsNameContaining 结果数量应为2,实际:" + r9_7.size());
|
||||||
|
|
||||||
|
var r9_8 = employeeRepository.findBySkillsNameIn(List.of("Java", "Python"));
|
||||||
|
Assert.isTrue(r9_8.size() == 3, "findBySkillsNameIn 结果数量应为3,实际:" + r9_8.size());
|
||||||
|
|
||||||
|
// ==================== 10. 嵌入式对象查询测试 ====================
|
||||||
|
|
||||||
|
formatLog("10.1 嵌入式对象查询");
|
||||||
|
var r10_1 = employeeRepository.findByAddressCity("Beijing");
|
||||||
|
Assert.isTrue(r10_1.size() == 1, "findByAddressCity 结果数量应为1,实际:" + r10_1.size());
|
||||||
|
Assert.isTrue(r10_1.get(0).getName().equals("Alice"), "findByAddressCity 结果应为Alice");
|
||||||
|
|
||||||
|
var r10_2 = employeeRepository.findByAddressCityContaining("ang");
|
||||||
|
Assert.isTrue(r10_2.size() == 2, "findByAddressCityContaining 结果数量应为2,实际:" + r10_2.size());
|
||||||
|
|
||||||
|
var r10_3 = employeeRepository.findByAddressState("Guangdong");
|
||||||
|
Assert.isTrue(r10_3.size() == 2, "findByAddressState 结果数量应为2,实际:" + r10_3.size());
|
||||||
|
|
||||||
|
var r10_4 = employeeRepository.findByAddressCountry("China");
|
||||||
|
Assert.isTrue(r10_4.size() == 5, "findByAddressCountry 结果数量应为5,实际:" + r10_4.size());
|
||||||
|
|
||||||
|
var r10_5 = employeeRepository.findByAddressCityAndAddressState("Shenzhen", "Guangdong");
|
||||||
|
Assert.isTrue(r10_5.size() == 1, "findByAddressCityAndAddressState 结果数量应为1");
|
||||||
|
|
||||||
|
// ==================== 11. 复合复杂查询测试 ====================
|
||||||
|
|
||||||
|
formatLog("11.1 多条件组合查询");
|
||||||
|
var r11_1 = employeeRepository.findByNameAndRoleAndAgeGreaterThan("Alice", Employee.Role.ADMIN, 25);
|
||||||
|
Assert.isTrue(r11_1.size() == 1, "findByNameAndRoleAndAgeGreaterThan 结果数量应为1");
|
||||||
|
|
||||||
|
var r11_2 = employeeRepository.findByRoleAndActiveTrueAndSalaryGreaterThan(Employee.Role.USER, new BigDecimal("40000.00"));
|
||||||
|
Assert.isTrue(r11_2.size() == 2, "findByRoleAndActiveTrueAndSalaryGreaterThan 结果数量应为2");
|
||||||
|
|
||||||
|
var r11_3 = employeeRepository.findByNameContainingIgnoreCaseAndActiveTrueAndAgeBetween("a", 25, 35);
|
||||||
|
Assert.isTrue(r11_3.size() == 3, "findByNameContainingIgnoreCaseAndActiveTrueAndAgeBetween 结果数量应为3");
|
||||||
|
|
||||||
|
formatLog("11.2 EXISTS语义查询");
|
||||||
|
var r11_4 = employeeRepository.findByCompanyNameContainingAndActiveTrue("Corp");
|
||||||
|
Assert.isTrue(r11_4.size() == 2, "findByCompanyNameContainingAndActiveTrue 结果数量应为2");
|
||||||
|
|
||||||
|
var r11_5 = employeeRepository.findBySkillsNameContainingAndAgeGreaterThan("Java", 25);
|
||||||
|
Assert.isTrue(r11_5.size() == 2, "findBySkillsNameContainingAndAgeGreaterThan 结果数量应为2");
|
||||||
|
|
||||||
|
// ==================== 12. DISTINCT 查询测试 ====================
|
||||||
|
|
||||||
|
formatLog("12.1 DISTINCT 查询");
|
||||||
|
var r12_1 = employeeRepository.findDistinctByRole(Employee.Role.ADMIN);
|
||||||
|
Assert.isTrue(r12_1.size() == 2, "findDistinctByRole 结果数量应为2");
|
||||||
|
|
||||||
|
// ==================== 13. TOP/LIMIT 查询测试 ====================
|
||||||
|
|
||||||
|
formatLog("13.1 TOP/LIMIT 查询");
|
||||||
|
var r13_1 = employeeRepository.findTop5BySalaryGreaterThan(new BigDecimal("40000.00"));
|
||||||
|
Assert.isTrue(r13_1.size() == 4, "findTop5BySalaryGreaterThan 结果数量应为4");
|
||||||
|
|
||||||
|
var r13_3 = employeeRepository.findFirstByRoleOrderBySalaryDesc(Employee.Role.ADMIN);
|
||||||
|
Assert.notNull(r13_3, "findFirstByRoleOrderBySalaryDesc 返回null");
|
||||||
|
Assert.isTrue(r13_3.orElseThrow().getSalary().compareTo(new BigDecimal("60000.00")) == 0, "findFirstByRoleOrderBySalaryDesc 最高薪资应为60000");
|
||||||
|
|
||||||
|
// ==================== 14. LIKE 模式查询测试 ====================
|
||||||
|
|
||||||
|
formatLog("14.1 LIKE 模式查询");
|
||||||
|
var r14_1 = employeeRepository.findByNameLikeIgnoreCase("%ALICE%");
|
||||||
|
Assert.isTrue(r14_1.size() == 2, "findByNameLikeIgnoreCase 结果数量应为2");
|
||||||
|
|
||||||
|
var r14_2 = employeeRepository.findByNameStartingWithAndRole("A", Employee.Role.ADMIN);
|
||||||
|
Assert.isTrue(r14_2.size() == 1, "findByNameStartingWithAndRole 结果数量应为1");
|
||||||
|
|
||||||
|
var r14_3 = employeeRepository.findByNameEndingWithAndAgeLessThan("e", 30);
|
||||||
|
Assert.isTrue(r14_3.size() == 1, "findByNameEndingWithAndAgeLessThan 结果数量应为1");
|
||||||
|
|
||||||
|
// ==================== 15. 其他未覆盖的有用方法测试 ====================
|
||||||
|
|
||||||
|
formatLog("15.1 聚合计数查询");
|
||||||
|
var r15_1 = employeeRepository.countByActiveTrue();
|
||||||
|
Assert.isTrue(r15_1 == 4, "countByActiveTrue 结果应为4,实际:" + r15_1);
|
||||||
|
|
||||||
|
var r15_2 = employeeRepository.countByActiveFalse();
|
||||||
|
Assert.isTrue(r15_2 == 1, "countByActiveFalse 结果应为1,实际:" + r15_2);
|
||||||
|
|
||||||
|
formatLog("15.2 技能和简历查询");
|
||||||
|
var r15_3 = employeeRepository.findByResumeIsNotNull();
|
||||||
|
Assert.isTrue(r15_3.size() == 5, "findByResumeIsNotNull 结果数量应为5,实际:" + r15_3.size());
|
||||||
|
|
||||||
|
var r15_4 = employeeRepository.findBySkillsEmpty();
|
||||||
|
Assert.isTrue(r15_4.isEmpty(), "findBySkillsEmpty 结果应该为空,实际:" + r15_4.size());
|
||||||
|
|
||||||
|
formatLog("15.3 公司成员数查询");
|
||||||
|
var r15_5 = employeeRepository.findByCompanyMembersGreaterThan(100);
|
||||||
|
Assert.isTrue(r15_5.size() == 1, "findByCompanyMembersGreaterThan(100) 结果数量应为1,实际:" + r15_5.size());
|
||||||
|
|
||||||
|
var r15_6 = employeeRepository.findByCompanyMembersGreaterThan(50);
|
||||||
|
Assert.isTrue(r15_6.size() == 3, "findByCompanyMembersGreaterThan(50) 结果数量应为3,实际:" + r15_6.size());
|
||||||
|
|
||||||
|
formatLog("15.4 城市包含查询");
|
||||||
|
var r15_7 = employeeRepository.findByAddressCityContaining("ang");
|
||||||
|
Assert.isTrue(r15_7.size() == 2, "findByAddressCityContaining('ang') 结果数量应为2,实际:" + r15_7.size());
|
||||||
|
|
||||||
|
formatLog("15.5 DISTINCT 查询");
|
||||||
|
var r15_8 = employeeRepository.findDistinctByRole(Employee.Role.USER);
|
||||||
|
Assert.isTrue(r15_8.size() == 3, "findDistinctByRole 结果数量应为3,实际:" + r15_8.size());
|
||||||
|
|
||||||
|
formatLog("15.6 TOP/LIMIT 查询");
|
||||||
|
var r15_9 = employeeRepository.findTop5BySalaryGreaterThan(new BigDecimal("40000.00"));
|
||||||
|
Assert.isTrue(r15_9.size() == 4, "findTop5BySalaryGreaterThan 结果数量应为4,实际:" + r15_9.size());
|
||||||
|
|
||||||
|
var r15_10 = employeeRepository.findFirstByRoleOrderBySalaryDesc(Employee.Role.ADMIN);
|
||||||
|
Assert.isTrue(r15_10.isPresent(), "findFirstByRoleOrderBySalaryDesc 返回null");
|
||||||
|
Assert.isTrue(r15_10.orElseThrow().getSalary().compareTo(new BigDecimal("60000.00")) == 0, "findFirstByRoleOrderBySalaryDesc 最高薪资应为60000");
|
||||||
|
|
||||||
|
formatLog("清理测试数据");
|
||||||
|
employeeRepository.deleteAllInBatch();
|
||||||
|
companyRepository.deleteAllInBatch();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
package com.lanyuanxiaoyao.service.template.database.jpa.repository;
|
package com.lanyuanxiaoyao.service.template.database.jpa.repository;
|
||||||
|
|
||||||
|
import com.lanyuanxiaoyao.service.template.database.jpa.entity.Company;
|
||||||
import com.lanyuanxiaoyao.service.template.database.jpa.entity.Employee;
|
import com.lanyuanxiaoyao.service.template.database.jpa.entity.Employee;
|
||||||
|
import com.lanyuanxiaoyao.service.template.database.jpa.entity.Skill;
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
import org.springframework.data.domain.Page;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.data.jpa.domain.Specification;
|
import org.springframework.data.jpa.domain.Specification;
|
||||||
import org.springframework.data.jpa.repository.EntityGraph;
|
import org.springframework.data.jpa.repository.EntityGraph;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
@@ -12,4 +19,194 @@ public interface EmployeeRepository extends SimpleRepository<Employee> {
|
|||||||
@EntityGraph(attributePaths = {"company"})
|
@EntityGraph(attributePaths = {"company"})
|
||||||
@Override
|
@Override
|
||||||
Optional<Employee> findOne(Specification<Employee> specification);
|
Optional<Employee> findOne(Specification<Employee> specification);
|
||||||
|
|
||||||
|
// ==================== 1. 基本字段查询 ====================
|
||||||
|
|
||||||
|
// 单字段精确匹配
|
||||||
|
List<Employee> findByName(String name);
|
||||||
|
|
||||||
|
Optional<Employee> findByCode(String code);
|
||||||
|
|
||||||
|
List<Employee> findByRole(Employee.Role role);
|
||||||
|
|
||||||
|
// 布尔值查询
|
||||||
|
List<Employee> findByActiveTrue();
|
||||||
|
|
||||||
|
List<Employee> findByActiveFalse();
|
||||||
|
|
||||||
|
long countByActiveTrue();
|
||||||
|
|
||||||
|
long countByActiveFalse();
|
||||||
|
|
||||||
|
// ==================== 2. 比较运算符查询 ====================
|
||||||
|
|
||||||
|
List<Employee> findByAgeGreaterThan(Integer age);
|
||||||
|
|
||||||
|
List<Employee> findByAgeLessThan(Integer age);
|
||||||
|
|
||||||
|
List<Employee> findByAgeGreaterThanEqual(Integer age);
|
||||||
|
|
||||||
|
List<Employee> findByAgeLessThanEqual(Integer age);
|
||||||
|
|
||||||
|
List<Employee> findByAgeBetween(Integer startAge, Integer endAge);
|
||||||
|
|
||||||
|
List<Employee> findBySalaryGreaterThan(BigDecimal salary);
|
||||||
|
|
||||||
|
List<Employee> findBySalaryLessThan(BigDecimal salary);
|
||||||
|
|
||||||
|
List<Employee> findBySalaryGreaterThanEqual(BigDecimal salary);
|
||||||
|
|
||||||
|
List<Employee> findBySalaryLessThanEqual(BigDecimal salary);
|
||||||
|
|
||||||
|
List<Employee> findBySalaryBetween(BigDecimal minSalary, BigDecimal maxSalary);
|
||||||
|
|
||||||
|
// ==================== 3. 字符串匹配查询 ====================
|
||||||
|
|
||||||
|
// 精确匹配
|
||||||
|
List<Employee> findByNameContaining(String name);
|
||||||
|
|
||||||
|
List<Employee> findByNameStartingWith(String prefix);
|
||||||
|
|
||||||
|
List<Employee> findByNameEndingWith(String suffix);
|
||||||
|
|
||||||
|
List<Employee> findByNameLike(String pattern);
|
||||||
|
|
||||||
|
// 忽略大小写
|
||||||
|
List<Employee> findByNameContainingIgnoreCase(String name);
|
||||||
|
|
||||||
|
List<Employee> findByNameStartingWithIgnoreCase(String prefix);
|
||||||
|
|
||||||
|
List<Employee> findByNameEndingWithIgnoreCase(String suffix);
|
||||||
|
|
||||||
|
List<Employee> findByNameIgnoreCase(String name);
|
||||||
|
|
||||||
|
// ==================== 4. NULL值和空值查询 ====================
|
||||||
|
|
||||||
|
List<Employee> findByBonusIsNull();
|
||||||
|
|
||||||
|
List<Employee> findByBonusIsNotNull();
|
||||||
|
|
||||||
|
List<Employee> findByResumeIsNull();
|
||||||
|
|
||||||
|
List<Employee> findByResumeIsNotNull();
|
||||||
|
|
||||||
|
List<Employee> findByHobbiesEmpty();
|
||||||
|
|
||||||
|
List<Employee> findByHobbiesIsNotEmpty();
|
||||||
|
|
||||||
|
List<Employee> findBySkillsEmpty();
|
||||||
|
|
||||||
|
List<Employee> findBySkillsIsNotEmpty();
|
||||||
|
|
||||||
|
// ==================== 5. 集合成员查询 (IN/NOT IN) ====================
|
||||||
|
|
||||||
|
List<Employee> findByRoleIn(Set<Employee.Role> roles);
|
||||||
|
|
||||||
|
List<Employee> findByRoleNotIn(Set<Employee.Role> roles);
|
||||||
|
|
||||||
|
List<Employee> findByNameIn(List<String> names);
|
||||||
|
|
||||||
|
List<Employee> findByNameNotIn(List<String> names);
|
||||||
|
|
||||||
|
List<Employee> findByAgeIn(List<Integer> ages);
|
||||||
|
|
||||||
|
List<Employee> findByAgeNotIn(List<Integer> ages);
|
||||||
|
|
||||||
|
// ==================== 6. 逻辑运算查询 (AND/OR/NOT) ====================
|
||||||
|
|
||||||
|
List<Employee> findByNameAndRole(String name, Employee.Role role);
|
||||||
|
|
||||||
|
List<Employee> findByAgeAndActive(Integer age, Boolean active);
|
||||||
|
|
||||||
|
List<Employee> findByNameAndSalaryGreaterThan(String name, BigDecimal salary);
|
||||||
|
|
||||||
|
List<Employee> findByRoleAndActiveAndAgeGreaterThan(Employee.Role role, Boolean active, Integer age);
|
||||||
|
|
||||||
|
List<Employee> findByNameOrRole(String name, Employee.Role role);
|
||||||
|
|
||||||
|
List<Employee> findByAgeLessThanOrSalaryGreaterThan(Integer age, BigDecimal salary);
|
||||||
|
|
||||||
|
List<Employee> findByNameOrCode(String name, String code);
|
||||||
|
|
||||||
|
List<Employee> findByRoleNot(Employee.Role role);
|
||||||
|
|
||||||
|
List<Employee> findByNameNot(String name);
|
||||||
|
|
||||||
|
// ==================== 7. 排序查询 ====================
|
||||||
|
|
||||||
|
List<Employee> findByActiveTrueOrderByAgeAsc();
|
||||||
|
|
||||||
|
List<Employee> findByActiveTrueOrderByAgeDesc();
|
||||||
|
|
||||||
|
List<Employee> findByActiveTrueOrderBySalaryDesc();
|
||||||
|
|
||||||
|
List<Employee> findByRoleOrderByAgeDescNameAsc(Employee.Role role);
|
||||||
|
|
||||||
|
// ==================== 8. 分页查询 ====================
|
||||||
|
|
||||||
|
Page<Employee> findByActiveTrue(Pageable pageable);
|
||||||
|
|
||||||
|
Page<Employee> findByRole(Employee.Role role, Pageable pageable);
|
||||||
|
|
||||||
|
Page<Employee> findBySalaryGreaterThan(BigDecimal salary, Pageable pageable);
|
||||||
|
|
||||||
|
// ==================== 9. 关联查询 (JOIN) ====================
|
||||||
|
|
||||||
|
List<Employee> findByCompany(Company company);
|
||||||
|
|
||||||
|
List<Employee> findByCompanyName(String companyName);
|
||||||
|
|
||||||
|
List<Employee> findByCompanyNameContaining(String companyName);
|
||||||
|
|
||||||
|
List<Employee> findByCompanyMembersGreaterThan(Integer members);
|
||||||
|
|
||||||
|
List<Employee> findBySkillsContaining(Skill skill);
|
||||||
|
|
||||||
|
List<Employee> findBySkillsName(String skillName);
|
||||||
|
|
||||||
|
List<Employee> findBySkillsNameContaining(String skillName);
|
||||||
|
|
||||||
|
List<Employee> findBySkillsNameIn(List<String> skillNames);
|
||||||
|
|
||||||
|
// ==================== 10. 嵌入式对象查询 ====================
|
||||||
|
|
||||||
|
List<Employee> findByAddressCity(String city);
|
||||||
|
|
||||||
|
List<Employee> findByAddressCityContaining(String city);
|
||||||
|
|
||||||
|
List<Employee> findByAddressState(String state);
|
||||||
|
|
||||||
|
List<Employee> findByAddressCountry(String country);
|
||||||
|
|
||||||
|
List<Employee> findByAddressCityAndAddressState(String city, String state);
|
||||||
|
|
||||||
|
// ==================== 11. 复合复杂查询 ====================
|
||||||
|
|
||||||
|
List<Employee> findByNameAndRoleAndAgeGreaterThan(String name, Employee.Role role, Integer age);
|
||||||
|
|
||||||
|
List<Employee> findByRoleAndActiveTrueAndSalaryGreaterThan(Employee.Role role, BigDecimal salary);
|
||||||
|
|
||||||
|
List<Employee> findByNameContainingIgnoreCaseAndActiveTrueAndAgeBetween(String name, Integer minAge, Integer maxAge);
|
||||||
|
|
||||||
|
List<Employee> findByCompanyNameContainingAndActiveTrue(String companyName);
|
||||||
|
|
||||||
|
List<Employee> findBySkillsNameContainingAndAgeGreaterThan(String skillName, Integer age);
|
||||||
|
|
||||||
|
// ==================== 12. DISTINCT 查询 ====================
|
||||||
|
|
||||||
|
List<Employee> findDistinctByRole(Employee.Role role);
|
||||||
|
|
||||||
|
// ==================== 13. TOP/LIMIT 查询 ====================
|
||||||
|
|
||||||
|
List<Employee> findTop5BySalaryGreaterThan(BigDecimal salary);
|
||||||
|
|
||||||
|
Optional<Employee> findFirstByRoleOrderBySalaryDesc(Employee.Role role);
|
||||||
|
|
||||||
|
// ==================== 14. LIKE 模式查询 ====================
|
||||||
|
|
||||||
|
List<Employee> findByNameLikeIgnoreCase(String pattern);
|
||||||
|
|
||||||
|
List<Employee> findByNameStartingWithAndRole(String prefix, Employee.Role role);
|
||||||
|
|
||||||
|
List<Employee> findByNameEndingWithAndAgeLessThan(String suffix, Integer age);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,16 +47,19 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
|||||||
mapper.saveOrUpdate(entities);
|
mapper.saveOrUpdate(entities);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
@Override
|
@Override
|
||||||
public Long count() {
|
public Long count() {
|
||||||
return (long) mapper.countAll(target);
|
return (long) mapper.countAll(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
@Override
|
@Override
|
||||||
public List<ENTITY> list() {
|
public List<ENTITY> list() {
|
||||||
return mapper.listAll(target);
|
return mapper.listAll(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
@Override
|
@Override
|
||||||
public List<ENTITY> list(Set<Long> ids) {
|
public List<ENTITY> list(Set<Long> ids) {
|
||||||
return mapper.listByIds(target, ids);
|
return mapper.listByIds(target, ids);
|
||||||
@@ -65,6 +68,7 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
|||||||
protected void commonPredicates(Where where) {
|
protected void commonPredicates(Where where) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
@Override
|
@Override
|
||||||
public Page<ENTITY> list(Query query) {
|
public Page<ENTITY> list(Query query) {
|
||||||
var chain = QueryChain.of(mapper, target);
|
var chain = QueryChain.of(mapper, target);
|
||||||
@@ -98,12 +102,14 @@ public abstract class SimpleServiceSupport<ENTITY extends SimpleEntity> implemen
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Named("detail")
|
@Named("detail")
|
||||||
|
@Transactional(readOnly = true)
|
||||||
@Override
|
@Override
|
||||||
public ENTITY detail(Long id) {
|
public ENTITY detail(Long id) {
|
||||||
return detailOptional(id).orElse(null);
|
return detailOptional(id).orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Named("detailOrThrow")
|
@Named("detailOrThrow")
|
||||||
|
@Transactional(readOnly = true)
|
||||||
@Override
|
@Override
|
||||||
public ENTITY detailOrThrow(Long id) {
|
public ENTITY detailOrThrow(Long id) {
|
||||||
return detailOptional(id).orElseThrow(() -> new IdNotFoundException(id));
|
return detailOptional(id).orElseThrow(() -> new IdNotFoundException(id));
|
||||||
|
|||||||
Reference in New Issue
Block a user