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
中添加以下依赖:
<dependency>
<groupId>top.continew.starter</groupId>
<artifactId>continew-starter-extension-crud-mp</artifactId>
</dependency>
全局配置
配置详情请查看:top.continew.starter.extension.crud.autoconfigure.CrudProperties
。
--- ### 全局树结构配置(简单树,对应前端 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
。
package top.continew.sample.mapper;
import top.continew.starter.data.mapper.BaseMapper;
// 其他略...
/**
* 用户Mapper接口
*
* @author Charles7c
*/
public interface UserMapper extends BaseMapper<UserDO> {
}
Service接口与实现
创建 Service 接口和实现类,继承 CrudService
和 CrudServiceImpl
:
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> {
}
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 操作:
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 能力。
@EnableCrudRestController
启动项目,查看 Swagger 接口文档,即可看到你配置的接口。
核心注解
@CrudRequestMapping
用于类级别,指定 CRUD 接口的路径映射和包含的 API 操作:
/**
* 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 方法进行自定义扩展,例如:权限校验或增强校验等):
/**
* 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
定义树结构字段,用于树列表查询:
前面我们还看到在全局配置中有一个树配置,区分一下:简单树(下拉列表)使用全局配置结构,复杂树(表格)使用局部配置。
/**
* 树结构字段
*
* @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
定义字典模型,用于字典列表查询(适合于下拉列表场景,例如:在用户创建表单中,我们需要提供一个查询角色列表的下拉框接口,这就比较适合):
/**
* 字典结构映射
*
* @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 注解即可:
/**
* 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服务基类,提供默认实现:
/**
* 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接口实现:
/**
* 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
中的方法进行自定义业务逻辑:
@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中添加自定义接口或重写默认接口,放开鉴权等:
@GetMapping("/custom")
public R<List<UserResp>> customMethod() {
// 自定义接口实现
return R.ok(userService.list(new UserQuery(), new SortQuery()));
}
重写 preHandle 方法
通过重写 preHandle
方法,为每个 CRUD 接口进行逻辑增强,适合添加校验(权限、参数等校验)。(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 |
cn.crane4j:crane4j-spring-boot-starter | Crane4j(基于注解的,用于完成一切 “根据 A 的 key 值拿到 B,再把 B 的属性映射到 A” 这类需求的字段填充框架) |
常见问题
Q:怎么实现的配哪个接口就出现哪个接口呢?
A:很简单,我们通过重写 RequestMappingHandlerMapping
实现的,详情请查看源码。