MyBatis-Plus 已经很好用,我为什么还要自研 ORM?
摘要:MyBatis 擅长把 SQL 与 Java 对象连接起来,MyBatis-Plus 又补齐了通用 CRUD 和 Lambda 条件,为什么 MetaLite 仍要自研 ORM?原因不是重复造一个更大的框架,而是希望把类型安全、MySQL 与 Elasticsearch 高频语义、多数据源路由、事务时序和复杂 SQL 逃生口放进同一套可控契约。本文结合
backend-orm源码说明这套取舍,也明确它不准备替代 MyBatis 的全部场景。
我长期使用过 MyBatis,也使用过 MyBatis-Plus。
它们能解决问题,生态成熟,团队也容易招聘到熟悉的开发者。尤其是复杂 SQL,MyBatis 让开发者保留了足够直接的控制力;MyBatis-Plus 的通用 CRUD 和 Lambda Wrapper,也显著减少了重复代码。
但在持续开发企业级项目时,我越来越在意另一组问题:
- 简单单表操作为什么仍要在 Mapper、XML、Service 之间来回切换;
- 字段改名后,字符串列名和 SQL 片段为什么很难被 IDE 完整重构;
- 多数据源为什么逐渐演变成注解、字符串、切面和 ThreadLocal 的组合;
- MySQL 与 Elasticsearch 为什么让业务层维护两套完全不同的查询模型;
- 事务为什么总在业务方法入口就启动,而数据源路由可能到第一次 DAO 调用才确定;
- 自研 DSL 一旦遇到联表和窗口函数,是否又会变成另一种更难读的“SQL”。
MetaLite ORM 就是在这些问题下形成的。它不是为了证明 MyBatis 不好,而是为了让 MetaLite 的数据访问层拥有一套更小、更一致、边界由自己控制的工程契约。
一、先说结论:MyBatis 与 MetaLite ORM 解决的问题不完全相同
可以把二者的主要关注点简化为:
| 方案 | 更擅长的问题 |
|---|---|
| MyBatis | SQL 映射、复杂 SQL、存量数据库和精细控制 |
| MyBatis-Plus | 在 MyBatis 之上补充通用 CRUD、Wrapper 和常用插件 |
| MetaLite ORM | 统一高频数据访问契约、组件路由、类型安全与跨存储复用 |
MetaLite ORM 没有试图创造一门覆盖全部数据库能力的新语言,而是把数据访问分成两条路:
高频单表操作 → BaseEntityDao + Criteria / Query / Update 复杂 SQL → BaseSqlDao + 显式 SQL这条分界是整个设计的核心。
如果把源码能力放在同一张表里,差异会更清楚:
| 能力 | MyBatis / MyBatis-Plus 的典型方式 | MetaLite ORM 的选择 |
|---|---|---|
| 通用 CRUD | Mapper;MyBatis-Plus BaseMapper | BaseEntityDao统一 insert、update、delete、exists、count、findOne、findList |
| 类型安全条件 | XML/字符串;MyBatis-Plus Lambda Wrapper | Criteria同时支持字符串和方法引用 |
| 按需返回字段 | 手写 SELECT 或 Wrapper select | Query.includeField/excludeField,用于 findOne、findList 与分页 |
| MySQL 与 Elasticsearch | 通常维护两套客户端和条件模型 | common 层复用BaseEntityDao、Criteria、Query |
| 多数据源 | 多 SqlSession 或动态数据源组件 | 数据源 group、master/slave、@Dao绑定与DbRouter |
| 分库分表 | 插件、中间件或业务代码 | DbRouter与TableRouter分开扩展 |
| 本地事务 | @Transactional最常见 | 只提供编程式入口,第一次 DAO 延迟绑定 DataSource |
| 事务内读一致性 | 依赖动态数据源与事务配置 | QueryToMasterSwitch强制事务内读取主库 |
| 字段更新 | 动态 SQL 或 UpdateWrapper | include、exclude、按 id/ids/Criteria 更新和实体差异比较 |
| 自增主键 | generatedKeys 配置 | 单条与批量回填,并校验批量主键模式 |
| 复杂 SQL | MyBatis 核心优势 | BaseSqlDao保留原生 SQL 逃生口 |
| DAO 观测 | 日志、拦截器或监控插件 | DaoCallAspect、DaoCallLogger预留统一入口 |
这张表不表示 MetaLite 在所有维度上“功能更多”。例如 MyBatis 对复杂 SQL 的控制力和生态成熟度明显更强;MetaLite 的重点是让高频能力能够在同一套工程契约里组合。
还要公开三条边界:TableRouter不是 SQL 解析与跨分片执行引擎;本地事务绑定后只覆盖一个实际 DataSource;Criteria当前不处理联表、子查询和 OR 组合。超出边界时应回到原生 SQL、成熟分片中间件或分布式一致性方案。
二、第一个痛点:简单 CRUD 不应该要求业务层理解太多基础设施
MyBatis 项目常见结构是:
Controller → Service → Mapper 接口 → XML / 注解 SQL对于复杂查询,这种分层很合理;但对按主键查询、条件列表、分页、更新指定字段等高频操作,大量结构只是在重复描述实体和表之间的关系。
MetaLite 把这些稳定操作收进BaseEntityDao<T>:
TfindOneById(Serializableid);List<T>findListByCriteria(Criteriacriteria);intupdateByCriteria(Criteriacriteria,Updateupdate);intdeleteByCriteria(Criteriacriteria);longcountByCriteria(Criteriacriteria);JDBC 实现由BaseEntityJdbcDao<T>承担,业务 DAO 只需要继承并声明实体类型:
@Dao(dataSourceGroup="main")publicclassUserDaoextendsBaseEntityJdbcDao<UserEntity>{}框架从泛型、@Table、@Column和主键元数据中生成高频 SQL,再交给 Spring JDBC 使用占位符执行。
它减少的不是一条 SQL,而是简单需求在多个文件之间跳转的理解成本。
三、第二个痛点:字段字符串无法获得完整重构保护
MyBatis XML、注解 SQL 和普通字符串条件都可能出现:
user_name userName "userName"实体字段重命名时,IDE 能修改 Getter 和方法引用,却不能判断每一个字符串究竟是不是数据库字段。
MyBatis-Plus 的 Lambda Wrapper 已经提供了一种成熟改善方式。MetaLite ORM 采用相同方向,把可序列化方法引用下沉到公共条件模型:
Criteria.where(UserEntity::getStatus,1).like(UserEntity::getName,"张%");EntityHelper.genFieldName通过SerializedLambda提取 Getter 对应的属性名:
UserEntity::getUserName → getUserName → userName必须说明,MetaLite 当前仍保留字符串重载,因为动态字段、ES 专有字段和某些框架场景无法全部使用方法引用。因此它是“优先类型安全”,不是“彻底消灭字符串”。
四、第三个痛点:业务同时使用 MySQL 和 Elasticsearch
许多项目的现实结构是:
MySQL → MyBatis / MyBatis-Plus Wrapper Elasticsearch → Java Client Query DSL两者能力当然不同,但等于、范围、集合、空值、分页和排序等查询意图高度重复。如果业务层必须维护两套条件对象,切换存储或做双写迁移时,重复会非常明显。
MetaLite 将公共契约放在backend-orm-common:
Criteria Query Update Pageable OrderBy BaseEntityDaoJDBC 实现把 Criteria 翻译成占位符 SQL,ES 实现把相同高频条件翻译成 Elasticsearch Query。
但这层统一有明确边界:
- SQL LIKE 与全文检索不是同一种语义;
- ES 地理查询没有必要伪装成关系数据库能力;
- 复杂聚合、关联和存储专有特性不能强行统一;
- 当前
AggCriteria仍只是预留,不能宣传为完整聚合能力。
MetaLite 统一的是高频查询意图,不是数据库本身。
五、第四个痛点:多数据源不只是选择一个连接池
MyBatis 生态中,多数据源通常通过注解、AOP 和 ThreadLocal 实现。这种方案很实用,但当系统继续加入主从、分库和事务时,业务代码可能逐渐知道太多路由细节。
MetaLite 把路由拆成几个层次:
@Dao.dataSourceGroup → 选择业务数据源组 → DbRouter 选择组内主库或从库 → JdbcTemplateManager 获取执行对象DAO 声明自己属于哪个数据源组;读写类型决定主从方向;分库规则由DbRouter实现;事务内则强制回到主库,避免同一事务读到复制延迟数据。
这套设计没有消除路由复杂性,而是把它从业务方法转移到可替换的基础设施边界。
六、第五个痛点:事务启动时间可能早于数据源路由
声明式事务通常在进入 Service 方法时获取连接。但动态数据源场景中,真正的路由键可能来自第一次 DAO 操作的用户、租户、城市或业务参数。
如果事务先绑定默认 DataSource,后续再切库已经太晚。
MetaLite 的TransactionContext使用两阶段状态:
PENDING → 记录需要事务,但暂不获取连接 BOUND → 第一次 DAO 调用确定 DataSource 后真正启动事务TransactionManager.doInTransaction只包围需要原子性的 DAO 操作,而不是把远程调用、复杂计算和等待时间全部放进数据库事务。
它的边界同样明确:本地事务不能跨数据源;跨数据源或跨服务一致性要进入 Seata 等分布式事务方案。
七、自研 ORM 最容易犯的错:假装能够表达所有 SQL
如果 Criteria 最终长出联表、子查询、窗口函数、任意 OR 树、数据库 Hint 和所有方言,它很可能变成一门比 SQL 更难学习的语言。
MetaLite 的Criteria源码直接声明:主要支持单表 AND 并列条件。遇到复杂场景,使用BaseSqlDao:
List<E>findListBySql(Stringsql,LinkedHashMap<String,Object>paramMap,Class<E>beanClass);所以 MetaLite ORM 的策略不是“拒绝 SQL”,而是:
简单操作不重复写 SQL 复杂操作不逃避写 SQL这一点实际上保留了 MyBatis 最重要的工程优点:开发者在复杂查询上仍然拥有直接控制权。
八、MetaLite ORM 的模块为什么拆成四部分
backend-orm当前结构如下:
backend-orm-common → 公共 DAO、Criteria、Query、Update、路由接口 backend-orm-jdbc → Spring JDBC、SQL 生成、多数据源和本地事务 backend-orm-es → Elasticsearch 客户端、查询翻译和索引路由 backend-orm-seata → DataSourceProxy、XID 绑定与传播这种拆分让业务可以按需依赖:只用 MySQL 不必引入 ES,只用本地事务不必启用 Seata。
公共契约负责稳定,具体存储实现负责差异,分布式事务作为可选能力接入。
九、哪些项目更适合继续使用 MyBatis
自研不等于适合所有人。以下场景继续使用 MyBatis 或 MyBatis-Plus 往往更合理:
- 团队已经有大量稳定 Mapper 和 XML;
- 核心工作以复杂 SQL、报表和存储过程为主;
- 团队依赖成熟插件生态;
- 没有 MySQL 与 ES 统一高频契约的需求;
- 不希望承担自研 ORM 的测试和维护成本。
而 MetaLite ORM 更适合:
- 大量操作是单表高频 CRUD;
- 希望业务 DAO 保持统一接口;
- 需要多数据源、主从和路由边界;
- 同时使用关系数据库和 Elasticsearch;
- 愿意为可控的底座契约维护自己的实现。
十、自研 ORM 的真正成本是什么
写出一个findById并不难,难的是长期保证:
- SQL 参数始终使用预编译绑定;
- null 字段和批量实体形状得到正确定义;
- 自增 ID 单条与批量回填可验证;
- 事务传播、隔离级别和超时符合预期;
- 主从路由与事务一致性不冲突;
- ES 批量部分失败不会被当成整体成功;
- 新增操作符在 JDBC 与 ES 上有清晰语义;
- 复杂能力超出 DSL 后有明确逃生口。
这也是为什么 MetaLite ORM 必须把“当前不支持什么”写进文章。自研框架的可信度,不来自功能清单,而来自边界是否诚实。
十一、我为什么最终选择自己写
使用 MyBatis 和 MyBatis-Plus 的经历,让我更清楚成熟框架解决了什么,也让我确认 MetaLite 想解决的是另一层问题:
不是让每一种 SQL 都更容易写,而是让企业应用最常见的数据访问拥有统一、可追踪、可替换的工程边界。
所以 MetaLite ORM 保留了类型安全的高频 DSL,也保留了显式 SQL;统一了 MySQL 与 ES 的公共意图,也承认两种存储的差异;封装了多数据源和事务时序,也没有把本地事务宣传成分布式事务。
它不是 MyBatis 的“全面替代品”,而是 MetaLite 为自己的工程目标做出的底座选择。
框架简介
MetaLite 是面向企业生产环境的新一代 Java 微服务技术底座。系列文章重点分享代码背后的设计思路、技术取舍与工程实践。
源码基线
JDK 21、Spring Boot 3.2.9、Spring Cloud 2023.0.1、Spring Cloud Alibaba 2023.0.1.3,具体组件版本以项目backend-bom为准。
作者简介
15 年 Spring 体系企业级开发经验,专注于 Java 微服务架构、工程治理与生产实践。
持续更新
MetaLite 系列内容将持续更新,围绕核心设计、源码链路、技术取舍与生产实践展开。欢迎关注作者,及时获取后续内容。
在线演示
演示地址: https://admin.metalite.top/
演示账号: guess
演示密码: admin@2026