外观
使用与配置
Base Business 通过继承和方法重写扩展 CRUD,没有模块专属的 YAML 配置前缀。先明确对象转换和持久化规则,再注册具体 Bean;最小装配见快速开始。
选择实体模型
| PO 基类 | 字段与行为 |
|---|---|
| BaseEntityPo | Long id,ASSIGN_ID 主键策略;LocalDateTime createTime |
| BaseEntityPo.Create | 增加 createUserId、createUserName |
| BaseEntityPo.Modified | 继承 Create,增加修改人、modifiedTime、isDeleted 和 version |
CreateMemo / ModifiedMemo | 在对应模型上增加 memo |
CreateSort / ModifiedSort | 在对应模型上增加 sort,实现顺序辅助计算 |
这些类没有 @TableName,应用为具体 PO 指定表并建立相应列。Create 不含逻辑删除字段;Modified.isDeleted 使用 @TableLogic(value="0", delval="1"),version 使用 @Version。数据库应初始化未删除标记及版本号,并在需要乐观锁时装配拦截器。Base Business 本身不创建表、不安装乐观锁拦截器,也不初始化这两个字段。
VO 和 SimpleVo 应选择与 PO 匹配的变体:BaseEntityVo 复制 ID、创建时间;Create/Modified 及 Memo/Sort 变体再复制对应字段。BaseEntitySimpleVo 只复制 ID,Memo/Sort 变体再带备注或排序,不复制审计人。应用自己的字段仍需在构造器中赋值。
isDeleted 在 PO 与完整 Modified VO 上标记为 JSON 忽略。公共 Long 和日期输出遵循 Base JSON 约定及应用转换器配置。
显式填充审计信息
po.init()每次都把创建时间设为当前时间;它不会填创建人,即便对象继承了 Create。po.init(loginUser)只在创建时间为空时填时间;Create 在创建人 ID 为空时填创建人 ID 和名称。- Modified 的
init(loginUser)还调用update(loginUser),后者每次更新修改时间和修改人。 - Sort 变体在
init(loginUser)中将空排序值设为 0,提供的getLastSort只计算边界,不会写库或重排其他记录。
默认 addByPo、updateByPo 不调用以上初始化方法,也不调用 beforeInsert()。应在保存 Hook 或明确的应用服务中填充;审计用户取自可信的 SessionUserProvider,不能直接信任请求体中的操作人。数据库默认值不保证回填到新增时的内存 VO,需要完整数据库结果时重新查询。
DTO 校验与转换
BaseEntityDto.check() 仅调用默认空实现 init();没有必填、长度或权限校验。addByDto 和 updateByDto 不调用 check(),应用入口应显式调用它,或用应用自己的校验方式。
BaseEntityDto.toPo() 调用 toPo(null);基类 toPo(entity) 原样返回参数,因此不重写时新增会得到 null。应用应显式创建新 PO 或修改旧 PO,再复制字段,完整示例见快速开始。
带 Supplier 的辅助重载 toPo(entity, supplier) 在空对象时仅返回 Supplier 结果,在非空时动态调用 toPo(entity)。若子类在自己的 toPo(entity) 中再次调用这一辅助重载,更新分支会递归回到自身。应使用 entity == null ? new Entity() : entity 等明确分支;不要把辅助方法当作无条件复制字段的父类实现。
Memo/Sort DTO 的单参数 toPo(entity) 仅在参数非空时复制对应字段;新对象需要子类保证这些值已赋入。更新也不天然等同 PATCH:空字段是否保留、清空及最终 SQL 字段策略,由 DTO 与 Mapper 配置共同决定。
注册服务与构造器
常规服务直接继承:
text
AbstractBaseService<PO, DTO, VO, MAP, QUERY, MAPPER>具体类注册为 Spring Bean,Mapper 通过 MyBatis 扫描注册,MapperUtil 单独导入或精确扫描。基类通过当前类的直接泛型父类反射提取 6 个实参,要求 PO 和 Query 有 public 无参构造器,VO 和 MAP 有 public 构造器且参数类型精确等于具体 PO 类。
不要将直接父类改成无泛型的中间实现,或保留未解析的类型变量,再假定反射仍能识别。缺失构造器会使 toVo、toMapVo、newPoInstance、newQueryInstance 在调用时抛出对应构造器异常;不符合父类形态时也可能在实例化阶段失败。
@ServiceInfo(name="...") 可为缺失对象错误提供模块名称,但不注册接口或授予权限。BaseController 本身没有 CRUD 路由,仅声明 @CrossOrigin("*");应用可像快速开始一样直接定义 Controller,按实际需要设置跨域规则。
保存与读取 Hook
Hook 是 Service 内部的方法扩展,不是自动收集的独立 Bean 列表,也不是在事务提交后发送的事件。
| 入口 | 默认调用顺序 |
|---|---|
addByDto(dto) | dto.toPo() → addByPo |
addByPo(po) | beforeAdd(po) → mapper.insert → toVo → afterAdd |
updateByDto(id, dto) | getPoById → dto.toPo(oldPo) → updateByPo |
updateByPo(po) | beforeUpdate → mapper.updateById → toVo → afterUpdate |
| 批量新增 | 列表版 beforeAdd(List) → MapperUtil.batchInsert → 逐条转换及 afterAdd(VO) |
| 批量更新 | 逐条 beforeUpdate → MapperUtil.updateByIdBatch → 逐条转换及 afterUpdate |
deleteById / ID 列表 / 外键删除 | 直接 Mapper 删除;不调用 beforeDelete、afterDelete |
updateByWrapper | 直接 mapper.update(null, wrapper);不调用保存 Hook |
updateSwitchStatus | 读取 PO → 计算并设置 0/1 → 直接 mapper.updateById;不调用更新 Hook |
默认 beforeAdd/beforeUpdate 分别执行 checkBeforeAdd/checkBeforeUpdate 返回的函数;后置方法同理。它们默认归到 checkBefore/checkAfter,这两个公共方法默认返回 null,表示不处理。
单条 beforeAdd(PO) 的重写不会被默认批量新增逐条调用。需要单条、批量一致时可重写 checkBeforeAdd(),或分别实现两个 beforeAdd 重载;也要保留原有规则的调用,避免重写方法后意外跳过公共函数。
queryPoById 和 queryPoByWrapper 的具体实现已经调用 afterGetPo,默认后者转调 afterQueryPo;非空结果再经过 getPoById 等强制存在入口时,会再次调用 afterGetPo。带默认 Supplier 的 query 重载也可能额外经过 afterQueryPo。这些 Hook 不应承担只能执行一次的扣减、发送等副作用。
列表补充通过 afterList 扩展,但并非所有列表重载都经过它,具体见查询与分页。
当前批量写入的实现限制
MapperUtil.batchInsert 和 updateByIdBatch 调用 ListUtil.groupList(list, length, fun) 后没有遍历其返回结果。该分组是惰性的,只有访问分组时才执行 Mapper 回调,因此当前批量方法不会触发预期 SQL。addBatchByPoList / updateBatchByPoList 仍可能转换并返回 VO、执行后置 Hook,不能据返回列表判断落库。使用前需修正调用链并验证数据库结果;增加事务不能解决回调未执行的问题。方法及源码见 MapperUtil。
事务与影响行数
普通 CRUD 和 MapperUtil 没有声明事务。多次写入、批次分组及后置处理需要整体回滚时,应用在通过 Spring 代理调用的公开服务入口声明事务。
单条新增/更新和多数删除方法不检查 Mapper 影响行数;更新返回的是当前内存对象转换成的 VO。乐观锁冲突或不存在的 ID 不能仅凭返回 VO 或 Controller 的 data=true 判断成功,需在应用层检查实际写入结果。
updateSwitchStatus 在指定值为空时切换已有状态;旧值为 null 会报错,旧值等于 1 时变 0,其余变 1。指定值非空时仅 1 映射为 1,其他整数映射为 0。这不是严格的输入枚举校验。
Mapper 与运行配置
| 装配项 | 由谁提供 | 用途与限制 |
|---|---|---|
| 数据源、驱动和 schema | 应用 | Base Business 不建表,不内置外部数据库驱动 |
@MapperScan 或受支持的 Mapper 注册方式 | 应用 | 扫描实际 Mapper 包;扫描 Service 不能替代 Mapper 注册 |
| MapperUtil Bean | 应用导入/扫描 | AbstractBaseService 的必需依赖,即使只用单条方法也要注册 |
pagehelper.* | 传递依赖的 PageHelper Starter | 为 query.page() 后的数据库查询提供分页拦截 |
mybatis-plus.* | MyBatis-Plus Starter | 表映射、XML 位置及框架参数;不是本模块专属配置 |
| 扩展 SQL 注入器 | 应用或 MyBatis 组件 | 使 BaseMapper 额外方法有可执行的 SQL |
BaseMapper 除标准 MyBatis-Plus API 外,还声明 insertBatch、insertIgnore、updateByIdBatch、updateByIdBatchSomeColumn、最大/最小排序、排序增减,以及 ignoreLogicDeleteUpdate*。只继承接口不等于 SQL 已存在。
需要框架的扩展实现时,在已管理 Component 版本的应用中增加:
xml
<dependency>
<groupId>com.own.component</groupId>
<artifactId>springboot-component-mybatis</artifactId>
</dependency>MyBatis 组件的自动配置按 Bean 缺失条件提供 CustomInjector 及乐观锁、防全表更新删除拦截器。已有自定义 ISqlInjector 或 MybatisPlusInterceptor 时,需要应用合并所需能力。扩展 SQL 的方言、字段要求和审计切面是 MyBatis 组件的职责;不要把单表 H2 示例理解成这些扩展已在所有数据库验证可用。
MapperUtil 默认每 1000 条分组调用 insertBatch 或 updateByIdBatch,可用带 length 的重载传入正分组长度。它不检查每组影响行数、不提供重试或全批事务;批量调用前应先完成校验,再配置事务及所需 SQL。
忽略逻辑删除的方法明确绕过正常删除过滤,不能直接作为普通用户更新入口;恢复规则和授权由应用定义。关联操作与导入导出边界见关系绑定与任务。