不灭的焱

革命尚未成功,同志仍须努力 下载Java

作者:AlbertWen  添加时间:2026-03-10 11:47:00  修改时间:2026-03-10 16:51:06  分类:01.AI编程  编辑

AI学习 MyBatis-Flex ORM框架

更新时间:2026-03-10

结论:本仓库后端 ORM 方案已经明确采用 MyBatis-Flex 1.11.6。下次开发时,不要把它当成“纯注解 CRUD 框架”,而要按本项目的真实模式使用:Entity + TableDef + BaseMapper + ServiceImpl + QueryWrapper 负责单表和常规条件查询,复杂分页/连表查询按需落到 Mapper XML。

1. 当前项目事实

  • MyBatis-Flex 版本:1.11.6
  • 依赖位置:
    • pom.xml
    • fuyo-launch/pom.xml
    • fuyo-dependencies/pom.xml
  • 注解处理器:mybatis-flex-processor
  • 主要业务模块:fuyo-common
  • 启动与 SQL 审计配置:fuyo-framework/src/main/java/com/fuyo/dic/framework/config/MyBatisFlexConfiguration.java

2. 本项目里的真实架构

当前仓库里,MyBatis-Flex 的主要组织形式如下:

fuyo-common/
├── entity/                 # 实体类,@Table / @Id
├── entity/table/           # TableDef,类型安全字段引用
├── mapper/                 # Mapper 接口,继承 BaseMapper
├── resources/mapper/       # 自定义 XML SQL
├── service/                # Service 接口
└── service/impl/           # ServiceImpl 实现,写业务逻辑

一句话理解:

  • 简单单表 CRUD:优先走 ServiceImpl + QueryWrapper
  • 条件查询/校验查询:优先走 QueryWrapper + TableDef
  • 复杂连表分页/DTO 投影:优先走 Mapper.java + Mapper.xml

这就是本项目当前最真实、最稳的用法。

3. 核心组成和职责

3.1 Entity

实体类负责映射数据库表。

典型文件:

  • fuyo-common/src/main/java/com/fuyo/dic/common/entity/SysUser.java
  • fuyo-common/src/main/java/com/fuyo/dic/common/entity/SysRole.java
  • fuyo-common/src/main/java/com/fuyo/dic/common/entity/SysMenu.java

当前项目特征:

  • 使用 @Table("表名")
  • 主键使用 @Id(keyType = KeyType.Auto)
  • 大多实体是代码生成产物
  • 主键大量使用 BigInteger
  • 没有统一强制继承 BaseEntity

结论:

  • 这个项目虽然有 BaseEntity,但当前主业务实体并没有统一继承它
  • 下次新增实体时,先保持和当前模块一致,不要强推继承体系改造

3.2 TableDef

TableDef 是 MyBatis-Flex 在本项目里最重要的“类型安全字段常量”。

典型文件:

  • fuyo-common/src/main/java/com/fuyo/dic/common/entity/table/SysUserTableDef.java

作用:

  • 避免字符串字段名硬编码
  • QueryWrapper 条件构造更安全
  • 支持 as(alias) 别名

常见用法:

import static com.fuyo.dic.common.entity.table.SysUserTableDef.SYS_USER;

QueryWrapper.create()
    .and(SYS_USER.USERNAME.eq("admin"))
    .and(SYS_USER.DELETED.eq(0));

开发约定:

  • 写查询条件时,优先用 TableDef
  • 不要自己写 "username""deleted" 这种字符串列名,除非确实是动态 SQL 场景

3.3 Mapper

Mapper 接口统一继承 BaseMapper<T>

典型文件:

  • fuyo-common/src/main/java/com/fuyo/dic/common/mapper/SysUserMapper.java

项目里 Mapper 分两类:

  1. 纯继承 BaseMapper<T>,吃基础 CRUD
  2. BaseMapper<T> 之上继续声明自定义方法,对应 XML 实现复杂 SQL

例如 SysUserMapper 额外声明了:

  • selectUserPageList
  • selectUserCount
  • selectUserRolesList

这说明一个非常重要的项目原则:

MyBatis-Flex 不是排斥 XML,而是与 XML 并存。复杂 SQL 不要硬塞进 QueryWrapper。

3.4 ServiceImpl

Service 实现统一采用:

public class XxxServiceImpl extends ServiceImpl<XxxMapper, XxxEntity> implements XxxService

典型文件:

  • SysUserServiceImpl
  • SysRoleServiceImpl
  • SysMenuServiceImpl

这里承担的职责是:

  • 参数校验
  • 业务规则校验
  • 调用 save / updateById / getOne / list / count / page
  • 多表写操作事务控制

结论:

  • Controller 不直接写 ORM 细节
  • 业务规则优先沉在 ServiceImpl
  • 事务边界通常放在 ServiceImpl 方法上

4. 本项目最常用的 MyBatis-Flex API

从当前代码看,最常用的其实不是所有 Flex API,而是下面这组:

  • getById
  • getOne(QueryWrapper)
  • list(QueryWrapper)
  • count(QueryWrapper)
  • save
  • saveBatch
  • updateById
  • remove(QueryWrapper)
  • page(Page, QueryWrapper)
  • QueryWrapper.create()

