Mybatis-flex小记

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.IDUSER.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.IDu2.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 对象(推荐,具备编译期校验);
  • 关系代数支持 :原生支持 FROMLEFT JOININNER JOINGROUP BYHAVING 等完整 SQL 语法,是多表关联查询的核心载体;
  • TableDef 适配规则:简单场景配合静态单例使用,直接链式嵌入条件;复杂关联场景配合 new 别名化实例使用;
  • 执行适配 :可同时适配 BaseMapperDb 两种执行入口;
  • 实例化方式 :推荐使用静态工厂方法 create(),保障链式调用的流畅性与一致性。

2.更新构造器

1.LambdaUpdateWrapper<T>

  • 功能定位:单表条件更新构造,同时承载 SET 赋值与 WHERE 条件两部分语义;
  • 列引用:Lambda 方法引用,具备编译期类型安全;
  • 适用场景:单表按动态条件批量更新。

2.UpdateWrapper(通用更新构造器)

  • 功能定位:通用条件更新,支持复杂 WHERE 条件与多表更新场景;
  • 列引用:QueryColumn 对象或字符串,配合 TableDef 使用;
  • 执行适配:可传入 BaseMapperDb

3.删除操作的构造器复用

删除操作仅依赖 WHERE 条件,无需 SET 语义,因此 QueryWrapperLambdaQueryWrapper 均可直接作为删除条件的载体传入执行层,框架仅提取其 WHERE 片段生成 DELETE 语句。插入操作无对应 Wrapper,直接通过实体或 Row 对象传入执行器。

三.执行入口层:SQL执行模型

MyBatis-Flex 提供两种执行入口范式,分别面向领域驱动的工程化开发与灵活的即席数据操作。

1.BaseMapper 动态代理执行器

  • 编程模型 :定义 Mapper 接口继承 BaseMapper<T>,依托 MyBatis 动态代理机制生成实现类,通过 Spring 依赖注入使用;
  • 内置 CRUD 能力 :提供 insertupdate(按主键)、selectOneByIddeleteByIdselectListpaginateselectCount 等标准化方法,无需编写 XML 与 SQL;
  • 参数适配 :支持接收 LambdaQueryWrapperQueryWrapper 作为查询条件;支持接收 LambdaUpdateWrapperUpdateWrapper 作为更新条件;
  • 返回值模型 :强类型实体对象 T 或分页集合 Page<T>,与领域模型直接映射,适合业务层开发;
  • 复合主键处理ById 系列方法要求传入完整主键参数(Id.ofKeys() 或实体对象),框架根据 TableInfo 中的主键列表自动拼接 AND 条件。

2.Db 静态域执行器

  • 编程模型:采用门面模式的静态工具类,无需定义 Mapper 接口与实体类,直接调用静态方法执行 SQL;
  • 参数适配 :仅支持通用 QueryWrapperUpdateWrapper,不支持 Lambda 系列构造器;
  • 返回值模型Row 对象(继承 HashMap),以数据库字段名为 key,属于弱类型数据结构;支持 toEntity() 方法转换为强类型实体;
  • 适用场景:多表关联查询、临时即席查询、数据迁移脚本、动态表名场景;
  • 内置能力 :覆盖 selectListselectOneinsertupdatedeletepaginate 等全量操作,同时支持原生 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 内置 selectOneByIdupdate(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.主键操作约束

selectOneByIddeleteById 等 ById 方法要求传入完整主键属性;仅使用复合主键中部分字段作为条件时,不得使用 ById 方法,需通过 Wrapper 构造条件。

六.总结

MyBatis-Flex 通过分层设计实现了 "类型安全的单表开发" 与 "灵活的多表查询" 的平衡:Lambda 体系保障业务代码的健壮性与可维护性,TableDef 体系提供编译期安全的 SQL 表达能力;而 TableDef 内部静态单例与别名化实例的分化设计,同时兼顾了常规场景的简洁性与复杂场景的灵活性 ------ 静态 final 单例支持无变量链式调用,代码极简高效;别名化实例则解决了自连接等复杂场景的命名冲突问题。BaseMapper 与 Db 双执行入口则分别适配工程化开发与灵活即席场景。理解各层的职责边界、类型约束以及 TableDef 两种模式的适用边界,是规范化使用该框架、规避常见运行时错误的核心。

相关推荐
2601_9621771338 分钟前
【MySQL篇】聚合查询,联合查询
android·java·mysql
それども1 小时前
IDEA接入Claude完整流程
java·ai·intellij-idea
小鹿的周先生1 小时前
Spring-AI-第2篇-ChatClient 实战:使用 DeepSeek 完成第一次 AI 对话
java·人工智能·spring
青梅味猪大肠1 小时前
【深入浅出C++】为什么虚表指针可以解决菱形继承
开发语言·c++
潘潘的嵌入式日记2 小时前
I²C 从机接收总被覆盖?双缓冲要在 STOP 时交接
c语言·开发语言·单片机
悲且狂2 小时前
SpringBoot项目改造注意事项(旧项目框架复用)
java·spring boot·后端
Lhan.zzZ2 小时前
QML 组件库:VS2022 + Qt 静态库方案
开发语言·c++·qt·visual studio
花间相见2 小时前
【LangChain组件03】—— LangChain Agents 执行与状态:工作流程与状态管理实战
java·前端·langchain
敲代码的嘎仔2 小时前
28届后端开发-海康威视日常实习一面(已OC)
java·开发语言·后端·面试·海康威视·实习·大厂