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

9.2 KiB
Raw Blame History

Database 模块使用指南

单表 CRUD → REST 接口快速实现框架。

使用模式

Web 应用

引入 database 模块,创建 Entity → Repository → Service → Controller实现 REST 接口。

非 Web 应用

无需引入 web 依赖database 模块的 spring-boot-starter-web scope 为 provided)。

仅使用 Entity → Repository → Service直接注入 Service 使用:

@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. 添加依赖

<dependency>
  <groupId>com.lanyuanxiaoyao</groupId>
  <artifactId>spring-boot-service-template-database</artifactId>
</dependency>

2. 创建实体

@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(雪花算法)、createdTimemodifiedTime(自动填充)。

3. 创建 Repository

@Repository
public interface EmployeeRepository extends SimpleRepository<Employee> {}

继承能力CRUD、分页、Specification、QueryDSL、Example。

4. 创建 Service

@Slf4j
@Service
public class EmployeeService extends SimpleServiceSupport<Employee> {
  public EmployeeService(EmployeeRepository repository) {
    super(repository);
  }
}

自动获得完整 CRUD 能力。

5. 创建 ControllerWeb 应用)

@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) {}
}

实现三个 MappersaveItemMapper(), listItemMapper(), detailItemMapper()

代码生成

DatabaseHelper.generateBasicFiles(
  "com.example.entity",              // 实体包
  "com.example",                     // 项目根包
  "./src/main/java/com/example",     // 源码路径
  false                              // 是否覆盖
);

生成 Repository、Service、Controller。

API 接口Web 应用)

POST /{entity}/save

保存/更新实体。

请求(新增):

{"name": "张三", "departmentId": 1, "email": "zhangsan@example.com"}

请求(更新):

{"id": 123456789, "name": "李四"}

响应:

{"status": 0, "message": "OK", "data": 123456789}

特性:不传 id 为新增,传 id 为更新(仅更新非 null 字段)。

GET/POST /{entity}/list

GET获取全部列表

POST条件查询

{
  "query": {
    "equal": {"departmentId": 1},
    "like": {"name": "%张%"},
    "greatEqual": {"createdTime": "2026-01-01 00:00:00"}
  },
  "sort": [{"column": "createdTime", "direction": "DESC"}],
  "page": {"index": 1, "size": 20}
}

响应:

{"status": 0, "message": "OK", "data": {"items": [...], "total": 100}}

GET /{entity}/detail/{id}

响应:

{"status": 0, "message": "OK", "data": {"id": 123, "name": "张三", ...}}

ID 不存在返回 500。

GET /{entity}/remove/{id}

响应:

{"status": 0, "message": "OK", "data": null}

查询条件

Query 结构

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"]

排序

{"sort": [{"column": "createdTime", "direction": "DESC"}]}

direction: ASC 升序,DESC 降序

分页

{"page": {"index": 1, "size": 20}}

index 从 1 开始,默认 (1, 10),无排序默认 createdTime DESC

高级用法

扩展 Service

@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 查询

public List<Employee> findActiveEmployees() {
  QEmployee q = QEmployee.employee;
  return repository.findAll(q.status.eq("ACTIVE").and(q.deleted.isFalse()));
}

MapStruct Mapper

@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

关联关系

@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 问题。

索引

@Entity
@Table(name = "employee", indexes = {
  @Index(name = "idx_department", columnList = "department_id")
})
public class Employee extends SimpleEntity { ... }

工具类

DatabaseHelper

// 生成 DDL
DatabaseHelper.generateDDL(
  "com.example.entity", "./sql", MySQL8Dialect.class,
  "jdbc:mysql://localhost:3306/test", "root", "password",
  com.mysql.cj.jdbc.Driver.class
);

SnowflakeHelper

Long id = SnowflakeHelper.next();

常见问题

Q: 非 Web 应用如何使用?

A: 不引入 web 依赖,创建 Entity → Repository → Service直接注入 Service 使用。

Q: 如何实现软删除?

A: 添加 deleted 字段,重写 commonPredicates() 过滤,覆盖 remove() 改为更新。

Q: 如何处理复杂查询?

A: 使用 QueryDSL 或 Repository @Query 方法:

@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/