增删改查 (crud)
简介
continew-starter-extension-crud-mp
是 ContiNew Starter 提供的基于 MyBatis Plus 实现的 CRUD(增删改查)扩展模块,旨在提供开箱即用的 RESTful API 快速开发解决方案。通过继承抽象基类,可快速实现标准的分页查询、列表查询、详情查询、新增、修改、删除等接口,大幅减少重复编码工作,统一项目规范。
主要特性
- 开箱即用的三层架构:基于 MyBatis Plus 实现完整的 Controller => Service => Mapper 三层 CRUD 能力
- Controller 层:提供分页查询、列表查询、树列表查询、详情查询、创建、修改、删除、批量删除、导出、字典列表(下拉选项场景)、树型字典列表(树型下拉选项场景)等标准 API
- Service 层:提供对应 API 实现,包含灵活的生命周期钩子(beforeCreate、afterCreate 等)
- Mapper 层:在 MP BaseMapper 基础上提供优雅的 Lambda CRUD API
- 灵活扩展机制:支持通过重写父类方法或实现特定接口进行功能扩展(参考
continew-admin/continew-common/base
包实现)
快速开始
引入依赖
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-extension-crud-mp</artifactId>
</dependency>
添加配置
配置详情请查看:top.continew.starter.extension.crud.autoconfigure.CrudProperties
。
--- ### CRUD 配置
continew-starter.crud:
## 树型结构字典映射配置,用于查询树型结构字典列表 API(树型结构下拉选项等场景)
## 请根据对应前端 UI 调整字段名
tree:
id-key: key
name-key: title
weight-key: sort
编写 POJO
编写 POJO 类:实体类(XxxDO)、列表响应参数类(XxxResp)、详情响应参数类(XxxDetailResp)、查询条件类(XxxQuery)、创建或修改请求参数类(XxxReq)。
import top.continew.starter.extension.crud.model.entity.BaseIdDO;
import top.continew.starter.extension.crud.annotation.DictModel;
/**
* 用户实体
*
* @author Charles7c
*/
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@DictModel(labelKey = "nickname", extraKeys = {"username"}) // 用于查询字典列表 API 使用
@TableName("sys_user")
public class UserDO extends BaseIdDO {
/**
* 用户名
*/
private String username;
/**
* 昵称
*/
private String nickname;
// 其他略...
}
/**
* 用户响应参数
*
* @author Charles7c
*/
@Data
@Schema(description = "用户响应参数")
public class UserResp {
/**
* 用户名
*/
@Schema(description = "用户名", example = "zhangsan")
private String username;
/**
* 昵称
*/
@Schema(description = "昵称", example = "张三")
private String nickname;
// 其他略...
}
/**
* 用户详情响应参数
*
* @author Charles7c
*/
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Schema(description = "用户详情响应参数")
public class UserDetailResp extends UserResp {
/**
* 最后一次修改密码时间
*/
@Schema(description = "最后一次修改密码时间", example = "2023-08-08 08:08:08", type = "string")
private LocalDateTime pwdResetTime;
// 其他略...
}
/**
* 用户查询条件
*
* @author Charles7c
*/
@Data
@Schema(description = "用户查询条件")
public class UserQuery {
/**
* 昵称
*/
@Schema(description = "昵称", example = "张三")
private String nickname;
// 其他略...
}
/**
* 用户创建或修改请求参数
*
* @author Charles7c
*/
@Data
@Schema(description = "用户创建或修改请求参数")
public class UserReq {
/**
* 用户名
*/
@Schema(description = "用户名", example = "zhangsan")
@NotBlank(message = "用户名不能为空")
private String username;
/**
* 昵称
*/
@Schema(description = "昵称", example = "张三")
@NotBlank(message = "昵称不能为空")
private String nickname;
// 其他略...
}
编写 Mapper
创建 Mapper 接口,继承 top.continew.starter.data.mapper.BaseMapper
。
import top.continew.starter.data.mapper.BaseMapper;
/**
* 用户 Mapper
*
* @author Charles7c
*/
public interface UserMapper extends BaseMapper<UserDO> {
}
编写 Service 接口与实现
创建 Service 接口和实现类,继承 CrudService
和 CrudServiceImpl
:
import top.continew.starter.extension.crud.service.CrudService;
/**
* 用户业务接口
* @author Charles7c
*/
public interface UserService extends CrudService<UserResp, UserDetailResp, UserQuery, UserReq> {
}
import top.continew.starter.extension.crud.service.CrudServiceImpl;
/**
* 用户业务实现
* @author Charles7c
*/
@Slf4j
@Validated // 启用 Service 层参数校验
@Service
public class UserServiceImpl extends CrudServiceImpl<UserMapper, UserDO, UserResp, UserDetailResp, UserQuery, UserReq> implements UserService {
// 可根据需要重写父类方法进行自定义扩展
@Override
protected void beforeCreate(UserReq req) {
log.info("创建用户前置处理: {}", req);
// 自定义业务逻辑
}
@Override
protected void afterCreate(UserReq req, UserDO entity) {
log.info("创建用户后置处理: {}", req);
// 自定义业务逻辑
}
}
编写 Controller
创建 Controller,继承 AbstractCrudController
并添加 @CrudRequestMapping
注解,指定好访问路径和需要的 API 操作:
import top.continew.starter.extension.crud.annotation.CrudApi;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.AbstractCrudController;
import top.continew.starter.extension.crud.enums.Api;
/**
* 用户管理 API
*
* @author Charles7c
*/
@RestController
@Tag(name = "用户管理", description = "用户管理相关 API")
@CrudRequestMapping(value = "/system/users", api = {Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE, Api.EXPORT, Api.DICT})
public class UserController extends AbstractCrudController<UserService, UserResp, UserDetailResp, UserQuery, UserReq> {
// 可根据需要重写父类方法进行自定义扩展
@CrudApi(Api.CREATE)
@Override
@Operation(summary = "新增数据", description = "新增数据")
public IdResp<Long> create(@RequestBody @Valid UserReq req) {
return super.create(req);
}
}
启用 CRUD
在 Spring Boot 启动类添加 @EnableCrudApi
注解启用 CRUD 能力。
@EnableCrudApi
@SpringBootApplication
public class ContiNewDemoApplication {
public static void main(String[] args) {
SpringApplication.run(ContiNewDemoApplication.class, args);
}
}
启动项目,查看 Swagger 接口文档,即可看到你配置的 API:分页查询列表、查询详情、创建数据、修改数据、删除数据、导出数据、查询字典列表。
用于方法级别,标记 CRUD 接口的具体操作类型(用于通过 AOP 增强 Controller 层接口方法,可重写 preHandler 方法进行自定义扩展,例如:权限校验或增强校验等):
查询字典列表
普通字典 @DictModel
@DictModel
用于定义字典结构映射,在查询字典列表 API 中会根据字典定义来返回结果。
查询字典列表 API:适合于下拉列表场景,例如:在用户创建表单中,我们需要提供一个查询角色列表的下拉选项 API。
/**
* 字典结构映射
*
* @author Charles7c
* @since 2.1.0
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DictModel {
/**
* 标签字段名
*
* @return 标签字段名
*/
String labelKey() default "name";
/**
* 值字段名
*
* @return 值字段名
*/
String valueKey() default "id";
/**
* 额外信息字段名
*
* @return 额外信息字段名
*/
String[] extraKeys() default {};
}
树型字典
定义树型字典结构映射,在查询树型字典列表 API 中会根据配置定义来返回结果。
查询树型字典列表 API:适合于下拉列表场景,例如:在用户创建表单中,我们需要提供一个查询树型部门列表的下拉选项 API。
--- ### CRUD 配置
continew-starter.crud:
## 树型结构字典映射配置,用于查询树型结构字典列表 API(树型结构下拉选项等场景)
## 请根据对应前端 UI 调整字段名
tree:
id-key: key
name-key: title
weight-key: sort
注意: 与查询字典(dictTree,简单结构)API 有所区别的是查询树型列表 API(tree,复杂结构),它需要在对应实体上配置 @TreeField
来进行字段映射。
核心类
CrudService 接口
定义 CRUD 操作的核心方法,我们还为 CRUD 方法增加了参数校验,如果你需要在 Service 层也拥有参数校验能力,那么只需要在 ServiceImpl 上添加 @Validated 注解即可:
/**
* CRUD 业务接口
*
* @param <L> 列表类型
* @param <D> 详情类型
* @param <Q> 查询条件类型
* @param <C> 创建或修改请求参数类型
* @author Charles7c
* @since 1.0.0
*/
public interface CrudService<L, D, Q, C> {
/**
* 分页查询列表
*
* @param query 查询条件
* @param pageQuery 分页查询条件
* @return 分页列表信息
*/
BasePageResp<L> page(@Valid Q query, @Valid PageQuery pageQuery);
/**
* 查询列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 列表信息
*/
List<L> list(@Valid Q query, @Valid SortQuery sortQuery);
/**
* 查询树列表(多个根节点)
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段,简单树(例如:下拉列表)使用 CrudTreeDictModelProperties
* 全局树型字典映射配置,复杂树(例如:表格)使用 @TreeField 局部结构配置)
* @return 树列表信息
* @see TreeField
* @see top.continew.starter.extension.crud.autoconfigure.CrudTreeDictModelProperties
*/
List<Tree<Long>> tree(@Valid Q query, @Valid SortQuery sortQuery, boolean isSimple);
/**
* 查询树列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @param isSimple 是否为简单树结构(不包含基本树结构之外的扩展字段,简单树(下拉列表)使用全局配置结构,复杂树(表格)使用 @TreeField 局部配置)
* @param isSingleRoot 是否为单个根节点
* @return 树列表信息
* @author lishuyan
* @since 2.13.3
* @see TreeField
* @see top.continew.starter.extension.crud.autoconfigure.CrudTreeDictModelProperties
*/
List<Tree<Long>> tree(@Valid Q query, @Valid SortQuery sortQuery, boolean isSimple, boolean isSingleRoot);
/**
* 查询详情
*
* @param id ID
* @return 详情信息
*/
D get(Long id);
/**
* 创建
*
* @param req 创建请求参数
* @return 自增 ID
*/
@Validated(CrudValidationGroup.Create.class)
Long create(@Valid C req);
/**
* 修改
*
* @param req 修改请求参数
* @param id ID
*/
@Validated(CrudValidationGroup.Update.class)
void update(@Valid C req, Long id);
/**
* 删除
*
* @param ids ID 列表
*/
void delete(@NotEmpty(message = "ID 不能为空") List<Long> ids);
/**
* 导出
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @param response 响应对象
*/
void export(@Valid Q query, @Valid SortQuery sortQuery, HttpServletResponse response);
/**
* 查询字典列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 字典列表信息
* @since 2.1.0
* @see top.continew.starter.extension.crud.annotation.DictModel
*/
List<LabelValueResp> dict(@Valid Q query, @Valid SortQuery sortQuery);
}
CrudServiceImpl 实现类
MyBatis Plus实现的CRUD服务基类,提供默认实现:
/**
* CRUD 业务实现基类
*
* @param <M> Mapper 接口
* @param <T> 实体类型
* @param <L> 列表类型
* @param <D> 详情类型
* @param <Q> 查询条件类型
* @param <C> 创建或修改参数类型
* @author Charles7c
* @since 1.0.0
*/
public class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D, Q, C> extends ServiceImpl<M, T> implements CrudService<L, D, Q, C> {
// 实现 CRUD 操作的具体方法略...,详情请查看源码
/**
* 填充数据
*
* @param obj 待填充信息
*/
protected void fill(Object obj) {
}
/**
* 新增前置处理
*
* @param req 创建信息
*/
protected void beforeCreate(C req) {
/* 新增前置处理 */
}
/**
* 修改前置处理
*
* @param req 修改信息
* @param id ID
*/
protected void beforeUpdate(C req, Long id) {
/* 修改前置处理 */
}
/**
* 删除前置处理
*
* @param ids ID 列表
*/
protected void beforeDelete(List<Long> ids) {
/* 删除前置处理 */
}
/**
* 新增后置处理
*
* @param req 创建信息
* @param entity 实体信息
*/
protected void afterCreate(C req, T entity) {
/* 新增后置处理 */
}
/**
* 修改后置处理
*
* @param req 修改信息
* @param entity 实体信息
*/
protected void afterUpdate(C req, T entity) {
/* 修改后置处理 */
}
/**
* 删除后置处理
*
* @param ids ID 列表
*/
protected void afterDelete(List<Long> ids) {
/* 删除后置处理 */
}
}
AbstractCrudController 控制器
RESTful API 控制器基类,提供默认的 API 接口实现:
/**
* CRUD 控制器抽象基类
*
* @param <S> 业务接口
* @param <L> 列表类型
* @param <D> 详情类型
* @param <Q> 查询条件类型
* @param <C> 创建或修改请求参数类型
* @author Charles7c
* @since 1.0.0
*/
public abstract class AbstractCrudController<S extends CrudService<L, D, Q, C>, L, D, Q, C> implements CrudApiHandler {
@Autowired
protected S baseService;
/**
* 分页查询列表
*
* @param query 查询条件
* @param pageQuery 分页查询条件
* @return 分页信息
*/
@CrudApi(Api.PAGE)
@Operation(summary = "分页查询列表", description = "分页查询列表")
@ResponseBody
@GetMapping
public BasePageResp<L> page(@Valid Q query, @Valid PageQuery pageQuery) {
return baseService.page(query, pageQuery);
}
/**
* 查询列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 列表信息
*/
@CrudApi(Api.LIST)
@Operation(summary = "查询列表", description = "查询列表")
@ResponseBody
@GetMapping("/list")
public List<L> list(@Valid Q query, @Valid SortQuery sortQuery) {
return baseService.list(query, sortQuery);
}
/**
* 查询树列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 树列表信息
*/
@CrudApi(Api.TREE)
@Operation(summary = "查询树列表", description = "查询树列表")
@ResponseBody
@GetMapping("/tree")
public List<Tree<Long>> tree(@Valid Q query, @Valid SortQuery sortQuery) {
return baseService.tree(query, sortQuery, false);
}
/**
* 查询详情
*
* @param id ID
* @return 详情信息
*/
@CrudApi(Api.GET)
@Operation(summary = "查询详情", description = "查询详情")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@ResponseBody
@GetMapping("/{id}")
public D get(@PathVariable("id") Long id) {
return baseService.get(id);
}
/**
* 创建
*
* @param req 创建请求参数
* @return ID
*/
@CrudApi(Api.CREATE)
@Operation(summary = "创建数据", description = "创建数据")
@ResponseBody
@PostMapping
@Validated(CrudValidationGroup.Create.class)
public IdResp<Long> create(@RequestBody @Valid C req) {
return new IdResp<>(baseService.create(req));
}
/**
* 修改
*
* @param req 修改请求参数
* @param id ID
*/
@CrudApi(Api.UPDATE)
@Operation(summary = "修改数据", description = "修改数据")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@ResponseBody
@PutMapping("/{id}")
@Validated(CrudValidationGroup.Update.class)
public void update(@RequestBody @Valid C req, @PathVariable("id") Long id) {
baseService.update(req, id);
}
/**
* 删除
*
* @param id ID
*/
@CrudApi(Api.DELETE)
@Operation(summary = "删除数据", description = "删除数据")
@Parameter(name = "id", description = "ID", example = "1", in = ParameterIn.PATH)
@ResponseBody
@DeleteMapping("/{id}")
public void delete(@PathVariable("id") Long id) {
baseService.delete(List.of(id));
}
/**
* 批量删除
*
* @param req 删除请求参数
*/
@CrudApi(Api.BATCH_DELETE)
@Operation(summary = "批量删除数据", description = "批量删除数据")
@ResponseBody
@DeleteMapping
public void batchDelete(@RequestBody @Valid IdsReq req) {
baseService.delete(req.getIds());
}
/**
* 导出
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @param response 响应对象
*/
@CrudApi(Api.EXPORT)
@ExcludeFromGracefulResponse
@Operation(summary = "导出数据", description = "导出数据")
@GetMapping("/export")
public void export(@Valid Q query, @Valid SortQuery sortQuery, HttpServletResponse response) {
baseService.export(query, sortQuery, response);
}
/**
* 查询字典列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 字典列表信息
*/
@CrudApi(Api.DICT)
@Operation(summary = "查询字典列表", description = "查询字典列表(下拉选项等场景)")
@GetMapping("/dict")
public List<LabelValueResp> dict(@Valid Q query, @Valid SortQuery sortQuery) {
return baseService.dict(query, sortQuery);
}
/**
* 查询树型字典列表
*
* @param query 查询条件
* @param sortQuery 排序查询条件
* @return 树型字典列表信息
*/
@CrudApi(Api.DICT_TREE)
@Operation(summary = "查询树型字典列表", description = "查询树型结构字典列表(树型结构下拉选项等场景)")
@GetMapping("/dict/tree")
public List<Tree<Long>> dictTree(@Valid Q query, @Valid SortQuery sortQuery) {
return baseService.tree(query, sortQuery, true);
}
}
自定义扩展
通过预留的钩子函数和面向对象特性,可以灵活实现业务定制。
重写 Service 方法
自定义查询逻辑示例:
@Override
public PageResp<UserResp> page(UserQuery query, PageQuery pageQuery) {
// 自定义查询逻辑
QueryWrapper<UserDO> queryWrapper = new QueryWrapper<>();
if (StrUtil.isNotBlank(query.getUsername())) {
queryWrapper.like("username", query.getUsername());
}
// ...
return super.page(query, pageQuery);
}
通过重写 buildQueryWrapper
方法自定义查询条件:
@Override
protected QueryWrapper<UserDO> buildQueryWrapper(UserQuery query) {
QueryWrapper<UserDO> queryWrapper = super.buildQueryWrapper(query);
// 添加自定义查询条件
queryWrapper.eq("status", 1);
return queryWrapper;
}
重写 Controller#preHandle 方法
通过重写 AbstractCrudController 的preHandle
方法,可以为每个 CRUD 接口进行逻辑增强,适合添加校验(权限、参数等校验)。
这是通过 @CrudApi
注解结合 AOP 来实现的。
public class UserController extends AbstractCrudController<UserService, UserResp, UserDetailResp, UserQuery, UserReq> {
@Override
public void preHandle(CrudApi crudApi, Object[] args, Method targetMethod, Class<?> targetClass) throws Exception {
}
}
核心依赖
依赖 | 描述 |
---|---|
top.continew.starter:continew-starter-api-doc | API 文档模块 |
top.continew.starter:continew-starter-web | Web 模块 |
top.continew.starter:continew-starter-data-core | 数据访问模块 - 核心模块 |
top.continew.starter:continew-starter-excel-fastexcel | Excel 文件处理模块 - FastExcel |
top.continew.starter:continew-starter-extension-crud-core | CRUD 核心模块 |
top.continew.starter:continew-starter-data-mp | 数据访问模块 - MyBatis Plus |