1
0
Files
spring-boot-service-template/docs/database-usage.md

409 lines
9.2 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.
# 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. 创建 ControllerWeb 应用)
```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/`