Files
java-g/README.md
2026-01-23 17:41:45 +08:00

112 lines
4.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Java Low-Code / Dynamic Query Engine
本项目展示了两种在 Java (Spring Boot) 环境下实现“低代码”后端查询引擎的方案。这两种方案都旨在解决同一个问题:**前端按需索取数据GraphQL 风格),后端自动构建查询,无需手动编写 CRUD 代码。**
## 方案对比
| 特性 | 方案 A: Hibernate/JPA + Squiggly | 方案 B: MyBatis 动态生成引擎 |
| :--- | :--- | :--- |
| **核心理念** | 运行时动态构建 Predicate + JSON 裁剪 | 自动生成优化的 SQL 和 ResultMap XML |
| **查询构建** | JPA Criteria API | 字符串拼接 / AST 构建 SQL |
| **字段裁剪** | 查全量数据 -> 内存中 JSON 裁剪 (Squiggly) | 数据库层直接只查所需字段 (Select Partial) |
| **关联查询** | 依赖 Hibernate 懒加载 (可能导致 N+1) | 单条 SQL LEFT JOIN (性能最优) |
| **递归/深层** | 容易 StackOverflow (需 `@JsonIdentityInfo`) | 通过 ResultMap + ColumnPrefix 完美支持无限递归 |
| **适用场景** | 快速开发、中后台管理、逻辑简单 | 高性能 API、复杂报表、移动端接口 |
---
## 方案 A: Hibernate/JPA 策略
利用 Spring Data JPA 的 `Specification` 和 Jackson 的过滤器实现。
### 特性
1. **通用 Controller/Repository**:只需继承 `BaseController``BaseRepository`,即可获得完整的 CRUD 和动态搜索能力。
2. **动态过滤**:支持 `key=value` 精确匹配和 `key=val*` 模糊匹配。
3. **动态投影**:通过 `fields=id,title,author.name` 参数,后端自动裁剪返回的 JSON 结构。
4. **全能写操作**
* **Create (POST)**: 支持级联创建(如一次性提交 Book + Author
* **Update (PUT)**: 全量更新。
* **Patch (PATCH)**: 支持**深度合并 (Deep Merge)**,仅更新 JSON 中提供的字段,保留嵌套对象的其他属性。
* **Delete (DELETE)**: 支持级联删除。
### 代码示例
```java
// 继承通用基类
public interface BookRepository extends BaseRepository<Book, Long> {}
// 1. 动态查询
// GET /books?author.name=John*&fields=title,author.name
// 2. 级联创建 (POST /books)
/*
{
"title": "New Book",
"author": { "name": "New Author" } // 同时创建 Author
}
*/
// 3. 局部更新 (PATCH /books/1)
// 仅修改标题Author 关联保持不变
/*
{ "title": "Updated Title" }
*/
```
---
## 方案 B: MyBatis 动态生成引擎
这是本项目最核心的创新点。我们要解决 MyBatis **"手写 XML 太累,自动生成又不灵活"** 的痛点。
我们实现了一个**运行时 XML 生成器** (`MyBatisGeneratorUtils`),它能根据请求动态生成完整的 MyBatis Mapper XML。
### 核心技术点
1. **递归 ResultMap 复用**
利用 MyBatis 的 `columnPrefix` 特性,我们只需要定义一套标准的 ResultMap`BookMap`, `AuthorMap`),就可以通过前缀(`author$`, `books$`)实现无限层级的嵌套映射。
2. **智能别名策略**
生成的 SQL 别名严格遵循 `path$ColumnName` 格式(例如 `book$author$publish_month`。MyBatis 在处理 ResultMap 时会逐层剥离前缀,最终将 `publish_month` 映射到正确的实体属性上。这解决了递归查询中列名冲突和映射失效的问题。
3. **一对多 (List) 自动折叠**
引擎会自动识别 `@OneToMany` 关系,生成正确的 `JOIN` 条件,并**强制投影 ID 列**。这确保了 MyBatis 能够正确地进行结果集去重Result Folding将多行数据合并为一个对象列表。
### 生成效果示例
**输入**:查询书籍,要求返回 `title``author.books.title`
**自动生成的 SQL**
```sql
SELECT
book.id AS id,
book.title AS title,
book$author.id AS author$id,
book$author$books.id AS author$books$id,
book$author$books.title AS author$books$title
FROM book book
LEFT JOIN author book$author ON book.author_id = book$author.id
LEFT JOIN book book$author$books ON book$author.id = book$author$books.author_id
```
**自动生成的 XML**(片段):
```xml
<resultMap id="AuthorMap" type="Author">
<!-- 引用 BookMap前缀为 books$ -->
<collection property="books" resultMap="BookMap" columnPrefix="books$"/>
</resultMap>
```
### 如何使用
目前引擎逻辑位于 `com.example.demo.util.MyBatisGeneratorUtils`,并配有完整的单元测试 `MyBatisGeneratorUtilsTest`。下一步计划是将其封装为 `MyBatisDynamicService`,在 Controller 中直接调用。
---
## 快速开始
1. **环境**Java 17+, Maven
2. **运行测试**
```bash
mvn test -Dtest=MyBatisGeneratorUtilsTest
```
查看控制台输出,观察自动生成的 SQL 和 XML 结构。