⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 网络 「网络」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

这道题为什么总被问?

面试官问这题,不是要你背功能对比表。他想看的是:你到底有没有在真实项目里做过技术选型。

答得好的人,三句话就能让面试官点头:”我们项目报表模块用的 MyBatis,因为涉及大量多表关联和窗口函数,SQL 必须手写;后台配置管理用的 JPA,简单 CRUD 没必要写 SQL。” 这叫实战判断力,比背十条区别有用一百倍。

答得差的人呢?”Hibernate 是全自动 ORM,MyBatis 是半自动……” 背完对面已经在想下一题了。

一句话讲透本质

Hibernate 赌你不需要碰 SQL;MyBatis 赌你离不开 SQL。两个赌注都对,只是赌的场景不同。

Hibernate 的哲学是”对象即世界”——你操作 Java 对象,它替你生成 SQL,替你管理对象状态(瞬时态/持久态/游离态),甚至替你决定什么时候该 flush。你越不碰 SQL,它越开心。

MyBatis 的哲学正好反过来——“SQL 是你的事,映射是我的事”。你写 SQL,它把结果往 Java 对象上一填,完事。它不管你 SQL 写得好不好,只负责搬运。

这不是谁高谁低的问题。选错了才有高低: 拿 Hibernate 写复杂报表是自虐,拿 MyBatis 做简单 CRUD 是浪费生命。

上代码:同一个需求,两种画风

别光说不练。同一个需求——“按用户名查用户”,看看两边写出来的代码差多少。

Hibernate 风格——SQL 对你透明:

// 方式一:HQL(面向对象的查询语言,写的是类名不是表名)
String hql = "FROM User u WHERE u.username = :name";
List<User> users = session.createQuery(hql, User.class)
.setParameter("name", "张三")
.list();

// 方式二:Criteria API(类型安全,IDE 能自动补全,但写起来啰嗦)
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> root = cq.from(User.class);
cq.select(root).where(cb.equal(root.get("username"), "张三"));
List<User> result = session.createQuery(cq).getResultList();

// 方式三:真扛不住了,可以直接写原生 SQL(很多人不知道这个)
List<User> nativeResult = session.createNativeQuery(
"SELECT * FROM t_user WHERE username = ?1", User.class)
.setParameter(1, "张三")
.getResultList();

注意:HQL 写的是 FROM User,不是 FROM t_user。你面对的是对象,不是表。

MyBatis 风格——SQL 一目了然:

<!-- UserMapper.xml -->
<mapper namespace="com.example.mapper.UserMapper">
<!-- 简单查询:一眼看到执行了什么 SQL -->
<select id="selectByName" resultType="com.example.model.User">
SELECT id, username, email, create_time
FROM t_user
WHERE username = #{name}
</select>

<!-- 动态 SQL:MyBatis 真正的杀手锏 -->
<select id="selectByCondition" resultType="com.example.model.User">
SELECT id, username, email, create_time
FROM t_user
<where>
<if test="username != null">AND username = #{username}</if>
<if test="email != null">AND email = #{email}</if>
<if test="startTime != null">AND create_time &gt;= #{startTime}</if>
</where>
ORDER BY create_time DESC
LIMIT #{offset}, #{limit}
</select>
</mapper>
// Mapper 接口——方法名和 XML 里的 id 对应
public interface UserMapper {
User selectByName(@Param("name") String name);
List<User> selectByCondition(UserQuery query);
}

// 调用——干净利落
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
User user = mapper.selectByName("张三");

写了什么 SQL,执行的就是什么。没有中间商赚差价。

真正决定选谁的六件事

面试背对比表没用,项目里做选型才需要较真。逐条过:

1. SQL 控制力——这是最核心的分歧

Hibernate 自动生成 SQL,简单查询很爽,但碰到多表关联、窗口函数、EXPLAIN 后需要加 hint……你会发现自己在跟框架较劲,而不是在写业务。MyBatis 不存在这个问题——SQL 你写的,EXPLAIN 结果你心里有数。

2. 开发效率——此消彼长

Hibernate 简单 CRUD 零代码:加个字段,改个注解,完事。MyBatis 呢?XML 里加 <result>、改 SQL、可能还要改 VO……繁琐。但 MyBatis-Plus 出来之后,单表 CRUD 的效率差距基本抹平了。

3. 性能——不是框架的锅,是人的锅

Hibernate 默认配置极其容易踩 N+1 查询的坑。一个 @OneToMany 没设 FetchType.LAZY,查一条主记录能触发几十条子查询。MyBatis 不会有这种”惊喜”——你写了什么 SQL,跑的就是什么。但 SQL 写得烂,全表扫描照样卡死。框架不决定性能,人决定。

4. 学习曲线——差距巨大

MyBatis 的学习成本约等于”会写 SQL + 看一遍官方文档”,半天上手。Hibernate?瞬时态、持久态、游离态、脏检查、flush 时机、继承映射策略、二级缓存失效……这套概念体系没两周啃不下来。

5. 数据库移植性——Hibernate 碾压

Hibernate 换数据库只需要改方言配置。MyBatis 用了 MySQL 特有语法(LIMIT、ON DUPLICATE KEY UPDATE)?迁移到 Oracle 时一条条改吧,没有捷径。

6. 缓存机制——Hibernate 开箱即用

Hibernate 一级缓存(Session 级)默认开启,二级缓存(应用级)配一下就能用,读多写少场景很省心。MyBatis 一级缓存默认关闭(Spring 下一个请求一个 SqlSession),二级缓存要手动配还容易翻车。

实际项目怎么选?给你一张决策树

别纠结了,按这个走:

你的 SQL 复杂吗?(多表关联、窗口函数、数据库特有语法)
├── 是 → MyBatis,SQL 你说了算
└── 否 → 你的项目要跨数据库吗?
├── 是 → Hibernate/JPA,换方言就行
└── 否 → 团队 SQL 功底如何?
├── 强 → MyBatis,透明可控
└── 一般 → Hibernate,少写 SQL 少出错

终极建议:大型项目完全可以混用。 核心业务用 MyBatis 保证性能和灵活性;配置表、日志表用 JPA 或 MyBatis-Plus 提效。务实的架构从不搞非此即彼。

两边各有一个”必踩坑”

Hibernate 的坑:N+1 查询

@OneToMany 没设 FetchType.LAZY?查 1 条订单触发 100 条子查询,数据库直接冒烟。记住:关联关系全部设 LAZY,需要的时候用 JOIN FETCH 显式加载。

MyBatis 的坑:动态 SQL 拼接不当

${} 和 #{} 分不清的人不少。${} 是字符串直接替换(有 SQL 注入风险),#{} 是预编译参数。规则只有一条:除了表名、列名这类不能参数化的地方,一律用 #{}。

<!-- ❌ 危险:直接拼接,SQL 注入一步到位 -->
<select id="search" resultType="User">
SELECT * FROM t_user WHERE username = '${name}'
</select>

<!-- ✅ 安全:预编译参数,MyBatis 自动转义 -->
<select id="search" resultType="User">
SELECT * FROM t_user WHERE username = #{name}
</select>
文章目录
  1. 1. 这道题为什么总被问?
  2. 2. 一句话讲透本质
  3. 3. 上代码:同一个需求,两种画风
  4. 4. 真正决定选谁的六件事
  5. 5. 实际项目怎么选?给你一张决策树
  6. 6. 两边各有一个”必踩坑”