409 lines
9.2 KiB
Markdown
409 lines
9.2 KiB
Markdown
# Database 模块使用指南
|
||
|
||
单表 CRUD → REST 接口快速实现框架。
|
||
|
||
## 使用模式
|
||
|
||
### Web 应用
|
||
|
||
引入 database 模块,创建 Entity → Repository → Service → Controller,实现 REST 接口。
|
||
|
||
### 非 Web 应用
|
||
|
||
**无需引入 web 依赖**(database 模块的 `spring-boot-starter-web` scope 为 `provided`)。
|
||
|
||
仅使用 Entity → Repository → Service,直接注入 Service 使用:
|
||
|
||
```java
|
||
@SpringBootApplication
|
||
public class Application implements CommandLineRunner {
|
||
@Autowired
|
||
private EmployeeService service;
|
||
|
||
@Override
|
||
public void run(String... args) {
|
||
Employee emp = new Employee();
|
||
emp.setName("张三");
|
||
Long id = service.save(emp);
|
||
Employee found = service.detail(id);
|
||
List<Employee> list = service.list();
|
||
}
|
||
}
|
||
```
|
||
|
||
适用:批处理、定时任务、数据迁移、命令行应用、后台服务。
|
||
|
||
## 快速开始
|
||
|
||
### 1. 添加依赖
|
||
|
||
```xml
|
||
<dependency>
|
||
<groupId>com.lanyuanxiaoyao</groupId>
|
||
<artifactId>spring-boot-service-template-database</artifactId>
|
||
</dependency>
|
||
```
|
||
|
||
### 2. 创建实体
|
||
|
||
```java
|
||
@Getter @Setter
|
||
@ToString(callSuper = true)
|
||
@FieldNameConstants
|
||
@Entity
|
||
@Table(name = "employee")
|
||
public class Employee extends SimpleEntity {
|
||
@Column(comment = "员工姓名", nullable = false)
|
||
private String name;
|
||
|
||
@Column(comment = "部门ID")
|
||
private Long departmentId;
|
||
|
||
@Column(comment = "邮箱")
|
||
private String email;
|
||
}
|
||
```
|
||
|
||
继承 `SimpleEntity` 自动获得 `id`(雪花算法)、`createdTime`、`modifiedTime`(自动填充)。
|
||
|
||
### 3. 创建 Repository
|
||
|
||
```java
|
||
@Repository
|
||
public interface EmployeeRepository extends SimpleRepository<Employee> {}
|
||
```
|
||
|
||
继承能力:CRUD、分页、Specification、QueryDSL、Example。
|
||
|
||
### 4. 创建 Service
|
||
|
||
```java
|
||
@Slf4j
|
||
@Service
|
||
public class EmployeeService extends SimpleServiceSupport<Employee> {
|
||
public EmployeeService(EmployeeRepository repository) {
|
||
super(repository);
|
||
}
|
||
}
|
||
```
|
||
|
||
自动获得完整 CRUD 能力。
|
||
|
||
### 5. 创建 Controller(Web 应用)
|
||
|
||
```java
|
||
@Slf4j
|
||
@RestController
|
||
@RequestMapping("employee")
|
||
public class EmployeeController
|
||
extends SimpleControllerSupport<Employee, EmployeeController.SaveItem, EmployeeController.ListItem, EmployeeController.DetailItem> {
|
||
|
||
private final EmployeeService service;
|
||
|
||
public EmployeeController(EmployeeService service) {
|
||
super(service);
|
||
this.service = service;
|
||
}
|
||
|
||
@Override
|
||
protected Function<SaveItem, Employee> saveItemMapper() {
|
||
return item -> {
|
||
Employee entity = new Employee();
|
||
entity.setId(item.id());
|
||
entity.setName(item.name());
|
||
entity.setDepartmentId(item.departmentId());
|
||
entity.setEmail(item.email());
|
||
return entity;
|
||
};
|
||
}
|
||
|
||
@Override
|
||
protected Function<Employee, ListItem> listItemMapper() {
|
||
return entity -> new ListItem(
|
||
entity.getId(), entity.getName(), entity.getDepartmentId(),
|
||
entity.getEmail(), entity.getCreatedTime()
|
||
);
|
||
}
|
||
|
||
@Override
|
||
protected Function<Employee, DetailItem> detailItemMapper() {
|
||
return entity -> new DetailItem(
|
||
entity.getId(), entity.getName(), entity.getDepartmentId(),
|
||
entity.getEmail(), entity.getCreatedTime(), entity.getModifiedTime()
|
||
);
|
||
}
|
||
|
||
public record SaveItem(Long id, String name, Long departmentId, String email) {}
|
||
public record ListItem(Long id, String name, Long departmentId, String email, LocalDateTime createdTime) {}
|
||
public record DetailItem(Long id, String name, Long departmentId, String email, LocalDateTime createdTime, LocalDateTime modifiedTime) {}
|
||
}
|
||
```
|
||
|
||
实现三个 Mapper:`saveItemMapper()`, `listItemMapper()`, `detailItemMapper()`
|
||
|
||
## 代码生成
|
||
|
||
```java
|
||
DatabaseHelper.generateBasicFiles(
|
||
"com.example.entity", // 实体包
|
||
"com.example", // 项目根包
|
||
"./src/main/java/com/example", // 源码路径
|
||
false // 是否覆盖
|
||
);
|
||
```
|
||
|
||
生成 Repository、Service、Controller。
|
||
|
||
## API 接口(Web 应用)
|
||
|
||
### POST /{entity}/save
|
||
|
||
保存/更新实体。
|
||
|
||
请求(新增):
|
||
```json
|
||
{"name": "张三", "departmentId": 1, "email": "zhangsan@example.com"}
|
||
```
|
||
|
||
请求(更新):
|
||
```json
|
||
{"id": 123456789, "name": "李四"}
|
||
```
|
||
|
||
响应:
|
||
```json
|
||
{"status": 0, "message": "OK", "data": 123456789}
|
||
```
|
||
|
||
特性:不传 id 为新增,传 id 为更新(仅更新非 null 字段)。
|
||
|
||
### GET/POST /{entity}/list
|
||
|
||
GET:获取全部列表
|
||
|
||
POST:条件查询
|
||
```json
|
||
{
|
||
"query": {
|
||
"equal": {"departmentId": 1},
|
||
"like": {"name": "%张%"},
|
||
"greatEqual": {"createdTime": "2026-01-01 00:00:00"}
|
||
},
|
||
"sort": [{"column": "createdTime", "direction": "DESC"}],
|
||
"page": {"index": 1, "size": 20}
|
||
}
|
||
```
|
||
|
||
响应:
|
||
```json
|
||
{"status": 0, "message": "OK", "data": {"items": [...], "total": 100}}
|
||
```
|
||
|
||
### GET /{entity}/detail/{id}
|
||
|
||
响应:
|
||
```json
|
||
{"status": 0, "message": "OK", "data": {"id": 123, "name": "张三", ...}}
|
||
```
|
||
|
||
ID 不存在返回 500。
|
||
|
||
### GET /{entity}/remove/{id}
|
||
|
||
响应:
|
||
```json
|
||
{"status": 0, "message": "OK", "data": null}
|
||
```
|
||
|
||
## 查询条件
|
||
|
||
### Query 结构
|
||
|
||
```java
|
||
Query(
|
||
query: Queryable, // 查询条件
|
||
sort: List<Sortable>, // 排序
|
||
page: Pageable // 分页
|
||
)
|
||
```
|
||
|
||
### 查询操作
|
||
|
||
| 操作 | 类型 | 示例 |
|
||
|---|---|---|
|
||
| equal | Map | `{"name": "张三"}` |
|
||
| notEqual | Map | `{"status": "DELETED"}` |
|
||
| like | Map | `{"name": "%张%"}` |
|
||
| contain | Map | `{"name": "张"}` → `%张%` |
|
||
| startWith | Map | `{"name": "张"}` → `张%` |
|
||
| endWith | Map | `{"name": "三"}` → `%三` |
|
||
| great/greatEqual | Map | `{"age": 18}` |
|
||
| less/lessEqual | Map | `{"age": 60}` |
|
||
| between | Map | `{"age": {"start": 18, "end": 60}}` |
|
||
| inside | Map | `{"id": [1, 2, 3]}` |
|
||
| notInside | Map | `{"status": ["DELETED"]}` |
|
||
| nullEqual | List | `["deletedAt"]` |
|
||
| notNullEqual | List | `["email"]` |
|
||
|
||
### 排序
|
||
|
||
```json
|
||
{"sort": [{"column": "createdTime", "direction": "DESC"}]}
|
||
```
|
||
|
||
direction: `ASC` 升序,`DESC` 降序
|
||
|
||
### 分页
|
||
|
||
```json
|
||
{"page": {"index": 1, "size": 20}}
|
||
```
|
||
|
||
index 从 1 开始,默认 `(1, 10)`,无排序默认 `createdTime DESC`
|
||
|
||
## 高级用法
|
||
|
||
### 扩展 Service
|
||
|
||
```java
|
||
@Service
|
||
public class EmployeeService extends SimpleServiceSupport<Employee> {
|
||
private final EmployeeRepository repository;
|
||
|
||
public EmployeeService(EmployeeRepository repository) {
|
||
super(repository);
|
||
this.repository = repository;
|
||
}
|
||
|
||
// 自定义方法
|
||
public List<Employee> findByDepartmentId(Long departmentId) {
|
||
return repository.findAll(
|
||
(root, query, builder) -> builder.equal(root.get("departmentId"), departmentId)
|
||
);
|
||
}
|
||
|
||
// 全局过滤条件
|
||
@Override
|
||
protected Predicate commonPredicates(Root<Employee> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
|
||
return builder.equal(root.get("deleted"), false); // 软删除过滤
|
||
}
|
||
}
|
||
```
|
||
|
||
### QueryDSL 查询
|
||
|
||
```java
|
||
public List<Employee> findActiveEmployees() {
|
||
QEmployee q = QEmployee.employee;
|
||
return repository.findAll(q.status.eq("ACTIVE").and(q.deleted.isFalse()));
|
||
}
|
||
```
|
||
|
||
### MapStruct Mapper
|
||
|
||
```java
|
||
@Mapper
|
||
public interface EmployeeMapper {
|
||
Employee toEntity(SaveItem item);
|
||
ListItem toListItem(Employee entity);
|
||
DetailItem toDetailItem(Employee entity);
|
||
}
|
||
|
||
// Controller 中使用
|
||
@Override
|
||
protected Function<SaveItem, Employee> saveItemMapper() {
|
||
return mapper::toEntity;
|
||
}
|
||
```
|
||
|
||
## 实体设计
|
||
|
||
### 字段类型
|
||
|
||
- ID: `Long`(雪花算法)
|
||
- 时间: `LocalDateTime`
|
||
- 金额: `BigDecimal`
|
||
- 枚举: Java enum(存储为字符串)
|
||
- 布尔: `Boolean`
|
||
|
||
### 关联关系
|
||
|
||
```java
|
||
@Entity
|
||
public class Order extends SimpleEntity {
|
||
@ManyToOne
|
||
@JoinColumn(name = "customer_id")
|
||
private Customer customer;
|
||
|
||
@OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
|
||
private List<OrderItem> items;
|
||
}
|
||
```
|
||
|
||
注意:谨慎使用 `@OneToMany`,可能导致 N+1 问题。
|
||
|
||
### 索引
|
||
|
||
```java
|
||
@Entity
|
||
@Table(name = "employee", indexes = {
|
||
@Index(name = "idx_department", columnList = "department_id")
|
||
})
|
||
public class Employee extends SimpleEntity { ... }
|
||
```
|
||
|
||
## 工具类
|
||
|
||
### DatabaseHelper
|
||
|
||
```java
|
||
// 生成 DDL
|
||
DatabaseHelper.generateDDL(
|
||
"com.example.entity", "./sql", MySQL8Dialect.class,
|
||
"jdbc:mysql://localhost:3306/test", "root", "password",
|
||
com.mysql.cj.jdbc.Driver.class
|
||
);
|
||
```
|
||
|
||
### SnowflakeHelper
|
||
|
||
```java
|
||
Long id = SnowflakeHelper.next();
|
||
```
|
||
|
||
## 常见问题
|
||
|
||
**Q: 非 Web 应用如何使用?**
|
||
|
||
A: 不引入 web 依赖,创建 Entity → Repository → Service,直接注入 Service 使用。
|
||
|
||
**Q: 如何实现软删除?**
|
||
|
||
A: 添加 `deleted` 字段,重写 `commonPredicates()` 过滤,覆盖 `remove()` 改为更新。
|
||
|
||
**Q: 如何处理复杂查询?**
|
||
|
||
A: 使用 QueryDSL 或 Repository `@Query` 方法:
|
||
```java
|
||
@Query("SELECT e FROM Employee e WHERE e.departmentId = :deptId")
|
||
List<Employee> findByDepartment(@Param("deptId") Long deptId);
|
||
```
|
||
|
||
**Q: 如何批量插入?**
|
||
|
||
A: `service.save(entities)` 或 `repository.saveAll(entities)`
|
||
|
||
**Q: 查询条件支持关联对象吗?**
|
||
|
||
A: 支持,使用多级路径如 `"department.name"`
|
||
|
||
## 最佳实践
|
||
|
||
1. DTO 设计:SaveItem 可修改字段,ListItem 列表字段,DetailItem 完整字段
|
||
2. 事务:Service 方法已加事务,无需重复
|
||
3. 性能:列表查询避免关联对象,使用投影或 DTO
|
||
4. 代码生成:初期脚手架生成,后期手动调整
|
||
|
||
## 测试用例
|
||
|
||
`src/test/java/.../integration/` |