资讯动态

从JDBC到ORM:MyBatis与JPA的选型分析及底层原理

发布时间:2026/9/30 12:47:05 来源:尧图企业网站定制
1. 一个让我彻底想写这篇课件的 JDBC 崩溃瞬间先讲个真实经历。几年前我带一个刚入行的同事做项目他要实现一个最简单的功能根据用户 ID 查询一条用户记录。他当时的 JDBC 代码长这样public User findUserById(Long id) { Connection conn null; PreparedStatement ps null; ResultSet rs null; User user null; try { conn DriverManager.getConnection(url, username, password); ps conn.prepareStatement(SELECT * FROM user WHERE id ?); ps.setLong(1, id); rs ps.executeQuery(); if (rs.next()) { user new User(); user.setId(rs.getLong(id)); user.setName(rs.getString(name)); user.setAge(rs.getInt(age)); user.setEmail(rs.getString(email)); user.setCreateTime(rs.getTimestamp(create_time)); } } catch (SQLException e) { e.printStackTrace(); } finally { if (rs ! null) try { rs.close(); } catch (SQLException e) {} if (ps ! null) try { ps.close(); } catch (SQLException e) {} if (conn ! null) try { conn.close(); } catch (SQLException e) {} } return user; }这段代码有什么问题功能是能跑通但从工程角度讲全是问题连接获取和释放埋在一起、异常处理靠 try-catch 层层包裹、大量的搬运工代码——从 ResultSet 里取字段、set 到对象上。我当时开玩笑说你这三十行代码里面真正跟业务有关的两行而已——一行是写 SQL一行是 new User()。后来他要再加一个按部门查询所有用户的接口又是复制粘贴几十行做基本相同的事。这种体力活式的开发就是 JDBC 时代写业务代码的日常。也正是这种日常催生了 ORM 框架。很多人在学习 ORM 的时候只是背了一个概念ORM 是对象关系映射Java 对象和数据库表对应MyBatis 和 JPA 是用来做这个的。但你真的理解它解决了什么、牺牲了什么、为什么会有两个流派、面试里为什么总被问 MyBatis 一级缓存和二级缓存吗这一篇我不会去抄官方文档而是从 JDBC 的痛点开始一层一层把 ORM 的认知框架搭起来。2. JDBC 的真正痛点不是连接数据库而是关系转对象的翻译成本2.1 JDBC 到底做了什么JDBCJava Database Connectivity是 Java 平台操作关系型数据库的底层标准接口。它做的事情其实很原始建立连接、执行 SQL、遍历结果集。这层接口从 JDK 1.1 就存在了二十多年过去核心模型基本没变。一个完整的 JDBC 数据操作流程永远逃不开以下五大步加载驱动现在通常省略SPI 机制自动发现获取 Connection 连接通过连接创建 Statement 或 PreparedStatement执行 SQL拿到 ResultSet 或其他返回值关闭 ResultSet、Statement、Connection这个流程本身不可怕可怕的是第 4 步之后的事情。2.2 ResultSet 到手之后才是噩梦的开始ResultSet 是什么它是一个游标式的数据集合。你可以把它理解成一张一次性的纸质表格只能一行一行往下扫描。你要把这张表格变成 Java 世界里的对象就得手动做字段搬运——用 rs.getLong、rs.getString、rs.getInt 一个字段一个字段地取然后 Projection 到 User 对象的 setter 里。字段少的时候还好说三个五个忍忍就过去了。但真实业务表有几个、十几个、几十个字段是常态而且还有联表查询、子查询、聚合查询。写这样的转换代码本质上是把数据库表中的行翻译成内存里的对象图这个翻译过程是纯重复劳动。网上有个段子我觉得说得很精准JDBC 是 Java 世界里最接近 C 语言的编程体验。你手动管理资源的生命周期你手动完成数据结构的转换你还要手动处理各种 checked exception。不是不能用而是工程效率太低。2.3 Connection 管理的脏活累活另一个看不到但更致命的问题在于连接管理。数据库连接是个昂贵的资源建立一条 TCP 连接、做认证握手、分配会话状态这些开销在并发场景下完全不可接受。所以正规项目一定会用连接池——你要先搞个池从池里取连接用完了还回去。但如果你的 JDBC 代码写得不够规范比如 finally 块写得不对、异常路径没考虑到连接就泄漏了。连接一泄漏池子水位下降下游请求全都阻塞在等连接上然后就是全线超时——排查这种线上问题经常找半天都找不到源头。ORM 框架不光帮你做了映射更把连接获取、事务边界、资源释放这些繁琐的事情统一管理起来了。这也是我从写业务代码转向写框架代码后体会最深的一点。2.4 还有一个隐藏问题SQL 分散在 Java 代码里原生 JDBC 写 SQL 都是在 Java 方法里拼字符串条件一多就是一堆 if-else 拼接String sql SELECT * FROM user WHERE 11; if (name ! null !name.isEmpty()) { sql AND name LIKE % name %; // 还有注入风险 } if (age ! null) { sql AND age age; }这种写法第一是丑第二是不安全字符串拼接 SQL 注入风险极高第三是无法维护。SQL 本身是另一种语言把它硬塞在 Java 代码里两种语言的复杂度互相纠缠代码根本没法看。3. ORM 的核心思路把数据库表当成对象的持久化终端3.1 三个关键动作Mapping、Persistence、CachingORM 的全称是 Object Relational Mapping中文叫对象关系映射。拆开来看就是概念含义对应物Object内存里的 Java 对象User、Order、ProductRelational关系型数据库中的表结构user 表、order 表、product 表Mapping建立两者之间对应关系的规则元数据配置XML、注解、API但 ORM 绝不是对象和表一一对应这么简单四个字。一个完整的 ORM 框架至少要承担三件大事映射Mapping把 Java 类的字段映射为数据库表的列。这里包括基本类型映射、复杂类型映射比如 Java 8 时间类型、Blob、枚举还包括对象之间的关联关系映射——一个用户有多个订单、一个订单属于一个用户这种关系怎么落到表上。持久化操作Persistence Operation对对象执行 save/update/delete/find 操作时自动翻译成 INSERT/UPDATE/DELETE/SELECT SQL 并执行。也就是让你用操作对象的方式操作数据库。生命周期管理Lifecycle Cache做了增删改查之后数据什么时候真正落库、什么时候查询结果该被缓存这是 ORM 引擎层面要考虑的事。3.2 用表格类比理解映射过程我想给零基础的同学一个更生活化的类比。数据库表就好比你家里的储物柜每个格子都有编号和标签列名里面放置的物资数据值有固定的类型——数量必须是数字、日期必须是某一天。而 Java 对象就好比你手里的一个手提箱箱子里有各种独立的小口袋属性每个口袋上写着属性名叫什么。ORM 框架负责做的事就是把储物柜里的物资按标签取出来塞进手提箱对应名称的口袋里。反过来你收拾好手提箱ORM 框架帮你把它按格子标签放到储物柜里去。每次你要取放物资不用自己亲手去开柜子、认标签了交给框架来做就行。你只需要告诉框架箱子上的 name 口袋对应储物柜里叫 name 的格子配置好一次以后都自动跑。这个类比里还有一个重要细节不一定每个格子都要塞进口袋也不一定每个口袋都要对应一个格子。比如 user 表里有个字段是注册来源但你 Java 类里根本不关心这个数据那就忽略它。ORM 的映射规则允许这种灵活裁剪。3.3 ORM 的隐藏价值方言屏蔽数据库的 SQL 语法是有方言差异的。MySQL 的 LIMIT、Oracle 的 ROWNUM 或 FETCH FIRST、SQL Server 的 TOP——同样一个查前 10 条三种数据库三种写法。直接用 JDBC 写死 SQL后期想换数据库基本等于重写。而好一点的 ORM 框架大多提供了方言适配层通过配置 dialect 就能在多种数据库之间切换。当然 MyBatis 本身不强依赖这个它更偏 SQL 模板但搭配 MyBatis 生态里的一些工具和拦截器也能实现部分方言切换能力JPA 在这方面的能力就非常强Hibernate 的方言机制是天生自带的。3.4 ORM 不是什么都能替你做写到这里必须再说清楚一点ORM 解决的是对象与表的映射和常规 CRUD 的自动化但不解决复杂的 SQL 优化和不合理的表结构设计。我见过太多人用 ORM 用出了问题反过来骂框架不好——实际上是他们把 ORM 用成了免写 SQL 神器一条复杂联表查询让 Hibernate 自动生成 SQL结果性能慢到令人发指。后面在 MyBatis 和 JPA 的对比部分我会展开讲这个问题。4. MyBatis 和 JPA 的两条路线SQL 优先还是模型优先4.1 为什么会有两个流派而不是一个标准ORM 这个概念本身并没有规定实现方式于是出现了两种全然不同的设计哲学SQL 优先MyBatis先用 SQL 把数据查出来然后再做映射。SQL 的主体是数据库语言Java 是壳。模型优先JPA/Hibernate先把 Java 的类模型建好然后框架自动生成 SQL。SQL 是影子Java 是本体。4.2 MyBatis把 SQL 当成一等公民MyBatis 的核心设计理念非常简单SQL 不应该是自动生成的而应是由人来写的。它只帮你做两件事一是参数映射二是结果映射。一个典型的 MyBatis Mapper 语句长这样select idfindUserByAgeGreaterThan resultTypeUser SELECT id, name, age, email, create_time FROM user WHERE age gt; #{minAge} /selectJava 接口里只需要定义public interface UserMapper { ListUser findUserByAgeGreaterThan(Param(minAge) Integer minAge); }看到区别了吗SQL 还是你自己写的但 MyBatis 帮你把#{minAge}解析成 PreparedStatement 的?占位符再帮你把 ResultSet 的结果集自动反射成 User 对象。也就是说MyBatis 是半自动化的 ORM——它管映射不管 SQL 生成。这种设计的优点在于SQL 是开发者自己写的所以复杂查询能力完全不受限。你可以写任何数据库支持的 SQL比如多表 JOIN、窗口函数、UNION、子查询全部可控。4.3 JPA倒过来从对象模型反推数据操作JPAJava Persistence API是 Java EE 规范中的持久化标准Hibernate 是其最著名的实现。它的思路是用注解描述映射关系EntityManager 或 Spring Data JPA 提供的 Repository 接口帮你完成绝大部分 CRUD。一个 JPA 实体的定义Entity Table(name user) public class User { Id GeneratedValue(strategy GenerationType.IDENTITY) private Long id; Column(name name, nullable false) private String name; Column(name age) private Integer age; // getter / setter ... }之后你就可以用继承自 JpaRepository 的接口直接玩出各种查询public interface UserRepository extends JpaRepositoryUser, Long { ListUser findByAgeGreaterThan(int minAge); ListUser findByNameContaining(String keyword); }方法名即查询——findByAgeGreaterThan框架根据方法名解析查询条件自动生成 SQL 并在内部封装成对象。4.4 两者适用场景的真实对比我在实际项目选型时的判断标准通常是这样一张表对比维度MyBatisJPA / HibernateSQL 控制力完全可控手写 SQL简单查询自动生成复杂查询可写 JPQL 或原生 SQL复杂查询开发效率需要手动写 XML 或注解 SQL门槛中等简单查询极快复杂查询的自动生成 SQL 不可控学习曲线理解 JDBC 后很快上手需要理解实体状态、级联、缓存等抽象概念懒加载与缓存需手工配置容易踩坑框架内建但坑更多数据库方言切换需自己控制 SQL 兼容性Hibernate 方言机制完善适合团队业务复杂、偏好 SQL 可控、有很多 DBA 或 SQL 能力强的人标准 CRUD 多、团队建模能力强、追求开发效率我的经验是业务强规则、报表型系统和大量复杂联表查询的情况MyBatis 会让你少掉很多头发领域模型完善、简单 CRUD 为主的后台管理系统JPA 能把开发效率拉满。5. 从 JDBC 到 MyBatis一个简单的增删改查进化实录5.1 纯 JDBC 实现插入用户数据再拿热搜里的JDBC 插入用户数据来演示。纯 JDBC 版本public void insertUser(User user) throws SQLException { String sql INSERT INTO user(name, age, email, create_time) VALUES(?, ?, ?, ?); try (Connection conn dataSource.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, user.getName()); ps.setInt(2, user.getAge()); ps.setString(3, user.getEmail()); ps.setTimestamp(4, Timestamp.valueOf(user.getCreateTime())); ps.executeUpdate(); } }注意我用到了 try-with-resources这已经是小优化了。但问题还在SQL 和参数绑定是硬编码的字段一变Java 代码要改而且这只是一个单独的 DAO 方法到了更新、删除、查询每一段都是类似的模板。5.2 MyBatis 完成同一件事Mapper XML 配置insert idinsertUser parameterTypeUser useGeneratedKeystrue keyPropertyid INSERT INTO user(name, age, email, create_time) VALUES(#{name}, #{age}, #{email}, #{createTime}) /insertJava 层调用只需要userMapper.insertUser(user);MyBatis 会读取 #{name}、#{age} 等从 User 对象里反射取属性值绑定到 PreparedStatement。useGeneratedKeystrue 还能帮你在插入后把自增主键回填到 user.id 上——这在纯 JDBC 里要用 KeyHolder 或者 getGeneratedKeys 去折腾。5.3 JPA 完成同一件事JPA 的方式还要更激进一些连 SQL 都不用写了Autowired private UserRepository userRepository; public void saveUser(User user) { userRepository.save(user); }三个方案对比下来差距非常明显。JDBC 是让你手动砌砖MyBatis 是给你一个半自动砌砖机JPA 是直接给你一个 3D 打印房屋的机器。但后面两者说到底都是在 JDBC 上面做封装——它们的底层仍然是 JDBC 在做 Connection 获取、SQL 执行、事务管理。理解了这一层你对 ORM 的认知就不再是浮在半空的概念而是建立在底层原理上的牢固结构。6. MyBatis 的工作流程详解从 XML 解析到 Mapper 代理6.1 SqlSessionFactory 的构建过程MyBatis 启动时的第一件事是构建 SqlSessionFactory。就这一件事里面隐藏了非常多的关键步骤也是面试题里特别爱问的mybatis 中 xmlconfigbuilser 的工作流程图。我把它拆成六个阶段XML 解析MyBatis 通过 XMLConfigBuilder 解析 mybatis-config.xml 主配置文件。这个文件里定义了数据源、事务工厂、类型别名、Mapper 映射文件位置等核心信息。Configuration 对象组装解析出的各种配置最终会落进一个 Configuration 对象。它是全 MyBatis 的配置中枢MappedStatement 的注册表、ResultMap 注册表、TypeHandler 注册表、SQL 缓存注册表全部挂在它身上。加载 Mapper XML 文件XMLMapperBuilder 解析每个 Mapper 文件把select、insert、update、delete标签解析为 MappedStatement——每一个 MappedStatement 都对应一条可执行的 SQL 语句节点。类型注册将 Java 类型和 JDBC 类型进行登记这个过程依赖 TypeHandler 机制。比如 Java 的 LocalDateTime 和 JDBC 的 TIMESTAMP 之间的变换需要一个对应的 TypeHandler。构建 SqlSessionFactory一切就绪后通过 SqlSessionFactoryBuilder 构建出 SqlSessionFactory 对象。构建 SqlSession每次执行数据库操作通过 SqlSessionFactory 打开一个 SqlSession。它就是执行 SQL 的会话门面应用通过它获得 Mapper 代理。6.2 Mapper 接口为什么能自动生成实现我们之前定义 UserMapper 接口并没有写实现类为什么 MyBatis 能自动调用关键在于 MyBatis 用 JDK 动态代理为每个 Mapper 接口生成代理对象。当你调用userMapper.findByAgeGreaterThan(20)时实际调用被代理对象拦截MapperMethodInvocation 根据接口方法名定位到对应的 MappedStatement然后走 SQL 解析、参数绑定、执行、结果映射这条链路。这套设计让 MyBatis 在用起来像纯接口调用的同时还保留了 SQL 完全掌控的优点。跟 JPA 那种方法名自动生成 SQL是两种完全不同的实现机制。6.3 结果映射的幕后ResultMap 和 TypeHandler日常开发中最常见的 MyBatis 功能就是 SELECT 后返回 POJO。但表字段是下划线命名如 create_timeJava 属性是驼峰命名如 createTime两边如何匹配两种方案配置mapUnderscoreToCamelCasetrueMyBatis 自动把create_time映射为属性名createTime。编写显式 ResultMap在 map 节点上手写每一列和属性的对应关系。除此之外TypeHandler 负责数据类型转换。默认情况下Integer、String、Long 这些简单类型都有内建 TypeHandler 自动匹配。遇到枚举、Json 字段、自定义类型的列就需要自己实现 TypeHandler 并把 XML 里的 jdbcType 和 javaType 关联起来。热搜里出现的mybatis 中 typehandler 的工作流程图考察的正是这个自定义扩展机制。6.4 缓存机制一级缓存与二级缓存先抛结论MyBatis 默认开启一级缓存作用域是 SqlSession 级别二级缓存默认关闭作用域是 namespaceMapper级别。一级缓存的意思是说在同一个 SqlSession 里如果执行了完全相同的 SQL包括参数相同第二次查询直接返回缓存里的对象不再访问数据库。听起来很美好但有一个经典大坑在同一个 SqlSession 里你 insert/update/delete 了数据一级缓存会被清空但如果你做了跨 SqlSession 的并发更新其他会话的缓存是不失效的——它不知道你改了数据查到的可能还是旧值。我实际遇到过的问题是Spring 管理的 SqlSession 每次 Mapper 方法执行完就关闭了一级缓存几乎起不到效果。这个恰恰是很多新手根本感知不到的细节——一级缓存有印象但它其实很短命。二级缓存开启了之后缓存粒度提升到 Mapper 级别跨 SqlSession 共享但并发读写、数据一致性、序列化要求都更复杂。比如你的实体类没有实现 Serializable二级缓存开启后会直接报错。我建议普通业务项目不要轻易开启二级缓存数据库连接池和业务层的本地缓存往往更可控。7. JPA 的进阶认知持久化上下文、脏检查和级联7.1 持久化上下文就是一个对象仓库JPA 和 MyBatis 的一个本质区别在于JPA 有持久化上下文Persistence Context的概念。它像一个仓库管理着当前事务范围内所有被加载过的实体对象。你用 userRepository.findById(1L) 找到 User 对象事务还没提交之前你直接更改这个对象的 name 属性然后什么方法都没调事务提交时 JPA 做脏检查Dirty Checking发现 name 变了自动生成 UPDATE 语句同步到数据库。这个机制叫隐式更新MyBatis 里不存在这种能力——MyBatis 的 update 必须显式调用。7.2 乐观锁、悲观锁和级联操作JPA 里的锁机制和级联也是面试的高频点。Version注解标注的字段用来实现乐观锁更新时 WHERE 条件带版本号比对防止并发覆盖。级联用cascade CascadeType.ALL表示对当前实体的操作会同步影响关联实体——保存用户时自动保存它的订单列表。但级联是一把双刃剑。我见过太多线上事故A 实体删了由于级联配置 DELETE把关联的 B、C、D 数据全删了。所以我在项目里有一个原则级联可以配置但生产环境的 DELETE 级联一定不配宁可写代码显式控制删除顺序。7.3 findBy 方法名解析规则Spring Data JPA 的方法名解析是一个很值得花时间掌握的机制。规则是这样的部分含义findBy固定前缀表示查询AgeGreaterThan属性 Age 大于Between→ 范围In→ 集合Containing→ 模糊OrderByAgeDesc按 Age 降序排序And / Or条件之间的逻辑关系比如findByNameAndAgeBetweenOrderByAgeDesc(String name, int start, int end)框架会自动解析出 SQLWHERE name ? AND age BETWEEN ? AND ? ORDER BY age DESC。这个机制的缺点是方法名一旦复杂起来会非常长可读性下降优点是简单查询的开发体验极好。我一般会把多条件动态查询改成Query注解或 Specification 的方式不会让方法名无限膨胀。8. 选型判断这个项目到底该用 MyBatis 还是 JPA8.1 影响决策的几个关键信号具体项目选型时我不会只看个人偏好而是看五个信号团队经验团队核心成员更熟悉 SQL 还是更熟悉面向对象建模熟悉 SQL 选 MyBatis喜欢基于实体建模的选 JPA。业务复杂度报表统计、财务核算、复杂的关联查询占比高不高高则选 MyBatis因为手写 SQL 效率最高标准 CRUD 占比高的管理系统选 JPA。数据库类型有没有可能未来换库有则 JPA 方言机制有优势公司长期锁定一种库则 MyBatis 无妨。性能要求高并发写、复杂查询压测是否严格MyBatis 更利于做精细 SQL 调优配合自定义 TypeHandler、动态 SQL 和 SqlSource 能精细控制执行计划。维护成本项目的 Mapper XML 文件多到爆炸还是 Entity 类多到爆炸哪个爆炸哪个就是维护痛点。8.2 混合使用也是一种常见方案我实际参与的项目里有不少是MyBatis 作为基础持久层 JPA 或者 Spring Data JPA 作为部分简单读写的混合形态或者反过来JPA 管实体的常规 CRUD MyBatis 管复杂 SQL。你可以在 Spring Boot 项目中让 MyBatis 和 JPA 共存配置好各自的 SqlSessionFactory 和 EntityManagerFactory数据库同一个库各自操作各自的表。或者用 MyBatis-Plus 这种增强框架它在 MyBatis 基础上提供了类似 JPA 的 BaseMapper 通用 CRUD。这个方案好不好因人而异但在我看过的中型项目里混合方案确实能兼顾两边优势。不过术语上要非常小心如果混用就必须明确哪些表归谁管绝不能同一张表同时被两个框架操作不然缓存和事务交织出问题的时候排查成本极其高。8.3 不要被必选 XX 框架的舆论裹挟现在的技术讨论特别喜欢站队。MyBatis 党说 JPA 性能烂JPA 党说 MyBatis 落伍。作为当了多年开发、踩过大量坑的人我的态度就一句话框架是手段业务是目的。如果一个团队把 JDBC 的 Connection、PreparedStatement 这些底层机制吃透了那么不管用 MyBatis 还是 JPA很多坑都能在意识层面提前避开——这比学会某个框架的 API 要重要一百倍。9. 学习 ORM 的实操路径和常见陷阱9.1 给新手的四个循序渐进阶段很多伙伴问我ORM 到底怎么学才扎实。我给的建议是别急着上框架按这个顺序来先用 JDBC 写十个增删改查重点感受 Connection 管理、PreparedStatement 参数绑定、ResultSet 结果遍历。不是让你以后用 JDBC 开发而是让你知道框架替你干了什么。然后学 MyBatis 的 XML 映射和 Mapper 接口把之前写过的那十个 JDBC 方法全部用 MyBatis 重写一遍。每写一个心里对比一下 JDBC 的样板代码少了多少。再用 Spring Data JPA 走一遍同样的 CRUD体会方法名即查询和事务上下文的便利然后写三个复杂查询感受 JPQL 和原生 SQL 的边界在哪。最后反过来看源码阅读 MyBatis 的 SqlSessionFactoryBuilder 和 Configuration 创建流程阅读 Spring Data JPA 的 Repository 代理创建流程。这时候你会发现两者很多核心思想殊途同归。9.2 常见问题的排查思路把热搜里几个常见场景串一下给点实际排查经验1. MyBatis 打印 SQL 不生效常见原因有三mybatis.configuration.log-impl没有设置成 StdOutImpl或者配置了但是没有加org.apache.ibatis包日志级别为 DEBUG还有一种可能是你在 XML 里用了settings标签但你设置的 logImpl 写错了。要检查 SQL 打印直接看 Spring Boot 的配置mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl2. JPA 的 findOne 方法在 Spring Data JPA 2.x 中没了这是新版本改动导致的。老项目用 findOne(id)新版本是findById(id).orElse(null)或自己控制 Optional。热搜里搜jpa findone 用法多半就是踩了这个坑。3. 用 MyBatis 查 Oracle 的时间字段映射异常Oracle 的 DATE 类型在 JDBC 驱动中返回java.sql.Timestamp还是java.sql.Date经常因为驱动版本和 MyBatis 的 TypeHandler 不匹配产生偏差。常见解决方案是自定义一个 TypeHandler 处理 Oracle 的DATE和TIMESTAMP类型然后在 XML 里显式指定。9.3 事务的理解是 ORM 学习的分水岭我带了那么多人发现一个规律很多人 CRUD 写得很溜但涉及事务就懵。JDBC 的默认行为是自动提交MyBatis 的 SqlSession 默认不自动提交Spring 里的Transactional又有一套自己的事务传播机制。这三者叠加很容易让初学者出现为什么我的数据没写进库或者为什么抛异常了数据还是进去了的困惑。这里我提一个必须记住的结论无论框架怎么封装最终的事务边界都落实在 Connection 的 commit 和 rollback 上。MyBatis 的 SqlSession 操作底层用的是同一个 ConnectionSpring 管理的事务也是一条 Connection 上做 setAutoCommit(false)、commit、rollback。你只要把这个底层逻辑想清楚框架层给你包装的花样再多你也知道它在干嘛。10. 写在最后ORM 不是银弹但它是现代社会分工的必然我在实际中使用 ORM 写业务代码已经很多年了从最初的纯 JDBC到 MyBatis再到 Spring Data JPA 和 MyBatis-Plus 混合使用经历过大大小小的项目。如果让我一句话总结多年的体会那一定是ORM 框架解决的是程序员和数据库之间翻译成本的问题它不解决程序员不懂数据库的问题。最后再分享一个小技巧无论你用哪个框架一定要保留查看底层 SQL 的习惯。MyBatis 就配置 StdOutImplJPA 就把spring.jpa.show-sqltrue打开生产环境注意关掉每次执行都盯着打印出来的 SQL 和参数看看一段时间你对 ORM 的理解深度会远超那些只会调用 API 的开发。数据库是你系统里最后的真相而 ORM 是它和代码之间的桥——桥造得好不好决定了这段路你走得顺不顺。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