Skip to content

CRUD模块(MyBatis Plus实现)

简介

continew-starter-extension-crud-mp 是 ContiNew Starter 提供的基于 MyBatis Plus 实现的 CRUD(增删改查)扩展模块,提供开箱即用的 RESTful API 快速开发解决方案。通过继承抽象基类,可快速实现标准的分页查询、列表查询、详情查询、新增、修改、删除等接口,大幅减少重复编码工作,统一项目规范。

主要特性

  • 开箱即用: 基于注解的 API 路径映射和权限控制,只需简单配置即可快速开发完成一套 CRUD 接口
  • 三层架构: 基于 MyBatis Plus 实现,提供完整的三层(Controller => Service => Mapper)CRUD 操作
    • Controller 层: 提供分页查询、列表查询、树列表查询、详情查询、创建、修改、删除、批量删除、导出接口等
    • Service 层: 提供相对应接口的实现,提供字典数据列表查询(适合下拉框等场景)、提供灵活的函数钩子,例如: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

yaml
--- ### 全局树结构配置(简单树,对应前端 UI)
continew-starter.crud:
  tree:
    id-key: key
    name-key: title
    weight-key: sort

实体

根据需要创建下方 POJO:

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

Mapper接口

创建 Mapper 接口,继承 top.continew.starter.data.mapper.BaseMapper

java
package top.continew.sample.mapper;

import top.continew.starter.data.mapper.BaseMapper;
// 其他略...

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

Service接口与实现

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

java
package top.continew.sample.service;

import top.continew.starter.extension.crud.service.CrudService;
// 其他略...

/**
 * 用户Service接口
 *
 * @author Charles7c
 */
public interface UserService extends CrudService<UserResp, UserDetailResp, UserQuery, UserReq> {
}
java
package top.continew.sample.service.impl;

import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import top.continew.starter.extension.crud.service.CrudServiceImpl;
// 其他略...

/**
 * 用户Service实现类
 *
 * @author Charles7c
 */
@Slf4j
@Validated // 如果你需要在 Service 层也拥有参数校验能力,那么只需要在 ServiceImpl 上添加 @Validated 注解即可
@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, entity);
        // 自定义业务逻辑
    }
}

Controller实现

创建 Controller,继承 AbstractCrudController 并添加 @CrudRequestMapping 注解,指定好访问路径和需要的 API 操作:

java
package top.continew.sample.controller;

import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.RestController;
import top.continew.starter.extension.crud.annotation.CrudRequestMapping;
import top.continew.starter.extension.crud.controller.AbstractCrudController;
import top.continew.starter.extension.crud.enums.Api;
// 其他略...

/**
 * 用户 Controller
 *
 * @author Charles7c
 */
@RestController
@RequestMapping("/users")
@Tag(name = "用户管理", description = "用户相关操作")
@CrudRequestMapping(value = "/users", api = {Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE, Api.EXPORT})
public class UserController extends AbstractCrudController<UserService, UserResp, UserDetailResp, UserQuery, UserReq> {
    // 可重写父类方法进行自定义扩展
}

启用 CRUD 注解

在启动类添加 @EnableCrudRestController 注解启用 CRUD 能力。

java
@EnableCrudRestController

启动项目,查看 Swagger 接口文档,即可看到你配置的接口。

核心注解

@CrudRequestMapping

用于类级别,指定 CRUD 接口的路径映射和包含的 API 操作:

java
/**
 * CRUD(增删改查)请求映射器注解
 *
 * @author Charles7c
 * @since 1.0.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrudRequestMapping {

    /**
     * 路径映射 URI(等同于:@RequestMapping("/foo1"))
     */
    String value() default "";

    /**
     * API 列表
     */
    Api[] api() default {Api.PAGE, Api.GET, Api.CREATE, Api.UPDATE, Api.DELETE, Api.EXPORT};
}

@CrudApi

用于方法级别,标记 CRUD 接口的具体操作类型(用于通过 AOP 增强 Controller 层接口方法,可重写 preHandler 方法进行自定义扩展,例如:权限校验或增强校验等):

java
/**
 * CRUD(增删改查)API
 *
 * @author Charles7c
 * @since 2.7.5
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CrudApi {

    /**
     * API 类型
     */
    Api value() default Api.LIST;
}

@TreeField

定义树结构字段,用于树列表查询:

前面我们还看到在全局配置中有一个树配置,区分一下:简单树(下拉列表)使用全局配置结构,复杂树(表格)使用局部配置。

