Java 持久层框架选型:JPA vs MyBatis,到底该选谁?

一个老生常谈又绕不开的问题

做 Java 后端开发,持久层框架的选择是个永恒的话题。进新公司、开新项目、重构老系统——几乎每个 Java 团队都会经历一次这个决策。

两边都有忠实拥趸,网上的论战贴能盖几百层楼。JPA 派说 MyBatis 是半成品,MyBatis 派说 JPA 是黑盒魔法。

作为两个框架都用过的开发者,我的态度很明确:没有银弹,只有适合不适合。

这篇不打算站队,就聊聊两个框架在我实际项目里的真实体验。

先认识一下这两位

JPA(Java Persistence API)是 Java 官方定义的 ORM 规范,Hibernate 是它的主流实现。核心思路是"对象关系映射"——你把 Java 对象和数据库表之间的映射关系告诉它,增删改查它帮你生成 SQL。

典型的 JPA 用法:

@Entity
@Table(name = "user")
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String username;
    private String email;
    
    @OneToMany(mappedBy = "user")
    private List<Order> orders;
}

// 使用
User user = userRepository.findById(1L).orElse(null);
List<User> adminUsers = userRepository.findByRole("admin");

MyBatis 是更偏向 SQL 控制的框架,你把 SQL 写清楚,它帮你映射结果。SQL 写得好不好,直接决定了性能好与不好。

MyBatis 的典型用法:

<mapper namespace="com.example.UserMapper">
    <select id="findUserWithOrders" resultMap="UserResultMap">
        SELECT u.*, o.id as order_id, o.amount 
        FROM user u 
        LEFT JOIN `order` o ON u.id = o.user_id 
        WHERE u.id = #{id}
    </select>
</mapper>

JPA 的爽和痛

爽的地方

开发速度快得离谱。 简单的 CRUD 基本不用写 SQL。定义好 Entity,继承 JpaRepositoryfindByIdfindAllsavedelete 全有了。搭个 Admin 后台或者原型系统,JPA 能把开发速度提升一倍。

对象思维对业务代码友好。 电商项目里,订单关联商品、用户关联地址,用 @OneToMany@ManyToOne 配置好关系,代码读起来就是自然语言。

Order order = orderRepository.findById(orderId);
List<OrderItem> items = order.getItems();
// 每个 item 又可以拿到关联的商品信息
String productName = item.getProduct().getName();

没有拼接 SQL 的割裂感,全程都是对象操作。

数据库无关性。 Hibernate 的方言(Dialect)机制让你切换数据库很方便。项目早期用 H2 做单元测试,上线切 MySQL,几乎不用改代码。

痛的地方

N+1 查询是头号杀手。 新手最容易踩的坑。查询 100 条订单时关联查商品,JPA 默认又发了 100 条 SQL。加 @EntityGraph@BatchSizejoin fetch 能解决,但在复杂的查询场景下调优起来真的很头疼。

复杂查询写起来像受刑。 多表关联、动态条件、分组统计、子查询 —— 这些场景用 JPA 的 Specification@Query 写 JPQL,体验非常不好。

// 一个稍微复杂点的查询
@Query("SELECT u FROM User u JOIN u.orders o " +
       "WHERE u.status = :status AND o.amount > :minAmount " +
       "AND u.createdAt BETWEEN :start AND :end " +
       "GROUP BY u HAVING COUNT(o) > :minOrderCount")

看着跟 SQL 差不多,但它不是 SQL。hint、force index、复杂函数这些 SQL 特性用不了,你拿它没办法。

优化起来像隔着一层雾。 SQL 是 Hibernate 生成的,Hibernate 觉得应该用 left join,但你知道 inner join 就够了,可你没法直接告诉它。最终只能退回到 @Query 写 native SQL,那用 JPA 的意义还剩多少?

MyBatis 的爽和痛

爽的地方

SQL 完全掌控在手。 查询要加 FORCE INDEX?窗口函数?跨库 join?MyBatis 里直接写原生 SQL,数据库支持什么你就能用什么。DBA 给你的优化建议,一把就能落地。

动态 SQL 是真方便。 <if><choose><foreach> 写动态条件,比 JPA 的 Specification 直观一百倍。

