MyBatis-Flex 是一款基于原生 MyBatis 的增强型 ORM 框架,通过注解驱动元数据、链式条件构造器、多形态执行入口三层架构,在兼容 MyBatis 原生能力的基础上,实现了无 XML 化开发、编译期类型安全、复合主键原生支持、多表关联查询等工程化能力。以下从元数据层、条件构造层、执行入口层三个核心层级,对其编程模型与使用规范进行系统化总结。
一.元数据层:实体建模与代码生成机制
元数据层是整个框架的类型基础,分为运行时反射元数据与编译期静态生成元数据两类,共同为上层 SQL 构建提供表结构信息。
1.运行时元数据模型(TableInfo)
框架启动阶段通过反射扫描实体注解,生成 TableInfo 运行时元数据对象,承载表结构的全部配置信息:
- 实体映射规则 :通过
@Table、@Id、@Column、@LogicDelete等注解声明表名、主键、字段映射、逻辑删除等策略; - 主键模型 :摒弃传统 ORM 的单一主键假设,以
primaryKeyColumns列表维护主键字段,原生支持单主键与复合主键两种模式; - 扩展能力元数据:封装主键生成策略、乐观锁、租户隔离等横切逻辑的配置,为 BaseMapper 内置方法提供元数据依据。
2.编译器静态元数据生成(TableDef)
通过 mybatis-flex-processor 注解处理器(APT)在编译期自动生成 TableDef 派生类,属于编译期代码生成技术,无运行时反射开销。
- QueryColumn 对象模型 :每个数据库字段映射为一个
QueryColumn实例,封装列名、所属表、别名等属性,提供eq()、gt()、like()、in()等条件构造方法,返回可拼接的 SQL 片段; - 表实例的两种构建模式:TableDef 存在静态单例与别名化实例两种使用形态,二者在生命周期、作用域、调用方式、适用场景上存在本质差异。
1.静态常量单例模式
APT 在生成 TableDef 类时,自动创建全局静态 final 实例作为单例常量:
java
public class UserTableDef extends TableDef {
public static final UserTableDef USER = new UserTableDef();
public final QueryColumn ID = new QueryColumn(this, "id");
public final QueryColumn USER_NAME = new QueryColumn(this, "user_name");
public final QueryColumn STATUS = new QueryColumn(this, "status");
}
- 生命周期:类加载时初始化,全局唯一实例,属于单例模式;
- 别名特性 :调用无参构造函数,表别名为空,SQL 中直接使用原始表名
t_user; - 链式调用特性:静态单例本身及其内部所有 QueryColumn 均为 final 不可变对象,支持直接链式调用条件构造方法,无需中间变量承接表实例,可直接将列条件表达式嵌入查询构造器的 where、join on 等语义节点;
- 使用方式 :通过静态导入直接引用常量
USER,列访问形式为USER.ID、USER.STATUS.eq(1),支持连续链式构建复合条件:
java
// 静态单例直接链式构建复合条件,无需额外声明表实例变量
QueryWrapper qw = QueryWrapper.create()
.where(USER.STATUS.eq(1).and(USER.AGE.gt(18)))
.orderBy(USER.CREATE_TIME.desc());
- 优势:开箱即用,无需手动实例化,代码简洁,链式表达流畅,适合绝大多数常规场景;
- 局限:单例全局共享,无法动态设置别名,不支持同一张表在同一 SQL 中多次出现
2.new别名实例化模式
通过带参构造函数手动创建独立实例,为表指定别名:
java
UserTableDef u1 = new UserTableDef("u1");
UserTableDef u2 = new UserTableDef("u2");
- 生命周期:代码运行到此处时创建,局部变量,每个实例相互独立;
- 别名特性 :构造参数作为表别名,SQL 中生成
t_user AS u1,所有列自动带上别名前缀u1.id; - 链式调用特性:实例同样支持链式调用列条件方法,但需先声明实例变量,再通过变量进行链式构建;
- 使用方式 :通过实例变量访问列,形式为
u1.ID、u2.USER_NAME;
java
UserTableDef u = new UserTableDef("u");
QueryWrapper qw = QueryWrapper.create()
.from(u)
.where(u.STATUS.eq(1).and(u.AGE.gt(18)));
- 优势:每个实例拥有独立的命名空间,支持同表多角色关联,消除列名歧义;
- 局限:需手动声明实例变量,代码量略高于静态单例模式。
3.两种模式的核心差异对比
| 对比维度 | 静态常量单例(USER) | new 别名化实例(new UserTableDef ("u")) |
|---|---|---|
| 实例数量 | 全局唯一单例 | 每次 new 生成独立对象,可多个并存 |
| 表别名 | 无,使用原始表名 | 可自定义别名,每个实例独立配置 |
| 列前缀 | 无前缀,直接输出字段名 | 自动附加别名前缀,如 u.id |
| 链式调用形式 | 直接通过静态常量链式调用,无需声明变量 | 需先声明实例变量,再通过变量链式调用 |
| 适用场景 | 单表查询、不同表的简单关联 | 自连接、同表多次关联、复杂多表查询 |
| 命名冲突 | 同表多次出现时产生列歧义 | 多实例命名空间隔离,无歧义 |
| 代码简洁性 | 静态导入后直接使用,简洁 | 需先声明实例变量,略繁琐 |
3.静态单例与 new 实例的使用边界
- 可直接使用静态单例的场景:单表条件查询、删除;两张及以上不同表的简单 JOIN,且无需显式别名;
- 必须使用 new 实例的场景:同一张表的自连接(如树形结构、上下级关联);同一张表在 SQL 中担任多个角色;需要通过别名提升 SQL 可读性;存在字段名冲突的多表查询。
典型错误范式:已通过 new 创建别名实例,却在条件中混用静态单例的列引用,导致列名与表别名不匹配,产生 SQL 语法错误或语义歧义。正确做法:同一查询中所有列引用必须来自同一个 TableDef 实例。
二.条件构造层:Wrapper 体系与 SQL 片段构建
Wrapper 是 MyBatis-Flex 的核心条件抽象,本质为SQL 片段构建器,仅负责构造 WHERE、JOIN、ORDER BY、GROUP BY 等语法片段,不具备数据库访问能力,需作为参数传入执行层方可生效。
按功能划分为查询构造器与更新构造器两类,每类均提供 Lambda 类型安全版本与通用版本。
1.构造查询器
1.LambdaQueryWrapper<T>
- 列引用方式 :基于 Java 方法引用(如
User::getAge),通过泛型与实体类型绑定; - 类型安全特性:编译期校验属性存在性,实体字段重构时可同步更新,从根源避免运行时字段拼写错误;
- 能力边界:仅支持单表查询条件构造,原生不支持 JOIN、显式 FROM、表别名等关系代数操作;
- 执行适配 :仅可与
BaseMapper配合使用,不可传入Db静态工具。
2.QueryWrapper(通用查询构造器)
- 列引用方式 :支持两种输入形态:① 手写字符串字段名;② TableDef 生成的
QueryColumn对象(推荐,具备编译期校验); - 关系代数支持 :原生支持
FROM、LEFT JOIN、INNER JOIN、GROUP BY、HAVING等完整 SQL 语法,是多表关联查询的核心载体; - TableDef 适配规则:简单场景配合静态单例使用,直接链式嵌入条件;复杂关联场景配合 new 别名化实例使用;
- 执行适配 :可同时适配
BaseMapper与Db两种执行入口; - 实例化方式 :推荐使用静态工厂方法
create(),保障链式调用的流畅性与一致性。
2.更新构造器
1.LambdaUpdateWrapper<T>
- 功能定位:单表条件更新构造,同时承载
SET赋值与WHERE条件两部分语义; - 列引用:Lambda 方法引用,具备编译期类型安全;
- 适用场景:单表按动态条件批量更新。
2.UpdateWrapper(通用更新构造器)
- 功能定位:通用条件更新,支持复杂 WHERE 条件与多表更新场景;
- 列引用:
QueryColumn对象或字符串,配合 TableDef 使用; - 执行适配:可传入
BaseMapper或Db。
3.删除操作的构造器复用
删除操作仅依赖 WHERE 条件,无需 SET 语义,因此 QueryWrapper 与 LambdaQueryWrapper 均可直接作为删除条件的载体传入执行层,框架仅提取其 WHERE 片段生成 DELETE 语句。插入操作无对应 Wrapper,直接通过实体或 Row 对象传入执行器。
三.执行入口层:SQL执行模型
MyBatis-Flex 提供两种执行入口范式,分别面向领域驱动的工程化开发与灵活的即席数据操作。
1.BaseMapper 动态代理执行器
- 编程模型 :定义 Mapper 接口继承
BaseMapper<T>,依托 MyBatis 动态代理机制生成实现类,通过 Spring 依赖注入使用; - 内置 CRUD 能力 :提供
insert、update(按主键)、selectOneById、deleteById、selectList、paginate、selectCount等标准化方法,无需编写 XML 与 SQL; - 参数适配 :支持接收
LambdaQueryWrapper、QueryWrapper作为查询条件;支持接收LambdaUpdateWrapper、UpdateWrapper作为更新条件; - 返回值模型 :强类型实体对象
T或分页集合Page<T>,与领域模型直接映射,适合业务层开发; - 复合主键处理 :
ById系列方法要求传入完整主键参数(Id.ofKeys()或实体对象),框架根据TableInfo中的主键列表自动拼接 AND 条件。
2.Db 静态域执行器
- 编程模型:采用门面模式的静态工具类,无需定义 Mapper 接口与实体类,直接调用静态方法执行 SQL;
- 参数适配 :仅支持通用
QueryWrapper、UpdateWrapper,不支持 Lambda 系列构造器; - 返回值模型 :
Row对象(继承HashMap),以数据库字段名为 key,属于弱类型数据结构;支持toEntity()方法转换为强类型实体; - 适用场景:多表关联查询、临时即席查询、数据迁移脚本、动态表名场景;
- 内置能力 :覆盖
selectList、selectOne、insert、update、delete、paginate等全量操作,同时支持原生 SQL 直接执行。
四.核心组合范式与适用场景
| 范式类型 | 组件组合 | TableDef 使用模式 | 核心特征 | 适用场景 |
|---|---|---|---|---|
| 单表业务 CRUD | BaseMapper + LambdaQueryWrapper / LambdaUpdateWrapper | 不涉及 TableDef | 编译期类型安全,代码重构友好,与领域模型强绑定 | 绝大多数业务单表增删改查、分页查询 |
| 简单多表关联 | BaseMapper / Db + QueryWrapper + 静态单例 TableDef | 静态常量单例(USER、ORDER) | 代码简洁,无需手动实例化,支持直接链式嵌入条件 | 两张不同表的基础 JOIN,无字段名冲突 |
| 复杂多表 / 自连接查询 | Db + QueryWrapper + new 别名化 TableDef 实例 | new 独立实例,指定别名 | 命名空间隔离,支持同表多角色,无列名歧义 | 自连接、树形查询、同表多次关联、复杂多表查询 |
| 条件批量更新 | 单表:BaseMapper + LambdaUpdateWrapper复杂:Db + UpdateWrapper + TableDef | 单表用 Lambda;复杂场景按需选择静态 / 实例 | 动态 set 赋值与 where 条件分离 | 按非主键条件批量更新字段 |
| 临时数据操作 | Db + QueryWrapper(字符串) | 不涉及 | 无需实体与 Mapper,开发周期短 | 脚本、数据初始化、运维临时操作 |
主键维度的单条 CRUD 无需构造 Wrapper,直接调用 BaseMapper 内置 selectOneById、update(entity)、deleteById 方法即可。
五.核心约束与边界规则
1.类型系统约束
- Lambda 系列构造器与 TableDef 体系不可互操作:
LambdaQueryWrapper仅接收方法引用,QueryWrapper仅接收QueryColumn或字符串,两套类型体系不兼容; Db执行器不支持 Lambda 系列构造器,仅适配通用 Wrapper。
2.TableDef 使用约束
- 单例全局唯一性约束:静态常量实例为全局单例,无别名,不可用于同表多次关联的自连接场景;同一 SQL 中同一张表需要出现多次时,必须通过 new 创建多个别名化实例;
- 实例一致性约束:同一查询逻辑中,表名与列引用必须来自同一个 TableDef 实例,禁止静态单例与别名化实例混用,避免列前缀与表别名不匹配导致的 SQL 语义错误;
- 别名作用域约束:new 创建的别名实例仅在当前查询上下文有效,不同查询之间互不影响。
3.职责边界约束
- Wrapper 仅负责 SQL 片段构造,不具备数据库访问能力,必须传入执行器方可生效;
QueryWrapper仅用于查询与删除条件构造,不提供set()方法;更新操作必须使用UpdateWrapper系列;- Insert 操作无对应 Wrapper,直接通过实体或 Row 对象传入执行器。
4.主键操作约束
selectOneById、deleteById 等 ById 方法要求传入完整主键属性;仅使用复合主键中部分字段作为条件时,不得使用 ById 方法,需通过 Wrapper 构造条件。
六.总结
MyBatis-Flex 通过分层设计实现了 "类型安全的单表开发" 与 "灵活的多表查询" 的平衡:Lambda 体系保障业务代码的健壮性与可维护性,TableDef 体系提供编译期安全的 SQL 表达能力;而 TableDef 内部静态单例与别名化实例的分化设计,同时兼顾了常规场景的简洁性与复杂场景的灵活性 ------ 静态 final 单例支持无变量链式调用,代码极简高效;别名化实例则解决了自连接等复杂场景的命名冲突问题。BaseMapper 与 Db 双执行入口则分别适配工程化开发与灵活即席场景。理解各层的职责边界、类型约束以及 TableDef 两种模式的适用边界,是规范化使用该框架、规避常见运行时错误的核心。