下次开发时,优先从这套能力里选,不要一上来引入陌生高级 API。

5. QueryWrapper 在本项目里的标准用法

5.1 基础查询

这是本项目最常见的写法:

SysUser user = getOne(QueryWrapper.create()
    .and(SYS_USER.USERNAME.eq(req.getUsername()))
    .and(SYS_USER.DELETED.eq(0)));

当前风格特点:

  • 常从 QueryWrapper.create() 开始
  • 习惯连续使用 .and(...)
  • 逻辑删除条件经常显式写出:DELETED.eq(0)

5.2 条件拼装

SysRoleServiceImpl.page()SysMenuServiceImpl.list() 为代表,项目当前推荐的动态条件写法是:

QueryWrapper queryWrapper = QueryWrapper.create()
    .and(SYS_ROLE.DELETED.eq(0));

if (req.getRoleName() != null && !req.getRoleName().trim().isEmpty()) {
    queryWrapper.and(SYS_ROLE.ROLE_NAME.like("%" + req.getRoleName().trim() + "%"));
}

结论:

  • 可选查询条件用 if 包起来动态拼接
  • 不追求一行链到底,清晰优先
  • 模糊查询当前项目普遍直接拼 %xxx%

5.3 常见条件

本项目实际最常见的是这些:

  • eq
  • ne
  • like
  • in
  • orderBy

示例:

QueryWrapper.create()
    .and(SYS_ROLE.ID.in(bigIntegerIds))
    .orderBy(SYS_ROLE.CREATE_TIME, false);

其中:

  • false 表示降序
  • true 表示升序

6. 分页在本项目里的两种模式

6.1 单表分页:直接用 Flex Page

代表文件:

  • fuyo-common/src/main/java/com/fuyo/dic/common/service/impl/SysRoleServiceImpl.java

写法:

Page<SysRole> pageResult = page(new Page<>(pageNum, pageSize), queryWrapper);

适用场景:

  • 单表分页
  • 条件不复杂
  • 返回实体或实体轻量转换

6.2 复杂分页:Mapper + XML 手写 SQL

代表文件:

  • fuyo-common/src/main/java/com/fuyo/dic/common/mapper/SysUserMapper.java
  • fuyo-common/src/main/resources/mapper/SysUserMapper.xml

特点:

  • 连表查询
  • DTO 投影
  • 单独查列表 + 单独查总数
  • 再补一轮批量附加数据

例如用户分页:

  1. XML 查用户主列表
  2. XML 查总数
  3. Java 批量查用户角色
  4. Java 组装返回 DTO

开发结论:

  • 单表分页不要过度设计,直接 page(new Page<>(...), wrapper)
  • 连表分页、复杂字段转换、特殊格式化,直接 Mapper XML
  • 不要为了“纯 Flex”把复杂 SQL 硬写成难维护的 Wrapper

7. 逻辑删除在本项目中的真实做法

虽然旧笔记可能会提到 @Column(isLogicDelete = true),但当前项目真实代码里更常见的是手动维护逻辑删除字段,而不是完全依赖注解自动逻辑删除。

当前模式:

  • 表里有 deleted
  • 查询时显式加 DELETED.eq(0)
  • 删除时不是 deleteById,而是更新 deleted = 1

例如:

SysRole role = new SysRole();
role.setId(BigInteger.valueOf(roleId));
role.setDeleted(1);
role.setUpdateTime(LocalDateTime.now());
updateById(role);

结论:

  • 下次开发默认继续沿用“显式逻辑删除”模式
  • 不要假设 Flex 已全局帮你自动过滤 deleted
  • 只要是业务数据查询,优先检查是否需要补 deleted = 0

8. 新增与更新的项目级写法

8.1 新增

典型模式:

  1. 先用 getOne(QueryWrapper) 做唯一性校验
  2. 构建实体
  3. 手动补 deleted/createTime/updateTime
  4. save

示例抽象:

Xxx exist = getOne(QueryWrapper.create()
    .and(XXX.CODE.eq(req.getCode()))
    .and(XXX.DELETED.eq(0)));
if (exist != null) {
    throw new BusinessException("编码已存在");
}

Xxx entity = Xxx.builder()
    .code(req.getCode())
    .deleted(0)
    .createTime(LocalDateTime.now())
    .updateTime(LocalDateTime.now())
    .build();

save(entity);

8.2 更新

当前项目更新主要有两种方式:

  1. 先查出原对象,再改字段,updateById
  2. 新建一个只带主键和变更字段的对象,updateById

例如修改密码:

SysUser updateUser = new SysUser();
updateUser.setId(user.getId());
updateUser.setPassword(encodedPassword);
updateUser.setUpdateTime(LocalDateTime.now());
sysUserService.updateById(updateUser);

结论:

  • 局部更新在本项目里是常规做法
  • 但用局部更新时要非常明确哪些字段会被带上
  • 如果是复杂更新,先查后改更稳