java
/**
 * 树结构字段
 *
 * @author Charles7c
 * @see cn.hutool.core.lang.tree.TreeNodeConfig
 * @see top.continew.starter.extension.crud.autoconfigure.CrudTreeProperties
 * @since 1.0.0
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TreeField {

    /**
     * ID 字段名
     *
     * @return ID 字段名
     */
    String value() default "key";

    /**
     * 父 ID 字段名
     *
     * @return 父 ID 字段名
     */
    String parentIdKey() default "parentId";

    /**
     * 名称字段名
     *
     * @return 名称字段名
     */
    String nameKey() default "title";

    /**
     * 排序字段名
     *
     * @return 排序字段名
     */
    String weightKey() default "sort";

    /**
     * 子列表字段名
     *
     * @return 子列表字段名
     */
    String childrenKey() default "children";

    /**
     * 递归深度(< 0 不限制)
     *
     * @return 递归深度
     */
    int deep() default -1;

    /**
     * 根节点 ID
     *
     * @return 根节点 ID
     */
    long rootId() default 0L;
}

@DictModel

定义字典模型,用于字典列表查询(适合于下拉列表场景,例如:在用户创建表单中,我们需要提供一个查询角色列表的下拉框接口,这就比较适合):

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 {};
}

核心接口与类

CrudService接口

定义 CRUD 操作的核心方法,我们还为 CRUD 方法增加了参数校验,如果你需要在 Service 层也拥有参数校验能力,那么只需要在 ServiceImpl 上添加 @Validated 注解即可:

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);

    /**
     * 查询树列表
     * <p>
     * 虽然提供了查询条件,但不建议使用,容易因缺失根节点导致树节点丢失。
     * 建议在前端进行查询过滤,如需使用建议重写方法。
     * </p>
     *
     * @param query     查询条件
     * @param sortQuery 排序查询条件
     * @param isSimple  是否为简单树结构(不包含基本树结构之外的扩展字段,简单树(下拉列表)使用全局配置结构,复杂树(表格)使用 @DictField 局部配置)
     * @return 树列表信息
     */
    List<Tree<Long>> tree(@Valid Q query, @Valid SortQuery sortQuery, boolean isSimple);

    /**
     * 查询详情
     *
     * @param id ID
     * @return 详情信息
     */
    D get(Long id);

    /**
     * 查询字典列表
     *
     * @param query     查询条件
     * @param sortQuery 排序查询条件
     * @return 字典列表信息
     * @since 2.1.0
     */
    List<LabelValueResp> listDict(@Valid Q query, @Valid SortQuery sortQuery);

    /**
     * 创建
     *
     * @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);
}

CrudServiceImpl实现类

MyBatis Plus实现的CRUD服务基类,提供默认实现:

java
/**
 * CRUD 业务实现基类
 *
 * @param <M> Mapper 接口
 * @param <T> 实体类型
 * @param <L> 列表类型
 * @param <D> 详情类型
 * @param <Q> 查询条件类型
 * @param <C> 创建或修改参数类型
 * @author Charles7c
 * @since 1.0.0
 */
public abstract class CrudServiceImpl<M extends BaseMapper<T>, T extends BaseIdDO, L, D, Q, C> extends ServiceImpl<M, T> implements CrudService<L, D, Q, C> {
    // 实现 CRUD 操作的具体方法,详情请查看源码
}

AbstractCrudController控制器

RESTful API控制器基类,提供默认的API接口实现:

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 {
    // CRUD 接口声明,详情请查看源码
}

自定义扩展

通过预留的钩子函数,以及面向对象能力,可以实现自定义扩展。

重写 Service 方法

例如:通过重写 CrudServiceImpl 中的方法进行自定义业务逻辑:

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中添加自定义接口或重写默认接口,放开鉴权等:

java
@GetMapping("/custom")
public R<List<UserResp>> customMethod() {
    // 自定义接口实现
    return R.ok(userService.list(new UserQuery(), new SortQuery()));
}

重写 preHandle 方法

通过重写 preHandle 方法,为每个 CRUD 接口进行逻辑增强,适合添加校验(权限、参数等校验)。(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
cn.crane4j:crane4j-spring-boot-starterCrane4j(基于注解的,用于完成一切 “根据 A 的 key 值拿到 B,再把 B 的属性映射到 A” 这类需求的字段填充框架)

常见问题

Q:怎么实现的配哪个接口就出现哪个接口呢?

A:很简单,我们通过重写 RequestMappingHandlerMapping 实现的,详情请查看源码。