<select id="searchUsers" resultType="UserVO">
    SELECT * FROM user
    <where>
        <if test="username != null">
            AND username LIKE CONCAT('%', #{username}, '%')
        </if>
        <if test="status != null">
            AND status = #{status}
        </if>
        <if test="startTime != null">
            AND created_at >= #{startTime}
        </if>
    </where>
    ORDER BY created_at DESC
    LIMIT #{offset}, #{pageSize}
</select>

这段 SQL 一眼就知道在干什么,出了性能问题也能直接在数据库里跑一遍验证。

结果映射灵活。 数据库字段叫 user_name,Java 属性叫 userNameresultMap 配一下就行。DO、DTO、VO 之间用不同的映射配置,从数据源到展示层一次搞定。

痛的地方

模板文件多到怀疑人生。 每个实体要配一个 XML,XML 里要写基础 CRUD,项目一膨胀 XML 数量也跟着膨胀。

更头疼的是维护,改一个字段名,XML 里的 SQL 和 resultMap 都得改,漏一个就是运行时错误。

没有自动建表。 JPA 的 ddl-auto: update 开发时太香了。MyBatis 需要自己维护数据库 DDL,不同环境之间 sync 数据库结构是个麻烦事。用 Flyway 或者 Liquibase 能解决,但也多了一个学习成本。

项目里的 SQL 分散在各处。 没有统一的地方审查所有查询。想看看当前版本改了哪些 SQL?得去 Git diff 里翻 XML。查到的 SQL 还经常带着 MyBatis 的占位符,拷到数据库没法直接跑。

什么场景选 JPA

标准 CRUD 项目占 80% 以上。 大部分业务就是增删改查,关联关系简单。JPA 自动生成 SQL 足够了。

领域驱动设计项目。 代码强调业务语义,领域模型之间有复杂关联关系。JPA 的实体映射 + 级联操作 + 持久化上下文,跟 DDD 的理念天然匹配。

初创或原型阶段。 先快速验证业务,后面的问题后面再说。JPA 的快速启动能力在这个阶段价值巨大。

数据库切换频繁。 比如开发用 H2、测试用 PostgreSQL、生产用 MySQL,JPA 的方言切换省心不少。

什么场景选 MyBatis

复杂查询密集。 报表系统、数据分析后台、运营管理后台,这些地方 SQL 逻辑复杂、性能要求高,MyBatis 能让你精细控制每一行 SQL。

老项目维护。 接手一个遗留系统,里面一堆几百行的复杂 SQL。不要想着用 JPA 重写——适配成本太高。保持 MyBatis,按需优化 SQL 就够了。

SQL 优化文化强的团队。 团队里有专职 DBA,或者团队本身就有强烈的 SQL 性能意识。MyBatis 让 DBA 能直接 review SQL,给出优化建议。

数据库特性重度依赖。 用到存储过程、自定义函数、特定数据库的 hint 或索引类型这些高级特性,MyBatis 是你唯一的选择。

一张表帮你快速决策

因素 倾向 JPA 倾向 MyBatis
业务复杂度 简单 CRUD 复杂查询/报表
团队能力 偏对象思维 偏 SQL 思维
数据库切换 频繁 基本固定
性能要求 中等 苛刻
项目阶段 快速启动 维护优化
领域模型 丰富关联 简单映射

可以不二选一

很多人觉得两个框架水火不容,实际上把它们用在一个项目里并不冲突。

拿我做过的一个电商项目举例:

  • 用户、商品、订单 这些核心领域用 JPA。业务逻辑围绕实体流转,代码清晰好理解。
  • 运营后台的数据看板、报表导出 用 MyBatis。几十行的统计 SQL 直接写原生,性能有保障。
  • 两个框架用不同的包路径区分,Service 层之上完全无感。

Spring Boot 支持两个框架共存,配置各自的数据源和 Mapper 扫描路径就行。

前提是团队两边都要熟,不然维护起来两边都不讨好。

总结

JPA 和 MyBatis 不是竞争对手,是应对不同场景的两把工具。

选 JPA: 业务逻辑复杂但查询简单,代码把速度当优先级,对象思维占主导。

选 MyBatis: 查询逻辑复杂且性能敏感,SQL 优化是刚需,DBA 需要把控所有查询。

小项目用自己喜欢的就行,大项目根据核心场景来选择。两边都深入用过,你自然就知道怎么选了。