不吹不黑,一针见血。下面从 15 个维度把三者的底裤扒干净。
1. 设计哲学
|
框架
|
哲学
|
一句话
|
|
JPA
|
全自动 ORM,"你操作对象,SQL 我来生成"
|
黑盒——方便到让你忘记 SQL 存在,也让你不知道它到底发了什么 SQL
|
|
MyBatis-Plus
|
增强工具,"MyBatis 的油门助力,但方向盘还是你的"
|
半自动——帮你省掉模板代码,复杂 SQL 还是你自己写 XML
|
|
Xbatis
|
重度 ORM + 链式 DSL,"写代码像写 SQL,但不用写 SQL"
|
透明——API 自带 SQL 语义,你看得见每一步在干什么,但不需要手写字符串
|
结论:JPA 想让你忘了数据库;MP 让你少写模板 SQL;Xbatis 让你用 Java 写出 SQL 的表达力。
2. 核心 API 风格
JPA:
// 方法命名约定 —— 简单查询靠猜名字
List<User> findByAgeGreaterThanAndNameLike(int age, String name);
// 复杂查询靠 JPQL 字符串@Query("SELECT u FROM User u JOIN u.roles r WHERE r.name = :roleName")
List<User> findByRole(@Param("roleName") String roleName);
// 动态查询靠 Criteria API —— 噩梦级冗长CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> root = cq.from(User.class);
List<Predicate> predicates = new ArrayList<>();
if (name != null) predicates.add(cb.like(root.get("name"), "%" + name + "%"));
// ... 20 行后终于拿到结果复制
MyBatis-Plus:
// Wrapper 是参数,不是方法体 —— API 分裂感强烈
LambdaQueryWrapper<User> wrapper = Wrappers.<User>lambdaQuery()
.like(StringUtils.isNotEmpty(name), User::getName, name)
.eq(User::getAge, 18);
List<User> list = userMapper.selectList(wrapper);
// 多表?对不起,回 XML复制
Xbatis:
// 链式 DSL,方法体即查询 —— 一气呵成
List<UserVO> list = dao.queryChain()
.from(User.class)
.leftJoin(User::getRoleId, Role::getId)
.eq(User::getAge, age, Objects::nonNull)
.like(User::getName, name, StringUtils::isNotEmpty)
.returnType(UserVO.class)
.list();
复制
结论:JPA 的方法名魔法在小项目里爽,大项目里是灾难;MP 的 Wrapper 是二等公民(方法参数),割裂感强;Xbatis 的 Chain 是一等公民(方法体),连贯到底。
3. 单表 CRUD:谁更省事?
|
操作
|
JPA
|
MyBatis-Plus
|
Xbatis
|
|
按 ID 查
|
repo.findById(id)
|
mapper.selectById(id)
|
dao.getById(id)
|
|
按 ID 查部分列
|
❌ 需 @Query 投影
|
❌ 需手写 select
|
dao.getById(id, User::getName, User::getAge)
|
|
按 ID 查单列值
|
❌
|
❌
|
dao.getValueById(id, User::getName)
|
|
批量插入
|
repo.saveAll(list)
|
mapper.insertBatch(list)
|
dao.insertChain().insert(list).execute()
|
|
部分字段更新
|
❌ 需先查再设再存
|
mapper.updateById(entity) 全字段
|
dao.partialUpdate(model) 精准修改
|
|
重复插入策略
|
❌
|
❌
|
内置 ON DUPLICATE KEY / ON CONFLICT
|
|
RETURNING 子句
|
❌
|
❌
|
updateChain().returning(...) 更新后直接返回结果
|
亮点:Xbatis 的 partialUpdate 是三者中唯一做到"先查出来,只改我关心的字段"的。JPA 需要 @DynamicUpdate,MP 只能用 LambdaUpdateWrapper 逐字段 set。
4. 多表联查——撕开底裤的一刀
这是三者的分水岭。
JPA:
// 靠 @OneToMany/@ManyToOne 隐式关联,爽在一时,死在 N+1@OneToMany(mappedBy = "user", fetch = FetchType.LAZY)private List<Order> orders;
// 然后某天发现一个列表页发了 500 条 SQL —— 因为忘了写 @EntityGraph复制
MyBatis-Plus:
// 官方态度:多表查询请回 XML// 没有 join API,没有联表 DSL,Wrapper 只负责 WHERE 条件// 这意味着你还是要写:@Select("SELECT u.*, r.role_name FROM sys_user u LEFT JOIN sys_role r ON u.role_id = r.id WHERE ...")
List<UserVO> selectUserWithRole(@Param("ew") Wrapper<User> wrapper);
// XML + Mapper + 手写 SQL —— MP 在这里等于裸 MyBatis复制
Xbatis:
// 原生 join DSL
dao.queryChain()
.from(User.class) // 主表
.leftJoin(User::getRoleId, Role::getId) // 方法引用 join,IDE 可重构
.leftJoin(User::getDeptId, Dept::getId)
.returnType(UserRoleDeptVO.class) // VO 自动映射
.paging(Pager.of(1, 10));
// 或者用 @Fetch 先查 A 再带出 B@Fetch(source = User.class, target = Role.class,
sourceField = "roleId", targetField = "id")复制
结论:JPA 隐式关联是双刃剑,简单场景爽、复杂场景炸;MP 在多表这件事上直接放弃治疗;Xbatis 是唯一把多表查询当一等公民对待的。
5. 动态条件查询
|
场景
|
JPA
|
MyBatis-Plus
|
Xbatis
|
|
搜索表单(忽略 null/空串/trim)
|
Specification 冗长
|
Wrapper 需手动判空
|
forSearch(true) 一键搞定
|
|
精确条件(按值决定是否忽略)
|
同 Specification
|
wrapper.eq(cond, field, val)
|
.eq(field, val, Objects::nonNull) predicate 重载
|
|
OR 条件成组
|
cb.or(...) 嵌套地狱
|
wrapper.and(w -> w.or(...))
|
.nested(chain -> chain.or(...))
|
|
对象转条件
|
❌
|
❌
|
@ConditionTarget + QO.where() 注解驱动
|
|
对象转排序
|
❌
|
❌
|
@OrderByTarget 自动转换
|
Xbatis 的 QO(查询对象)——杀手级差异:
@Data@ConditionTarget(User.class)public class UserQO implements QO {
@Condition(Condition.Type.LIKE)
private String name;
@Condition
private Integer age;
@Override
public Where where() {
return WhereUtil.where(this);
}
}
// Service 层干净得像假的:public List<UserVO> search(UserQO qo, Pager pager) {
return userDao.queryChain(qo.where()).paging(pager);
}
复制
结论:JPA 的 Specification 写一次你就想辞职;MP 的 Wrapper 在 Service 层散落一堆 if 判空;Xbatis 的 QO 把条件和查询分离,是三者中架构最干净的。
6. 分页能力
|
能力
|
JPA
|
MyBatis-Plus
|
Xbatis
|
|
基础分页
|
Pageable
|
Page<T> + 插件
|
Pager 内建
|
|
自动 COUNT 优化
|
❌
|
基础
|
激进:自动去掉无用 LEFT JOIN、ORDER BY
|
|
JOIN 分页优化
|
❌
|
❌
|
自动识别 1 对多,支持 rightJoin 切换
|
|
子查询分页
|
❌
|
❌
|
✅ 原生支持
|
|
无 SQL 解析分页
|
❌
|
❌(依赖 JsqlParser)
|
✅ 更轻量
|
结论:JPA 的分页靠 Pageable 传参,SQL 优化靠祈祷。MP 靠 JsqlParser 解析。Xbatis 的分页优化是内置的、无解析开销的,而且对 JOIN 场景做了专项处理。
7. VO 映射 / 结果处理
|
能力
|
JPA
|
MyBatis-Plus
|
Xbatis
|
|
自动列选择
|
❌ 需 DTO 投影
|
❌
|
select(VO.class) 自动推导
|
|
JOIN 结果自动映射
|
❌
|
❌
|
returnType(VO.class) 一键
|
|
多层嵌套结果
|
❌
|
❌
|
@NestedResultEntity
|
|
枚举名称注入
|
❌
|
❌
|
@PutEnumValue
|
|
@Fetch 二次查询
|
❌(可用 @EntityGraph)
|
❌
|
✅ 原生 @Fetch
|
直观对比——联表返回 VO:
JPA 做法:写 JPQL + 构造器表达式,或 @SqlResultSetMapping,或投影接口,没有标准优雅方案。
MP 做法:回退 XML,手写 ResultMap。
Xbatis 做法:
@ResultEntity(User.class)public class UserVO {
private Long id;
private String name;
@PutEnumValue // 自动注入枚举中文名
private String statusName;
@ResultEntityField(target = Role.class, property = "roleName")
private String roleName; // 连表字段自动映射
}
// 查询:returnType(UserVO.class) 即可,不需要 select("u.id, u.name, ...")复制
结论:VO 映射是 JPA 的阿喀琉斯之踵,是 MP 的不治之症,是 Xbatis 的舒适区。
8. Mapper 模式
|
模式
|
JPA
|
MyBatis-Plus
|
Xbatis
|
|
每实体一个 Mapper/Repository
|
✅
|
✅
|
可选(多 Mapper 模式)
|
|
单 Mapper 模式
|
❌
|
❌
|
✅ 一个 BasicMapper 操作所有实体
|
单 Mapper 的意义:当你有 200 张表,MP 需要 200 个 Mapper 接口 + 200 个 XML 文件。Xbatis 的单 Mapper 模式下,一个 XbatisMapper extends BasicMapper 通吃所有实体,项目越大优势越明显。
结论:JPA 的 Repository 和 MP 的 Mapper 在项目膨胀时都是文件爆炸。Xbatis 的单 Mapper 是工业化项目的真实刚需。
9. 逻辑删除 / 多租户 / 乐观锁
三个框架都支持,但实现质量天差地别:
|
特性
|
JPA
|
MyBatis-Plus
|
Xbatis
|
|
逻辑删除
|
@SQLDelete + @Where Hibernate 扩展
|
@TableLogic
|
@LogicDelete + @LogicDeleteTime(支持除 flag 外任意字段 set)
|
|
多租户
|
❌ 需自己实现拦截器
|
插件,需配置
|
@TenantId 注解驱动,透明注入
|
|
乐观锁
|
@Version(JPA 标准)
|
@Version
|
@Version
|
|
动态值/审计字段
|
@CreatedDate 等 Spring Data 注解
|
@TableField(fill = ...)
|
@TableField(defaultValue = "{NOW}", update = false) 模板语法更强
|
Xbatis 逻辑删除的独特性:不仅支持 deleted=1,还支持删除时自动填入删除时间、删除人。MP 只能设 flag。
结论:三个都"有",但 JPA 的多租户需要自己搞,MP 的逻辑删除偏简单,Xbatis 的注解驱动更彻底。
10. SQL 控制力 & XML 回退
|
场景
|
JPA
|
MyBatis-Plus
|
Xbatis
|
|
手写 SQL
|
@Query JPQL / Native
|
XML / @Select
|
SQL 模板 + 原生 SQL
|
|
SQL 模板(局部复杂表达式)
|
❌
|
❌
|
Methods.tpl() / Methods.fTpl()
|
|
不同数据库不同 SQL
|
❌
|
❌
|
dbAdapt(selector -> ...) 局部差异化
|
|
XML 是否必须
|
基本不用
|
复杂场景必须
|
最后手段(90%+ 需求不需要)
|
Xbatis 的 SQL 模板——在 DSL 中精准插入原生 SQL:
dao.queryChain()
.select(User.class)
.select(Methods.fTpl("COALESCE({0}, 0) as total", User::getScore))
.from(User.class)
.list();
复制
结论:JPA 用 JPQL 是另一种语言。MP 复杂查询必须 XML。Xbatis 让你绝大部分时候不需要 XML,即使需要也能用模板在 DSL 中局部嵌入,而非整个查询回退。
11. 跨数据库兼容
|
能力
|
JPA
|
MyBatis-Plus
|
Xbatis
|
|
内置数据库函数
|
❌(靠方言)
|
❌
|
✅ 13+ 种数据库通用函数
|
|
不同数据库不同 ID 自增策略
|
❌
|
❌
|
✅
|
|
分页方言
|
✅ Hibernate 方言
|
基础
|
深度适配
|
|
代码零改动迁移
|
理论可行,实际坑多
|
❌
|
✅
|
结论:JPA 靠 Hibernate 方言层,但遇到复杂函数就跪。MP 基本不考虑跨库。Xbatis 内置了大量跨数据库函数(如 Methods.concat()、Methods.dateFormat()),一套代码跑多种数据库不折腾。
12. 注解体系完整度
|
注解类型
|
JPA
|
MyBatis-Plus
|
Xbatis
|
|
实体映射
|
@Entity @Table @Id @Column
|
@TableName @TableId @TableField
|
@Table @TableId @TableField
|
|
逻辑删除
|
❌(Hibernate 扩展)
|
@TableLogic
|
@LogicDelete @LogicDeleteTime
|
|
多租户
|
❌
|
❌
|
@TenantId
|
|
乐观锁
|
@Version
|
@Version
|
@Version
|
|
结果映射
|
❌
|
❌
|
@ResultEntity @NestedResultEntity @ResultCalcField
|
|
条件映射
|
❌
|
❌
|
@ConditionTarget @Condition @Conditions @ConditionGroup
|
|
Fetch 抓取
|
@OneToMany(fetch=...)
|
❌
|
@Fetch
|
|
枚举值注入
|
❌
|
❌
|
@PutEnumValue
|
|
POJO 安全检查
|
❌
|
❌
|
✅ 启动时检查映射缺口
|
|
排序对象
|
❌
|
❌
|
@OrderByTarget
|
结论:JPA 只有 ORM 基础注解,MP 只有单表注解。Xbatis 的注解体系是唯一覆盖"字段→条件→结果→排序→安全检查"全链路的。
13. 代码生成器
|
能力
|
JPA
|
MyBatis-Plus
|
Xbatis
|
|
Entity 生成
|
IDE / 手动
|
✅ 成熟
|
✅
|
|
Mapper/DAO 生成
|
不需要(Repository 足够)
|
✅ 全套
|
✅
|
|
可视化配置
|
❌
|
✅
|
❌(较弱)
|
|
自定义模板
|
❌
|
✅ Velocity/Freemarker/Enjoy
|
✅
|
结论:MP 的代码生成器是它的招牌,成熟度最高。Xbatis 有但不如 MP 丰富。如果代码生成器是你的核心选型依据,MP 赢。
14. 生态与社区
|
维度
|
JPA
|
MyBatis-Plus
|
Xbatis
|
|
社区规模
|
🌍 全球标准
|
🇨🇳 国内霸主
|
🔥 新兴(GitHub 132 Stars)
|
|
文档质量
|
⭐⭐⭐⭐
|
⭐⭐⭐⭐⭐
|
⭐⭐⭐⭐(中文详尽)
|
|
问题解决速度
|
Google 随便搜
|
CSDN/掘金 海量
|
社区尚小
|
|
Spring Boot 集成
|
原生
|
Starter 齐全
|
Starter 齐全
|
|
Solon 集成
|
❌
|
✅(3.5.9+)
|
✅ 原生 Solon Starter
|
|
GraalVM Native
|
❌
|
3.5.17+ 支持
|
❌
|
结论:JPA 有 Java EE 标准背书,MP 有用户基数碾压,Xbatis 的生态还需要时间。
15. 学习曲线
|
框架
|
入门门槛
|
精通门槛
|
典型痛点
|
|
JPA
|
低
|
极高
|
持久化上下文、懒加载、N+1、缓存机制——你以为懂了,上线就炸
|
|
MyBatis-Plus
|
低
|
中
|
Wrapper 写复杂条件时嵌套地狱,多表必须回 XML
|
|
Xbatis
|
中
|
中
|
概念稍多(Chain/QO/Model/VO),但一旦理解就一通百通
|
结论:JPA 是"入门即巅峰,深入即地狱"。MP 是"一马平川,直到需要 JOIN"。Xbatis 是"前 3 天消化概念,后面全是爽的"。
最终裁决:选谁?
┌─────────────────────────────────────────────────────┐
│ 你的项目以单表 CRUD 为主,团队想快速出活 │
│ → MyBatis-Plus。代码生成器一把梭,社区方案多。 │
├─────────────────────────────────────────────────────┤
│ 你的项目是标准的企业级应用,有大量关联查询 │
│ → Xbatis。JOIN 不用 XML,VO 自动映射,分页自动优化。 │
├─────────────────────────────────────────────────────┤
│ 你追求 Java EE 标准、DDD、纯面向对象 │
│ → JPA。但要准备好为 N+1 和复杂查询付出额外代价。 │
├─────────────────────────────────────────────────────┤
│ 你的项目有 100+ 张表,复杂报表,还要跨数据库 │
│ → Xbatis。单 Mapper + 原生 JOIN + SQL 模板,无对手。 │
├─────────────────────────────────────────────────────┤
│ 你已经有成熟的 MP 项目,不想重构 │
│ → 继续 MP。别折腾。 │
├─────────────────────────────────────────────────────┤
│ 你从 Spring Boot 迁移到 Solon │
│ → Xbatis。Solon 原生集成,MP 的 Solon 支持是后补的。 │
└─────────────────────────────────────────────────────┘
复制
一句话暴击:
-
JPA:你以为它在帮你,其实它在瞒你。N+1 的债,迟早要还。
-
MyBatis-Plus:单表的神,多表的狗。JOIN 的那一刻,它把你丢回给了 MyBatis。
-
Xbatis:把 MyBatis 真正 ORM 化的框架。JOIN、VO、分页、多租户——API 到哪,能力到哪,不需要 XML 救场。