9. 批量操作怎么做

本项目已实际使用的批量模式:

  • saveBatch
  • list(QueryWrapper.create().and(ID.in(...)))
  • 循环 updateById
  • remove(QueryWrapper)

例如用户角色关联批量保存:

sysUserRoleService.saveBatch(userRoles);

例如按条件删除中间表:

sysUserRoleService.remove(QueryWrapper.create()
    .and(SYS_USER_ROLE.USER_ID.eq(BigInteger.valueOf(req.getUserId()))));

结论:

  • 中间表关联关系,常见方案是“先删后插”
  • 批量逻辑删除当前项目仍常用循环 updateById
  • 如果后续性能要求更高,再考虑批量更新 SQL 优化

10. 事务边界

当前项目中,涉及多步写操作的方法基本都加:

@Transactional(rollbackFor = Exception.class)

典型场景:

  • 新增用户 + 保存用户角色
  • 修改用户 + 清空旧角色 + 保存新角色
  • 批量删除
  • 重置密码

开发准则:

  • 只要跨多张表写,默认加事务
  • 只读查询通常不需要事务

11. SQL 审计和调试

配置文件:

  • fuyo-framework/src/main/java/com/fuyo/dic/framework/config/MyBatisFlexConfiguration.java

当前已开启:

  • AuditManager.setAuditEnable(true)
  • 输出完整 SQL 和耗时到 mybatis-flex-sql 日志

结论:

  • 调试 SQL 时,优先看日志,不要盲猜 Wrapper 生成结果
  • 如果查询异常,先核对最终 SQL 是否符合预期

12. BaseEntity 的定位

项目里有:

  • fuyo-framework/src/main/java/com/fuyo/dic/framework/model/BaseEntity.java

它定义了:

  • id
  • createBy
  • createTime
  • updateBy
  • updateTime
  • delFlag

但当前 fuyo-common 里的核心实体并没有统一继承它,且字段命名也不完全一致,例如很多表用的是 deleted 而不是 delFlag

结论:

  • 当前项目并不存在“统一的 MyBatis-Flex 实体基类体系”
  • 下次开发不要贸然让新实体强依赖 BaseEntity,除非你同时确认数据库字段也统一

13. DTO / Entity / XML 的分工

这是本项目很关键的工程实践。

当前分工:

  • Entity:数据库表映射
  • Req DTO:请求参数
  • Resp DTO:响应结构
  • Mapper XML:复杂 SQL 和结果映射
  • ServiceImpl:业务组装、校验、事务

以用户分页为例:

  • SysUser 不是直接返回给前端的分页对象
  • 前端需要的结构是 UserPageResp.UserItem
  • 所以分页查询直接走 Mapper XML -> ResultMap -> DTO

开发结论:

  • 不要把所有查询都硬返回 Entity
  • 只要出现连表字段、字典名称、格式化时间,优先考虑单独 DTO

14. 下次新增一个标准模块时的 ORM 操作顺序

建议按这个顺序走:

  1. 建表
  2. 生成或编写 Entity
  3. 生成或编写 TableDef
  4. 新建 Mapper extends BaseMapper<Entity>
  5. 新建 ServiceServiceImpl extends ServiceImpl<Mapper, Entity>
  6. 单表查询先用 QueryWrapper + TableDef
  7. 如果出现复杂分页/连表,再补 Mapper.xml
  8. 在 ServiceImpl 中补唯一性校验、删除校验、事务

15. 本项目的使用红线

下次继续写后端时,默认遵守这些规则:

  • 优先使用 TableDef,避免字符串列名
  • 查询业务数据时,默认检查是否要补 deleted = 0
  • 单表条件查询优先 QueryWrapper
  • 复杂连表和 DTO 投影优先 XML
  • 多表写操作必须考虑事务
  • 不要把 Controller 写成 ORM 层
  • 不要假设 BaseEntity 已经统一接管所有实体

16. 容易踩坑的地方

  • 忘记加 deleted = 0,把逻辑删除数据查出来
  • Long / BigInteger 混用时忘记转换
  • 复杂分页还硬用 QueryWrapper,结果可读性和维护性都很差
  • 局部更新对象字段带错,覆盖不该更新的数据
  • 中间表更新不加事务,导致半成功半失败
  • 以为用了 MyBatis-Flex 就不需要 XML,实际本项目并不是这样

17. 下次直接复用的心智模型

一句话记住:

在这个项目里,MyBatis-Flex 不是“只写链式查询”的工具,而是一套 ORM 基础设施。最优路径通常是:单表用 QueryWrapper + TableDef,复杂查询用 Mapper XML,业务规则放 ServiceImpl,事务放服务层。

如果下次让我继续基于这个仓库写后端 ORM 代码,我默认这样执行:

  • Entity 映表
  • TableDef 做类型安全条件
  • BaseMapper 吃基础 CRUD
  • ServiceImpl 放业务校验和事务
  • 简单查询用 Wrapper
  • 复杂查询用 XML
  • 逻辑删除手动维护 deleted