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.xmlfuyo-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.javafuyo-common/src/main/java/com/fuyo/dic/common/entity/SysRole.javafuyo-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 分两类:
- 纯继承
BaseMapper<T>,吃基础 CRUD - 在
BaseMapper<T>之上继续声明自定义方法,对应 XML 实现复杂 SQL
例如 SysUserMapper 额外声明了:
selectUserPageListselectUserCountselectUserRolesList
这说明一个非常重要的项目原则:
MyBatis-Flex 不是排斥 XML,而是与 XML 并存。复杂 SQL 不要硬塞进 QueryWrapper。
3.4 ServiceImpl
Service 实现统一采用:
public class XxxServiceImpl extends ServiceImpl<XxxMapper, XxxEntity> implements XxxService
典型文件:
SysUserServiceImplSysRoleServiceImplSysMenuServiceImpl
这里承担的职责是:
- 参数校验
- 业务规则校验
- 调用
save / updateById / getOne / list / count / page - 多表写操作事务控制
结论:
- Controller 不直接写 ORM 细节
- 业务规则优先沉在 ServiceImpl
- 事务边界通常放在 ServiceImpl 方法上
4. 本项目最常用的 MyBatis-Flex API
从当前代码看,最常用的其实不是所有 Flex API,而是下面这组:
getByIdgetOne(QueryWrapper)list(QueryWrapper)count(QueryWrapper)savesaveBatchupdateByIdremove(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 常见条件
本项目实际最常见的是这些:
eqnelikeinorderBy
示例:
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.javafuyo-common/src/main/resources/mapper/SysUserMapper.xml
特点:
- 连表查询
- DTO 投影
- 单独查列表 + 单独查总数
- 再补一轮批量附加数据
例如用户分页:
- XML 查用户主列表
- XML 查总数
- Java 批量查用户角色
- 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 新增
典型模式:
- 先用
getOne(QueryWrapper)做唯一性校验 - 构建实体
- 手动补
deleted/createTime/updateTime - 调
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 更新
当前项目更新主要有两种方式:
- 先查出原对象,再改字段,
updateById - 新建一个只带主键和变更字段的对象,
updateById
例如修改密码:
SysUser updateUser = new SysUser(); updateUser.setId(user.getId()); updateUser.setPassword(encodedPassword); updateUser.setUpdateTime(LocalDateTime.now()); sysUserService.updateById(updateUser);
结论:
- 局部更新在本项目里是常规做法
- 但用局部更新时要非常明确哪些字段会被带上
- 如果是复杂更新,先查后改更稳
9. 批量操作怎么做
本项目已实际使用的批量模式:
saveBatchlist(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
它定义了:
idcreateBycreateTimeupdateByupdateTimedelFlag
但当前 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 操作顺序
建议按这个顺序走:
- 建表
- 生成或编写
Entity - 生成或编写
TableDef - 新建
Mapper extends BaseMapper<Entity> - 新建
Service和ServiceImpl extends ServiceImpl<Mapper, Entity> - 单表查询先用
QueryWrapper + TableDef - 如果出现复杂分页/连表,再补
Mapper.xml - 在 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