Skip to content

增删改查 (crud)

最后更新: 3 天前
实践版本: v2.13.4

简介

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 包实现)

快速开始

引入依赖

pom.xml
xml
<dependency>
    <groupId>top.continew.starter</groupId>
    <artifactId>continew-starter-extension-crud-mp</artifactId>
</dependency>

添加配置

配置详情请查看:top.continew.starter.extension.crud.autoconfigure.CrudProperties

application.yml
yaml
--- ### CRUD 配置
continew-starter.crud:
  ## 树型结构字典映射配置,用于查询树型结构字典列表 API(树型结构下拉选项等场景)
  ## 请根据对应前端 UI 调整字段名
  tree:
    id-key: key
    name-key: title
    weight-key: sort

编写 POJO

编写 POJO 类:实体类(XxxDO)、列表响应参数类(XxxResp)、详情响应参数类(XxxDetailResp)、查询条件类(XxxQuery)、创建或修改请求参数类(XxxReq)。

java
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;
    // 其他略...
}
java
/**
 * 用户响应参数
 *
 * @author Charles7c
 */
@Data
@Schema(description = "用户响应参数")
public class UserResp {

    /**
     * 用户名
     */
    @Schema(description = "用户名", example = "zhangsan")
    private String username;

    /**
     * 昵称
     */
    @Schema(description = "昵称", example = "张三")
    private String nickname;
    // 其他略...
}
java
/**
 * 用户详情响应参数
 *
 * @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;
    // 其他略...
}
java
/**
 * 用户查询条件
 *
 * @author Charles7c
 */
@Data
@Schema(description = "用户查询条件")
public class UserQuery {

    /**
     * 昵称
     */
    @Schema(description = "昵称", example = "张三")
    private String nickname;
    // 其他略...
}
java
/**
 * 用户创建或修改请求参数
 *
 * @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

UserMapper.java
java
import top.continew.starter.data.mapper.BaseMapper;

/**
 * 用户 Mapper
 *
 * @author Charles7c
 */
public interface UserMapper extends BaseMapper<UserDO> {
}

编写 Service 接口与实现

创建 Service 接口和实现类,继承 CrudServiceCrudServiceImpl

UserService.java
java
import top.continew.starter.extension.crud.service.CrudService;

/**
 * 用户业务接口
 * @author Charles7c
 */
public interface UserService extends CrudService<UserResp, UserDetailResp, UserQuery, UserReq> {
}
UserServiceImpl.java
java
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 操作:

UserController.java
java
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 能力。

ContiNewDemoApplication.java
java
@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。

DictModel.java
java
/**
 * 字典结构映射
 *
 * @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。

yaml
--- ### 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 注解即可:

CrudService.java
java
/**
 * 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服务基类,提供默认实现:

CrudServiceImpl.java
java
/**
 * 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 接口实现:

AbstractCrudController.java
java
/**
 * 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 方法

自定义查询逻辑示例

java
@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 方法自定义查询条件:

java
@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 来实现的。

java
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-docAPI 文档模块
top.continew.starter:continew-starter-webWeb 模块
top.continew.starter:continew-starter-data-core数据访问模块 - 核心模块
top.continew.starter:continew-starter-excel-fastexcelExcel 文件处理模块 - FastExcel
top.continew.starter:continew-starter-extension-crud-coreCRUD 核心模块
top.continew.starter:continew-starter-data-mp数据访问模块 - MyBatis Plus