发布时间:2026/7/17 20:49:53
一篇文章掌握一个技能:MyBatis 从入门到精通与高频面试题精讲 我记得刚学 Java 那会儿带我的老大哥让我连个数据库查个用户列表。我吭哧吭哧写了半天 JDBC——Class.forName、DriverManager.getConnection、Connection.prepareStatement、ResultSet遍历、finally里关连接……代码写了 40 行功能只是查了 3 条记录。老大哥看完代码面无表情地说了一句小伙子没用过 MyBatis 吗那一天我的 Java 世界观被刷新了。现在 MyBatis 已经成为 Java 开发中司空见惯、不可或缺的持久层框架。但有些人即便是 MyBatis 使用起来熟练到起飞也从未系统地学习过它更不能说清楚它到底是怎么工作的。这篇文章将带你从原理到实战从避坑到面试一步一步拿下 MyBatis。后续的文章我们再拿下MyBatis-Plus。一、基础入门从手写 JDBC到优雅操作数据库1.1 先看看我们为什么需要 MyBatis很多 Java 新人可能没经历过纯 JDBC 时代我们先来感受一下痛苦。下面这段代码功能仅仅是根据 ID 查一个用户public User getUserById(int id) { Connection conn null; PreparedStatement ps null; ResultSet rs null; User user null; try { Class.forName(com.mysql.cj.jdbc.Driver); conn DriverManager.getConnection( jdbc:mysql://localhost:3306/my_db?useSSLfalseserverTimezoneUTC, root, password); ps conn.prepareStatement(SELECT id, name, age, email FROM user WHERE id ?); ps.setInt(1, id); rs ps.executeQuery(); if (rs.next()) { user new User(); user.setId(rs.getInt(id)); user.setName(rs.getString(name)); user.setAge(rs.getInt(age)); user.setEmail(rs.getString(email)); } } catch (Exception e) { e.printStackTrace(); } finally { if (rs ! null) try { rs.close(); } catch (Exception e) {} if (ps ! null) try { ps.close(); } catch (Exception e) {} if (conn ! null) try { conn.close(); } catch (Exception e) {} } return user; }这段代码有四个致命问题痛点具体表现带来的后果样板代码多每次操作都要手动管理连接、Statement、ResultSet40 行代码完成一个查询80% 是重复代码硬编码 SQLSQL 写在 Java 字符串里拼接参数时极易出错改一行 SQL 要重新编译部署结果集手动映射字段名和 Java 属性要一个一个手动 set表字段一变几十处代码要改连接管理复杂手动开关连接忘记关就连接泄漏线上数据库连接池被耗尽MyBatis 正是为了解决这些痛点而生的。它的核心思想很简单把 SQL 和 Java 代码解耦自动完成参数绑定和结果映射。1.2 一句话讲清楚MyBatis 到底是什么MyBatis是一个半自动的持久层框架它封装了 JDBC 的复杂操作让你只需要关注 SQL 本身其他的事情参数设置、结果集映射、连接管理全部帮你搞定。说它半自动是因为它不像 Hibernate 那样完全自动生成 SQL——SQL 仍然由你控制但执行 SQL 的脏活累活 MyBatis 帮你干了。简单来说MyBatis 是你和 JDBC 之间的翻译官——你告诉它要查什么它帮你把结果翻译成 Java 对象交给你。1.3 3 分钟跑通你的第一个 MyBatis 查询光说不练假把式先跟着我跑通一个最简示例。我们用 Spring Boot 3.x MyBatis 来搭建。第一步引入依赖!-- pom.xml -- dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependency第二步配置数据源# application.yml spring: datasource: url: jdbc:mysql://localhost:3306/my_db?useSSLfalseserverTimezoneAsia/Shanghai username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver # MyBatis 配置可选有默认值 mybatis: mapper-locations: classpath:mapper/*.xml # XML 映射文件位置 type-aliases-package: com.example.entity # 实体类别名包 configuration: map-underscore-to-camel-case: true # 下划线转驼峰user_name → userName log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 控制台打印 SQL第三步创建实体类和 Mapper 接口// User.java — 实体类 Data public class User { private Long id; private String name; private Integer age; private String email; }// UserMapper.java — Mapper 接口这就是你以后写 SQL 的地方 Mapper public interface UserMapper { User selectById(Param(id) Long id); }第四步写 XML 映射文件!-- resources/mapper/UserMapper.xml -- ?xml version1.0 encodingUTF-8? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.mapper.UserMapper select idselectById resultTypecom.example.entity.User SELECT id, name, age, email FROM user WHERE id #{id} /select /mapper第五步测试一下SpringBootTest class UserMapperTest { Autowired private UserMapper userMapper; Test void testSelectById() { User user userMapper.selectById(1L); System.out.println(user); // User(id1, name张三, age25, emailzhangsantest.com) } }只用 5 行 Java 代码不算注解就完成了之前 40 行 JDBC 才能干的事。这就是 MyBatis 的魅力。新手常见疑惑UserMapper明明只是一个接口没有实现类啊为什么能直接调用——因为 MyBatis 在启动时用动态代理帮我们生成了接口的实现类。下一节我们会深入聊这个。1.4 MyBatis 核心组件速览在深入学习之前先认识一下 MyBatis 的核心成员组件职责生命周期一句话描述SqlSessionFactory创建 SqlSession 的工厂应用级全局一个整个应用只有一个它负责生产 SqlSessionSqlSession执行 SQL、获取 Mapper、管理事务请求级用完即关每次数据库操作都要通过它用完记得关Executor真正执行 SQL 的组件SqlSession 内部SQL 的实际执行者还管缓存StatementHandler向 JDBC 发送 SQL每次执行时创建负责参数绑定和结果集处理Mapper 接口你定义的数据库操作接口应用级你写 SQL 声明的地方这些组件之间的协作关系可以用一句话概括SqlSessionFactory 生产 SqlSessionSqlSession 通过 Executor 执行 SQLExecutor 委托 StatementHandler 和 JDBC 交互。好了基础知识就到这里。接下来我们进入实战环节——你会接触到 MyBatis 最常用的那些功能。二、核心进阶MyBatis 的十八般武艺2.1 XML 映射 vs 注解映射你该怎么选MyBatis 支持两种方式定义 SQLXML 文件和注解。先看对比注解方式——SQL 写在 Java 代码里Mapper public interface UserMapper { Select(SELECT id, name, age, email FROM user WHERE id #{id}) User selectById(Param(id) Long id); Insert(INSERT INTO user(name, age, email) VALUES(#{name}, #{age}, #{email})) Options(useGeneratedKeys true, keyProperty id) int insert(User user); }XML 方式——SQL 写在 XML 文件中mapper namespacecom.example.mapper.UserMapper select idselectById resultTypeUser SELECT id, name, age, email FROM user WHERE id #{id} /select insert idinsert useGeneratedKeystrue keyPropertyid INSERT INTO user(name, age, email) VALUES(#{name}, #{age}, #{email}) /insert /mapper那么问题来了——到底用哪个这里给出一个实用的决策指南用什么方式写 SQL │ ├── 简单 CRUD单表增删改查 │ └── ✅ 用注解简洁方便 │ ├── 复杂查询多表 JOIN、嵌套子查询 │ └── ✅ 用 XMLSQL 和 Java 分离便于维护 │ ├── 动态 SQL条件搜索、批量操作 │ └── ✅ 用 XML注解里写动态 SQL 是噩梦 │ └── 团队规范 └── ✅ 统一用 XML保持一致性推荐我的建议实际项目中绝大多数团队选择统一用 XML。因为需求一变简单 SQL 就可能变成复杂 SQL。与其后期迁移不如一开始就用 XML。2.2 动态 SQLMyBatis 最香的功能如果说 MyBatis 有一个功能让我觉得用了就回不去那一定是动态 SQL。来看一个经典场景——用户搜索// 请求参数name 和 age 都是可选的 UserSearchReq req new UserSearchReq(); req.setName(张三); // 可能为空 req.setAge(25); // 可能为 null如果不用动态 SQL你就得在 Java 代码里手动拼接 SQL 字符串那简直是一场灾难。而 MyBatis 的动态 SQL 让这件事变得优雅select idsearchUsers resultTypeUser SELECT id, name, age, email FROM user where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if testage ! null AND age #{age} /if /where ORDER BY id DESC /selectwhere标签很聪明——当内部条件都不满足时它什么都不输出当有满足的条件时它会自动去掉第一个AND。这就避免了WHERE AND name ?这种 SQL 语法错误。常用动态 SQL 标签速查标签作用典型场景if条件判断如果传了参数就拼条件where自动处理 WHERE 子句的 AND/OR多条件搜索set自动处理 UPDATE SET 的逗号动态更新foreach遍历集合IN 查询、批量插入choose/when/otherwise多选一类似 switch只按第一个非空条件查trim更灵活的字符串裁剪自定义的前缀/后缀处理sql/includeSQL 片段复用多表共用的查询列来看几个实战例子批量插入foreachinsert idbatchInsert INSERT INTO user(name, age, email) VALUES foreach collectionlist itemuser separator, (#{user.name}, #{user.age}, #{user.email}) /foreach /insert动态更新setupdate idupdateUser UPDATE user set if testname ! nullname #{name},/if if testage ! nullage #{age},/if if testemail ! nullemail #{email},/if /set WHERE id #{id} /updateset会自动去掉最后一个多余的逗号——很贴心吧SQL 片段复用sql/includesql iduserColumns id, name, age, email, create_time /sql select idselectById resultTypeUser SELECT include refiduserColumns/ FROM user WHERE id #{id} /select select idselectAll resultTypeUser SELECT include refiduserColumns/ FROM user ORDER BY id /select字段变了只需要改一处这就是复用的价值。2.3 #{} vs ${}90% 的人都踩过的坑这是面试最高频的 MyBatis 问题之一也是生产事故的高发区。先看结论符号处理方式效果用法#{}预编译占位符?WHERE id ?传参数值${}直接字符串拼接WHERE id 1传表名、列名、ORDER BY安全对比——SQL 注入实验// 用户输入name 张三 OR 11使用#{}安全select idselectByName resultTypeUser SELECT * FROM user WHERE name #{name} /select !-- 实际执行SELECT * FROM user WHERE name ? -- !-- 参数张三 OR 11 → 被当作一个完整的字符串值 --使用${}危险select idselectByName resultTypeUser SELECT * FROM user WHERE name ${name} /select !-- 实际执行SELECT * FROM user WHERE name 张三 OR 11 -- !-- 条件永远为真所有用户数据被暴露--${}的正确使用场景虽然${}有安全风险但以下场景必须用它!-- 1. 动态表名 -- select idselectByTable resultTypemap SELECT * FROM ${tableName} WHERE id #{id} /select !-- 2. 动态排序字段 -- select idselectAll resultTypeUser SELECT * FROM user ORDER BY ${orderColumn} ${orderDirection} /select !-- 3. 动态分组 -- select idselectByGroup resultTypemap SELECT ${groupColumn}, COUNT(*) as cnt FROM user GROUP BY ${groupColumn} /select安全守则使用${}时参数值必须在 Java 代码中做白名单校验绝不能让用户输入直接传入。// 白名单校验示例 private static final SetString ALLOWED_COLUMNS Set.of(id, name, age, create_time); private static final SetString ALLOWED_DIRECTIONS Set.of(ASC, DESC); public ListUser selectByOrder(String orderColumn, String orderDirection) { if (orderColumn null || !ALLOWED_COLUMNS.contains(orderColumn)) { throw new IllegalArgumentException(非法的排序字段: orderColumn); } if (orderDirection null || !ALLOWED_DIRECTIONS.contains(orderDirection.toUpperCase())) { throw new IllegalArgumentException(非法的排序方向: orderDirection); } return mapper.selectAll(orderColumn, orderDirection.toUpperCase()); }2.4 ResultMap当字段名和属性名不一致时MyBatis 默认按照字段名和属性名同名匹配来映射。但现实中有三种情况会导致不匹配数据库字段用下划线user_nameJava 属性用驼峰userName关联查询的嵌套对象字段名和属性名完全不同情况一最简单——开启驼峰转换即可mybatis: configuration: map-underscore-to-camel-case: true # user_name → userName 自动映射情况二——关联对象这才是 ResultMap 的真正威力所在。假设用户表有收货地址Data public class User { private Long id; private String name; // 关联对象一个用户有多个地址 private ListAddress addresses; } Data public class Address { private Long id; private Long userId; private String province; private String city; private String detail; }用 ResultMap 处理一对多resultMap idUserWithAddresses typeUser id propertyid columnid/ result propertyname columnname/ !-- collection一对多关联 -- collection propertyaddresses ofTypeAddress columnid selectselectAddressesByUserId/ /resultMap select idselectUserWithAddresses resultMapUserWithAddresses SELECT id, name FROM user WHERE id #{id} /select select idselectAddressesByUserId resultTypeAddress SELECT id, user_id, province, city, detail FROM address WHERE user_id #{userId} /select一对一关联用associationresultMap idUserWithIdCard typeUser id propertyid columnid/ result propertyname columnname/ !-- association一对一关联 -- association propertyidCard javaTypeIdCard columnid selectselectIdCardByUserId/ /resultMap性能提示collection和association的分步查询默认会触发N1 查询问题。查 1 个用户 → 再查 N 个子记录。如果数据量大建议改用 JOIN 连表查询。!-- 用 JOIN 一次性查出来避免 N1 -- resultMap idUserWithAddressesJoin typeUser id propertyid columnid/ result propertyname columnname/ collection propertyaddresses ofTypeAddress resultMapaddressResultMap/ /resultMap select idselectUserWithAddressesJoin resultMapUserWithAddressesJoin SELECT u.id, u.name, a.id as a_id, a.province, a.city, a.detail FROM user u LEFT JOIN address a ON u.id a.user_id WHERE u.id #{id} /select2.5 缓存机制帮你的查询提提速MyBatis 内置了两级缓存。不过先说一个反常识的建议生产环境通常不推荐开启 MyBatis 的二级缓存。缓存级别作用范围默认状态一句话描述一级缓存SqlSession 级别默认开启同一会话中相同的查询只执行一次 SQL二级缓存Mapper 级别跨 SqlSession默认关闭多个会话共享缓存但要小心脏数据一级缓存演示// 同一个 SqlSession 中 User user1 userMapper.selectById(1L); // 查数据库 User user2 userMapper.selectById(1L); // 从缓存取不发 SQL System.out.println(user1 user2); // true同一个对象引用一级缓存在以下情况会失效执行了增删改操作、手动清空缓存、不同的 SqlSession。为什么不推荐二级缓存!-- 开启二级缓存的方式很简单 -- cache/但问题是二级缓存是Mapper 级别的如果其他 Mapper 修改了同一张表的数据缓存不会自动失效。这就导致你读到的可能是脏数据。最佳实践将缓存控制权交给更专业的组件——Redis。MyBatis 的缓存只适合极少更新的字典表、配置表等场景。需要分布式缓存时可以集成mybatis-redis等扩展。三、深入理解钻进 MyBatis 的发动机舱3.1 一条 SQL 的执行全链路面试时经常被问到MyBatis 是怎么工作的下面这张流程图就是标准答案┌─────────────────────────────────────────────────────┐ │ 1. 调用 Mapper 接口方法 │ │ userMapper.selectById(1L) │ └───────────────────────┬─────────────────────────────┘ │ ┌───────────────────────▼─────────────────────────────┐ │ 2. MapperProxy动态代理拦截调用 │ │ 根据接口全限定名 方法名找到对应的 SQL │ │ → com.example.mapper.UserMapper.selectById │ └───────────────────────┬─────────────────────────────┘ │ ┌───────────────────────▼─────────────────────────────┐ │ 3. SqlSession 将请求交给 Executor │ │ Executor 判断是否命中缓存 │ └───────────────────────┬─────────────────────────────┘ │ ┌───────────────────────▼─────────────────────────────┐ │ 4. StatementHandler 处理参数和 SQL │ │ ParameterHandler将 Java 参数设置到 ? │ │ SQL 解析完成生成可执行的 JDBC Statement │ └───────────────────────┬─────────────────────────────┘ │ ┌───────────────────────▼─────────────────────────────┐ │ 5. TypeHandler类型转换 │ │ Java 类型 ↔ JDBC 类型 ↔ 数据库类型 │ └───────────────────────┬─────────────────────────────┘ │ ┌───────────────────────▼─────────────────────────────┐ │ 6. ResultSetHandler结果映射 │ │ 将 JDBC ResultSet → Java 对象 │ └───────────────────────┬─────────────────────────────┘ │ ┌───────────────────────▼─────────────────────────────┐ │ 7. 返回结果给调用者 │ └─────────────────────────────────────────────────────┘对应到源码层面核心流程大致如下简化版SqlSession.selectOne() → Executor.query() → 创建 CacheKey判断缓存 → BaseExecutor.query() / CachingExecutor.query() → SimpleExecutor.doQuery() → StatementHandler.prepare() → 获取 Connection预编译 SQL → StatementHandler.parameterize() → ParameterHandler 设置参数 → StatementHandler.query() → 执行 SQL得到 ResultSet → ResultSetHandler.handleResultSets() → 映射为 Java 对象面试回答模板MyBatis 通过动态代理拦截 Mapper 接口方法调用根据方法签名找到对应的 SQL 语句再通过 Executor → StatementHandler → TypeHandler 的链路完成参数绑定、SQL 执行和结果集映射。3.2 动态代理为什么 Mapper 接口没有实现类也能工作这是理解 MyBatis 的关键。看看 MyBatis 是怎么偷梁换柱的// MyBatis 内部大致做了这样一件事极度简化版 public class MapperProxyT implements InvocationHandler { Override public Object invoke(Object proxy, Method method, Object[] args) { // 1. 获取方法全限定名com.example.mapper.UserMapper.selectById String statementId method.getDeclaringClass().getName() . method.getName(); // 2. 判断 SQL 类型SELECT/INSERT/UPDATE/DELETE // 3. 调用 SqlSession 的对应方法selectOne / selectList / insert / update / delete // 4. 处理参数和返回值 if (returnsList) { return sqlSession.selectList(statementId, args[0]); } else { return sqlSession.selectOne(statementId, args[0]); } } } // 你拿到的 userMapper 实际上是这个 UserMapper userMapper (UserMapper) Proxy.newProxyInstance( UserMapper.class.getClassLoader(), new Class[]{UserMapper.class}, new MapperProxy(sqlSession) );这就是为什么我们只需要写接口——MyBatis 在运行时帮你生成了代理对象这个代理对象会拦截你的每一次调用然后去 XML 或注解中找到对应的 SQL 来执行。3.3 插件机制MyBatis 的中间件系统MyBatis 的插件Interceptor可以在 SQL 执行过程中做手脚原理是责任链模式 动态代理。最著名的插件莫过于PageHelper——分页插件。它的工作原理就是拦截Executor.query()在原始 SQL 外面包一层LIMIT// 你写的 SQL // SELECT * FROM user WHERE age 18 ORDER BY id // // PageHelper 改写成以 MySQL 为例 // SELECT * FROM user WHERE age 18 ORDER BY id LIMIT ?, ?自己实现一个简单的 SQL 日志插件Intercepts({ Signature(type StatementHandler.class, method prepare, args {Connection.class, Integer.class}) }) public class SqlLogInterceptor implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler handler (StatementHandler) invocation.getTarget(); MetaObject metaObject SystemMetaObject.forObject(handler); // 拿到原始 SQL有占位符 ? 的 String sql handler.getBoundSql().getSql(); System.out.println(执行的 SQL sql); // 拿到参数 Object parameterObject handler.getBoundSql().getParameterObject(); System.out.println(参数 parameterObject); // 计算执行时间 long start System.currentTimeMillis(); Object result invocation.proceed(); long cost System.currentTimeMillis() - start; System.out.println(耗时 cost ms); return result; } }MyBatis 允许拦截的四个关口拦截对象可拦截方法典型用途Executorupdate, query, commit, rollback分页、缓存、慢 SQL 监控StatementHandlerprepare, parameterize, batch, update, querySQL 改写、加密解密ParameterHandlersetParameters参数处理ResultSetHandlerhandleResultSets结果集脱敏3.4 MyBatis vs Hibernate vs JPA别再纠结了这个话题在团队技术选型时总能吵上半天。直接上对比维度MyBatisHibernate/JPASQL 控制权开发者手写 SQL框架自动生成HQL/JPQL学习曲线低会 SQL 就能上手高要学 HQL、Criteria、缓存策略复杂查询直接写 SQL灵活可控用 HQL/JPQL复杂场景费劲自动映射须配 ResultMap自动映射Entity 注解即关联数据库移植要改 SQL不同库语法不同理论上无缝切换实际也有坑性能优化直接优化 SQL要理解 Hibernate 的 SQL 生成逻辑适用场景复杂 SQL 多、报表系统、互联网项目CRUD 为主、快速开发、标准业务系统选型建议你是哪种团队 │ ├── 互联网公司SQL 需要精细优化关联查询多 │ └── MyBatis / MyBatis-Plus │ ├── 企业管理系统CRUD 为主需要快速开发 │ └── JPA QueryDSL │ └── 既要灵活又要效率 └── MyBatis-Plus保留了 MyBatis 的灵活性又提供了便捷的 CRUD四、实战与面试从踩坑到拿 Offer4.1 常见踩坑指南坑 1Mapper 接口方法重载Mapper public interface UserMapper { User selectById(Long id); // ❌ User selectById(Long id, String name); // ❌ }MyBatis 通过接口名.方法名定位 SQL方法重载会导致查找失败。不要重载 Mapper 方法。坑 2忘记处理 NULL 值select idselectByName resultTypeUser SELECT * FROM user WHERE name #{name} /select如果name传入nullSQL 会变成WHERE name null这永远查不到东西因为 SQL 中用IS NULL而非 NULL。正确做法select idselectByName resultTypeUser SELECT * FROM user where if testname ! null name #{name} /if /where /select坑 3大事务导致数据库连接耗尽// ❌ 反面教材 Transactional public void processLargeData() { ListUser users userMapper.selectAll(); // 假设 100 万条 for (User user : users) { // 一顿操作持有事务 30 分钟 userMapper.update(user); } }如果这个接口的 QPS 较高连接池很快就会被耗尽。正确做法是分批处理public void processLargeData() { int pageSize 1000; Long lastId 0L; while (true) { ListUser batch userMapper.selectByPage(lastId, pageSize); if (batch.isEmpty()) break; for (User user : batch) { // 每批在独立事务中执行 userService.updateUser(user); } lastId batch.get(batch.size() - 1).getId(); } }坑 4foreach 拼接 IN 查询时参数过多select idselectByIds resultTypeUser SELECT * FROM user WHERE id IN foreach collectionids itemid open( close) separator, #{id} /foreach /select如果ids有 5000 个元素生成的 SQL 会非常长甚至超出数据库限制。建议分批处理每批不超过 1000 个 ID。4.2 性能优化实战1. 只查需要的字段!-- ❌ 差查出所有字段 -- select idselectUserList resultTypeUser SELECT * FROM user /select !-- ✅ 好只查需要显示的字段 -- select idselectUserListForDisplay resultTypeUser SELECT id, name, age FROM user /select2. 合理使用批量操作!-- ✅ 批量插入 -- insert idbatchInsert INSERT INTO user(name, age) VALUES foreach collectionlist itemuser separator, (#{user.name}, #{user.age}) /foreach /insertMyBatis 默认的批量提交需要额外配置对接 JDBC 的rewriteBatchedStatementsspring: datasource: url: jdbc:mysql://localhost:3306/my_db?rewriteBatchedStatementstrue这个参数能把多条 INSERT 合并成一条性能提升非常明显。3. 避免 N1 查询N1 问题是指先查主表1 次再逐条查子表N 次总计 N1 次查询。前面讲 ResultMap 的collection和association时就提到了这个问题。解决方案是用 JOIN 一次查出所有数据。4.3 10 道高频面试题精讲Q1MyBatis 中 #{} 和 ${} 的区别是什么经典必问。#{} 是预编译占位符安全防注入${} 是字符串直接拼接有注入风险用于表名/列名等动态场景。Q2MyBatis 的一级缓存和二级缓存有什么区别一级缓存SqlSession 级别默认开启同一会话相同查询不重复查库。二级缓存Mapper 级别跨 SqlSession默认关闭需手动开启且要小心脏数据。Q3MyBatis 是如何工作的执行流程MapperProxy 动态代理拦截 → 找 SQL 语句 → Executor 执行 → StatementHandler 参数化 → JDBC 执行 → ResultSetHandler 映射结果。Q4MyBatis 的分页是如何实现的MyBatis 自身不分页需要借助插件如 PageHelper。插件拦截 Executor.query()改写 SQL 添加 LIMIT/OFFSET。Q5MyBatis 支持哪些执行器ExecutorType三种SIMPLE默认每次创建新 Statement、REUSE复用 Statement、BATCH批量执行。Q6当实体类字段名和数据库字段名不一致时怎么办三种方案① 开启mapUnderscoreToCamelCase下划线转驼峰② 用 ResultMap 显式映射③ SQL 中起别名SELECT user_name AS userName FROM user。Q7MyBatis 的延迟加载原理通过动态代理代理关联对象的 getter 方法。当你真正调用关联对象时才触发第二条 SQL。配置lazyLoadingEnabledtrue开启。Q8如何获取自动生成的主键insert useGeneratedKeystrue keyPropertyid或者selectKey标签。插入后主键值会回填到传入的对象中。Q9MyBatis 和 Hibernate 的区别及选型半自动 vs 全自动。MyBatis 手写 SQL、灵活可控Hibernate 自动生成 SQL、开发快。互联网项目多用 MyBatis企业管理系统多用 JPA。Q10如何打印 MyBatis 执行的 SQL三种方式①mybatis.configuration.log-implStdOutImpl② 调整日志级别logging.level.com.example.mapperDEBUG③ 自定义插件拦截。写在最后回顾一下我们从 JDBC 的痛苦出发认识了 MyBatis 的核心价值——把 SQL 和 Java 代码解耦让我们专注于 SQL 本身。我们学了 XML 和注解两种映射方式掌握了动态 SQL、ResultMap、参数绑定这些核心技能还钻进了源码层理解了动态代理和执行链路。记住一句话MyBatis 的灵魂是 SQL。框架帮你省掉的是样板代码但 SQL 质量和数据库设计永远是你自己的功底。

相关新闻

2026/7/17 20:49:53

sqlserver 链接 MYSQL数据库 (无废话 百分100%可用)

1.去mysql官网下载odbc的驱动 在sqlserver服务器安装,根据自己实际情况的版本下载(我是下载的这两个)2.配置odbc,选含Unicode的,其它的会导致数据库列字段是中文的情况下乱码3.sqlserver数据源这个位置,填odbc的MYSQL在sqlserver中…

2026/7/17 20:44:53

05-08-YooAsset源码-Unity缓存系统与内存管理

源码-资源缓存机制 篇章:05-源码深度-运行时核心模块 阅读时间:约 25 分钟 前置知识:了解引用计数机制 一、引言 本章将深入解析 YooAsset 资源缓存机制的源码实现。资源缓存是 YooAsset 资源管理的重要组成部分,通过缓存机制可以…

2026/7/17 20:44:53

Creo二次开发实战:MCADExTools安装配置与阶梯剖视图自动化

1. 项目概述:MCADExTools 是什么,为什么值得花时间装它MCADExTools 这个名字在 Creo 用户圈里不算陌生,但真正搞清楚它到底能干什么、值不值得为它折腾安装流程的人,其实不多。我第一次听说它,是在帮一家做精密齿轮箱的…

2026/7/17 22:00:01

DeepSeek API 实战:从接入到搭建你自己的 AI 助手

DeepSeek API 实战:从接入到搭建你自己的 AI 助手 最近做项目需要接入大模型 API,对比了一圈最终选了 DeepSeek。原因很简单:便宜、中文效果好、兼容 OpenAI 协议。 这篇文章记录我从零接入到搭建一个可用 AI 助手的全过程,踩过…

2026/7/17 22:00:01

一针见血!用JSLeakWatcher解决HarmonyOS内存泄漏难题

本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看: 一针见血!用JSLeakWatcher解决HarmonyOS内存泄漏难题-华为开发者话题 | 华为开发者联盟 一、为什么你的Harm…

2026/7/17 22:00:01

HarmonyOS应用性能监测服务APMS-崩溃问题定位分析指导

本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看: HarmonyOS应用性能监测服务APMS-崩溃问题定位分析指导-华为开发者话题 | 华为开发者联盟 版本刚上线,崩溃告…

2026/7/17 22:00:01

HarmonyOS应用性能监测服务APMS-故障分析:快速定位线上崩溃根因

本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看: HarmonyOS应用性能监测服务APMS-故障分析:快速定位线上崩溃根因-华为开发者话题 | 华为开发者联盟 线上崩溃…

2026/7/17 22:00:01

HarmonyOS应用性能监测服务APMS-故障指标:多维度量你的应用健康

本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看: HarmonyOS应用性能监测服务APMS-故障指标:多维度量你的应用健康-华为开发者话题 | 华为开发者联盟 你的应用…

2026/7/17 21:55:00

Ubuntu 22.04中Chrome中文字体显示异常的解决方案

1. 问题现象与根源分析最近在Ubuntu 22.04 LTS上使用Chrome浏览器时,发现中文字体显示异常混乱——有些页面显示为宋体,有些变成黑体,还有些直接变成无法辨认的方块字。这种情况在访问中文网站、查看PDF文档或使用在线文档工具时尤为明显。经…

2026/7/17 5:59:06

3步解锁音乐自由:ncmdumpGUI终极NCM文件解密转换指南

3步解锁音乐自由:ncmdumpGUI终极NCM文件解密转换指南 【免费下载链接】ncmdumpGUI C#版本网易云音乐ncm文件格式转换,Windows图形界面版本 项目地址: https://gitcode.com/gh_mirrors/nc/ncmdumpGUI 你是否曾在网易云音乐下载了心爱的歌曲&#…

2026/7/17 1:21:45

CANoe 19 SP3 配置 GB/T 27930-2023 A类系统:3步搭建BMS仿真测试环境

CANoe 19 SP3 配置 GB/T 27930-2023 A类系统:3步搭建BMS仿真测试环境随着新能源汽车行业的快速发展,充电通信协议的标准化和测试验证变得尤为重要。GB/T 27930-2023作为中国智能充电协议的最新版本,对充电机与电动汽车之间的通信提出了更严格…

2026/7/17 7:39:19

3步搞定RTL8852BE驱动:从零开始配置Wi-Fi 6网卡

3步搞定RTL8852BE驱动:从零开始配置Wi-Fi 6网卡 【免费下载链接】rtl8852be Realtek Linux WLAN Driver for RTL8852BE 项目地址: https://gitcode.com/gh_mirrors/rt/rtl8852be 还在为Linux系统无法识别RTL8852BE Wi-Fi 6网卡而烦恼吗?&#x1f…

2026/7/17 0:04:23

BiSheng JDK-build性能调优:构建速度提升30%的优化策略

BiSheng JDK-build性能调优:构建速度提升30%的优化策略 【免费下载链接】bishengjdk-build BiSheng JDK build and test scripts - common across all releases/versions 项目地址: https://gitcode.com/openeuler/bishengjdk-build 前往项目官网免费下载&am…

2026/7/17 14:59:44

3个高效策略:快速掌握Axure中文界面配置

3个高效策略:快速掌握Axure中文界面配置 【免费下载链接】axure-cn Chinese language file for Axure RP. Axure RP 简体中文语言包。支持 Axure 11、10、9。不定期更新。 项目地址: https://gitcode.com/gh_mirrors/ax/axure-cn 还在为Axure RP的英文界面感…