ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Mybatis基础操作实战:从初始化原理到缓存机制避坑指南

Mybatis基础操作实战:从初始化原理到缓存机制避坑指南 如果你正在学 JavaWeb大概率已经绕不开 Mybatis 这个名字了。很多教程把它当作“配置完就能跑”的黑盒但真正上手写项目时你会发现一个残酷的事实增删改查谁都会抄可一旦遇到 SQL 报错、参数绑定失败、缓存数据对不上你没搞清楚底层那点机制就只能靠 CtrlC / CtrlV 和瞎猜来写代码。这篇东西就是把我从入门到进阶阶段踩过的坑、看过的源码、整理过的笔记浓缩成一份可以照着做的实战总结核心讲 Mybatis 的基础操作也会把 XML 配置初始化原理、缓存机制、动态 SQL 失效这些高频问题一并说透。内容适合两类人刚学完 JavaWeb 基础、准备把 Mybatis 真正用进项目里的新手以及写过一些 CRUD 但从来没认真排查过“为什么我的查询结果不对”的半进阶同学。我会尽量用大白话讲原理再补上可以直接抄进项目的配置和代码。我当年第一次用 Mybatis 的时候整整卡了一个下午原因不过是 Mapper 接口和 XML 命名空间没对齐控制台给我甩了一句 “Invalid bound statement (not found)”。当时完全不知道这句话在说什么后来把 XML 配置初始化流程从头捋了一遍才恍然大悟。所以这篇我决定不按教科书顺序来而是先讲清楚 Mybatis 在项目里是怎么工作的再带你手写整套基础操作最后把常见问题做成速查表。看完你至少能实现一个完整的注册功能并且能跟面试官把 Mybatis 的初始化原理、缓存机制聊得明明白白。1. 先从项目视角理解JavaWeb 项目的持久层到底在做什么1.1 数据访问这件事为什么不能让 JDBC 裸奔JavaWeb 项目做到后期绕不开一件事把内存里的对象数据保存到数据库以及把数据库的表数据读回到内存对象里。最原始的 JDBC 写法核心代码无非就是DriverManager.getConnection()、PreparedStatement、ResultSet这三板斧但问题在于“样板代码”太多了。举个例子你只是想查一个用户表里 id 为 1 的记录JDBC 至少要写 8 到 10 行准备代码加载驱动、拿连接、写 SQL、填参数、执行查询、遍历结果集、手动 set 到 User 对象、关闭连接。这还只是一条最简单的查询。真实项目里一张表二三十个字段一条 insert 要写二十多个ps.setString()体验极其痛苦而且手写结果集映射特别容易漏字段、写错下标编译器还不会报错只有跑到线上数据量大的时候才在某个角落悄悄返回 null。我把这种状态总结为“三个重复”重复的获取连接逻辑、重复的参数装配代码、重复的结果映射代码。Mybatis 的价值就是把这三种重复全部从业务代码里剥离出去让 SQL 本身成为项目里的一等公民。1.2 Mybatis 解决的不是“不要写 SQL”而是“把 SQL 管理好”很多刚入门的同学有一个误区觉得用了 ORM 框架就不用写 SQL 了。这其实是 JPA / Hibernate 那套的思维。Mybatis 从来不是“帮你生成 SQL”的框架它是“帮你把 SQL 组织起来、把参数和结果映射好”的框架。它做的事情可以拆成四块管理数据库连接的生命周期不再让业务代码里到处getConnection()。通过 Mapper 接口与 XML / 注解绑定把 SQL 写在一个可控、可 review 的地方。参数处理上支持#{}预编译占位符和${}字符串拼接两种模式。结果映射支持自动映射和高度定制化的resultMap让数据库字段和 Java 属性之间的关系清清楚楚。所以我一直建议学 JavaWeb 的同学哪怕以后想用 JPA也值得先把 Mybatis 学扎实。因为你会 SQL、懂映射、能调优这些能力在任何持久层方案里都是通用的而 Mybatis 是让你最快建立这套感觉的框架。1.3 和 JPA、JDBC 对比之后你就知道自己该怎么选很多讲 Mybatis 的文章喜欢直接开喷 JPA我觉得没必要。不同框架适合不同场景简单用一张表说清楚更实在维度原生 JDBCMybatisJPA / HibernateSQL 控制力完全控制完全控制框架生成复杂 SQL 较难调优开发效率低中高高上手门槛低但繁琐中需懂 SQL中高需理解对象关系映射动态 SQL 支持手动拼接非常灵活相对笨重适合场景学习原理、小工具复杂业务、团队规范明确标准 CRUD 为主、领域模型复杂我的观点很直接如果你做的是互联网业务系统SQL 复杂、性能要求高Mybatis 是稳妥选择如果做企业级管理系统标准 CRUD 占绝大多数JPA 能省不少事。但不管选哪个先把 Mybatis 搞明白你的 SQL 功底和排错能力一定会涨一大截。2. Mybatis 核心原理一条 SQL 从接口到数据库的完整旅程2.1 五个核心角色先记住它们再学操作我第一次看 Mybatis 的初始化流程时头都是大的因为名词太多。后来我用一条“点外卖”的类比把它记住了。SqlSessionFactoryBuilder相当于“餐馆总部的建店部门”它拿着一份装修图纸mybatis-config.xml帮你把一家餐馆SqlSessionFactory建好。它是个一次性的工具用完就可以扔。SqlSessionFactory建好的餐馆本体。一家餐馆可以接待很多客人所以它是全局单例整个应用生命周期里只需要创建一次。SqlSession一个客人到店后领到的“就餐位”。每个会话对应一次数据库连接的获取与释放它不是线程安全的所以不能共享。Executor后厨的厨师。真正执行 SQL、处理缓存、管理事务的都是它。MappedStatement菜单上的一道菜。它封装了一条 SQL 的完整定义SQL 文本、参数类型、返回类型、缓存策略等。记住这五个角色后面看配置初始化、看源码、排查问题都会轻松很多。2.2 XML 配置初始化工作原理XMLConfigBuilder 到底做了什么很多面试题会问“Mybatis 基于 XML 配置的初始化工作原理”其实答案就是围绕XMLConfigBuilder展开的。它的工作流程非常清晰读取mybatis-config.xml拿到根节点configuration。按 XML 节点的顺序依次解析properties、settings、typeAliases、environments、mappers等。每解析一个节点就把配置填充到Configuration对象对应的字段里比如environments节点会解析出数据源和事务工厂mappers节点会加载 Mapper XML 文件并把每一条 SQL 解析成MappedStatement。全部解析完成Configuration对象就是一份“完整菜单”SqlSessionFactoryBuilder拿着它构建出SqlSessionFactory。这里有个细节值得注意XMLConfigBuilder解析 Mapper XML 时会用XMLMapperBuilder逐个解析mapper文件校验 namespace 是否唯一然后解析select、insert、update、delete最终注册成MappedStatementkey 就是“namespace id”。这就是为什么“Invalid bound statement (not found)”那个报错会出现在方法找不到绑定上你的 Mapper 接口方法名或 namespace 只要有一个跟 XML 里对不上注册表里就查不到对应条目。到这一步你再回头看那个报错就会觉得它非常直白——不是数据库连不上而是你的“菜单”里根本没这道菜。2.3 手写一个最简 XML 配置初始化流程加深理解如果你跟我一样是“看十遍不如跑一遍”的选手可以自己用纯 Java 写一次初始化不需要 Spring Boot反而能把原理看得更透。String resource mybatis-config.xml; InputStream inputStream Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream); try (SqlSession sqlSession sqlSessionFactory.openSession()) { UserMapper userMapper sqlSession.getMapper(UserMapper.class); User user userMapper.findById(1L); System.out.println(user.getUsername()); }对应的最小mybatis-config.xmlconfiguration environments defaultdevelopment environment iddevelopment transactionManager typeJDBC/ dataSource typePOOLED property namedriver valuecom.mysql.cj.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/javaweb_demo/ property nameusername valueroot/ property namepassword value123456/ /dataSource /environment /environments mappers mapper resourcemapper/UserMapper.xml/ /mappers /configuration跑通这段代码你就等于亲手完成了一遍XMLConfigBuilder的初始化旅程。后面接入 Spring Boot无非是把“创建 factory”这一步交给容器管理本质没有变化。2.4 配置打印 SQL调试期的救命开关新手阶段最痛苦的事情之一就是 Mybatis 报 SQL 语法错误但你不知道它实际执行的是什么 SQL。网上搜“mybatis 配置打印”答案五花八门其实就两招属于我实测下来最稳定的方案。第一种方式在application.yml里配置日志级别logging: level: com.example.demo.mapper: debug原则是把 Mapper 接口所在的包名配成 debugMybatis 就会打印这个包下所有 SQL 的预编译语句和参数。第二种方式在 mybatis 配置里指定 StdOutImplmybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这种方式会把 SQL 直接打到控制台优点是简单粗暴缺点是不支持按包过滤所有 SQL 都会输出适合本地调试不建议带到生产。我建议新手无论如何先把打印 SQL 打开。你只有看到了 Preparing:和 Parameters:这两行才能真正理解#{}和${}的区别也才能在动态 SQL 出问题时第一时间定位。3. Mybatis 基础操作实战从环境搭建到完成注册功能3.1 搭建 Spring Boot Mybatis 项目骨架现在做 JavaWeb 项目最主流的方式就是 Spring Boot 整合 Mybatis。很多老教程还在教手动建SqlSessionFactory的 Bean其实mybatis-spring-boot-starter已经帮我们封装好了你只需要做三件事。第一步引入依赖。在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: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/javaweb_demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.demo.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl第三步在主类上加上MapperScan注解或者在每个 Mapper 接口上加Mapper注解。我个人的习惯是用MapperScan写在启动类上这样新加 Mapper 接口不用反复打注解SpringBootApplication MapperScan(com.example.demo.mapper) public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }这里有个细节容易踩坑mapper-locations配的是 XML 文件路径type-aliases-package配的是实体类包名。如果 XML 里的resultType不想写全限定类名就必须配置别名包扫描。而map-underscore-to-camel-case几乎必开不然数据库的create_time字段映射不到 Java 的createTime属性上。3.2 基础 CRUD 的 Mapper XML 写法每个符号都有讲究配置好环境之后核心就是写 Mapper 接口和 XML。我以一张最简单的user表为例把增删改查全部写透。表结构CREATE TABLE user ( id bigint NOT NULL AUTO_INCREMENT, username varchar(50) NOT NULL, password varchar(100) NOT NULL, email varchar(100) DEFAULT NULL, create_time datetime DEFAULT NULL, PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;对应的实体类public class User { private Long id; private String username; private String password; private String email; private LocalDateTime createTime; // getter / setter 省略 }Mapper 接口public interface UserMapper { User findById(Long id); ListUser findAll(); int insert(User user); int update(User user); int deleteById(Long id); }然后是核心的 XML。先说查询这是最基础的mapper namespacecom.example.demo.mapper.UserMapper select idfindById resultTypecom.example.demo.entity.User SELECT id, username, password, email, create_time FROM user WHERE id #{id} /select select idfindAll resultTypecom.example.demo.entity.User SELECT id, username, password, email, create_time FROM user /select insert idinsert parameterTypecom.example.demo.entity.User useGeneratedKeystrue keyPropertyid INSERT INTO user(username, password, email, create_time) VALUES(#{username}, #{password}, #{email}, #{createTime}) /insert update idupdate parameterTypecom.example.demo.entity.User UPDATE user SET username #{username}, password #{password}, email #{email} WHERE id #{id} /update delete iddeleteById DELETE FROM user WHERE id #{id} /delete /mapper这里有三个重点需要展开讲。第一个重点是#{}和${}的区别。#{}在预编译阶段会被替换成?占位符参数通过PreparedStatement安全传入能有效防 SQL 注入。${}是字符串直接拼接如果有用户输入参与等于把 SQL 注入漏洞直接打开。我的铁律是能用#{}的地方绝不用${}。唯一允许${}的场景是动态传入表名、排序字段这类无法预编译的 SQL 片段而且必须经过严格白名单校验。第二个重点是useGeneratedKeys和keyProperty。这两个属性是配合自增主键用的。不加这两个属性执行完 insert 之后传入的user.getId()仍然是 null因为你还没查数据库。加上之后Mybatis 会在执行完插入后把生成的主键回填到对象的id属性上省一次查询。注意keyProperty必须写 Java 属性名id而不是数据库列名。第三个重点是resultType的自动映射规则。开了map-underscore-to-camel-case之后create_time会自动映射到createTime但千万注意username这种没有下划线的字段也有隐式约定如果数据库列名和属性名完全一致自动映射没问题不一致时最好在 SQL 里用别名对齐或者使用resultMap强制指定后者的可维护性更高。举个例子如果表里的列叫uname属性叫username自动映射就不会生效你只能写SELECT uname AS username FROM user或者定义resultMap。3.3 用 Spring Boot Mybatis 实现一个最简单的注册功能前面说了那么多不如落地一个完整功能。注册功能是最典型的“项目整合”场景它能串起 Mapper、Service、Controller 三层同时涉及 insert、唯一性校验、密码处理三个关键点。第一步在 Service 层处理注册逻辑。我这里把校验写在代码里方便你看懂流程真实项目里建议配合参数校验注解Service public class UserService { Autowired private UserMapper userMapper; public void register(String username, String password, String email) { // 1. 校验用户名是否已存在 User existUser userMapper.findByUsername(username); if (existUser ! null) { throw new RuntimeException(用户名已存在); } // 2. 构建实体 User user new User(); user.setUsername(username); user.setPassword(password); user.setCreateTime(LocalDateTime.now()); // 3. 插入数据库 userMapper.insert(user); System.out.println(注册成功生成的自增ID user.getId()); } }第二步在 Mapper 中补充按用户名查询的方法。注意这里有一个新手常见问题如果你用selectByUsername返回User但查无此人时 Mybatis 返回的是 null 而不是空对象所以existUser ! null的判断是可靠的。另外如果只判断用户名是否重复建议给表的username字段加上唯一索引双保险避免并发场景下检查通过但插入失败。第三步Controller 层写入口。返回 JSON 结果的结构我习惯用一个统一响应类但这里只演示最小实现RestController RequestMapping(/api/user) public class UserController { Autowired private UserService userService; PostMapping(/register) public String register(RequestBody RegisterRequest request) { userService.register(request.getUsername(), request.getPassword(), request.getEmail()); return 注册成功; } }跑通这个功能之后你可以自己验证两个点。第一控制台打印的 SQL 里Parameters:行是否包含三个参数如果参数是 null 也会在这行显示这能帮你排查参数没传进来的问题。第二插入完成后user.getId()是否已经拿到自增主键拿不到就回去检查useGeneratedKeystrue和keyPropertyid是否配对。3.4 动态 SQLif 条件不生效多半是这几个原因动态 SQL 是 Mybatis 最实用的能力也是“条件不生效”问题的高发区。我先把最常用的if、where、set、foreach写一遍再讲为什么条件会失效。最常见的场景是多条件查询select idsearchUsers resultTypecom.example.demo.entity.User SELECT id, username, password, email, create_time FROM user where if testusername ! null and username ! AND username LIKE CONCAT(%, #{username}, %) /if if testemail ! null and email ! AND email #{email} /if /where /select条件不生效的问题我总结过四个高频原因。第一test表达式里忘了判断空字符串。username ! null只排除了 null如果前端传了个空字符串进来条件就会拼成AND username LIKE %%查出来一堆不该出现的数据。所以字符串字段的判断必须写成! null and ! 。第二if的位置不对。test里引用的参数名必须和 Mapper 方法参数名一致。如果你在接口里写的是User search(Param(name) String name)那么 XML 里也必须用name写成username就会报参数找不到。第三数值类型判断0的坑。如果条件参数是Integer类型值等于 0 时if teststatus ! null and status ! 这个表达式会直接报错或者不生效因为和Integer之间的比较行为不符合直觉。整数类型只要判! null就够了不用也不能和空字符串比较。第四动态 update 里要用set而不是手写SET。如果你这样写update idupdateUser UPDATE user SET if testemail ! null and email ! email #{email}, /if WHERE id #{id} /update当email为空时SQL 会变成UPDATE user SET WHERE id ?语法直接报错。用set标签会自动去除多余的逗号省心得多update idupdateUser UPDATE user set if testemail ! null and email ! email #{email}, /if /set WHERE id #{id} /update另外再说一个高频场景批量插入或批量查询用foreach。比如批量插入insert idbatchInsert INSERT INTO user(username, password, email, create_time) VALUES foreach collectionlist itemitem separator, (#{item.username}, #{item.password}, #{item.email}, #{item.createTime}) /foreach /insertcollection的值有讲究如果接口参数加了Param(list)这里就写list如果不加单参数集合默认也可以用list或collection但为了可读性我建议所有集合参数都显式加Param。4. Mybatis 缓存机制一级缓存、二级缓存与那些让你怀疑人生的坑4.1 一级缓存默认开启但它的生命周期比你想的短Mybatis 的一级缓存是SqlSession级别的也就是说同一个 SqlSession 内执行同一条 SQL相同语句、相同参数第二次会直接命中缓存不再查数据库。但实际在 Spring 环境中一级缓存的作用比你想象的小得多。因为 Spring 整合 Mybatis 后每一次 Mapper 方法调用都会重新获取和关闭 SqlSession默认情况下不同方法调用之间根本不会共享同一个 SqlSession。只有在同一个事务里Spring 才会让多个 Mapper 调用共用一个 SqlSession。我见过不少新手同学误以为一级缓存能帮忙减少重复查询结果发现根本不起效原因就在这里一级缓存的生效范围是“同一个 SqlSession 同一条 SQL 相同参数”Spring 默认的非事务调用根本不满足第一个条件。另外有几种情况会主动清空一级缓存。执行任何 insert、update、delete 会清空显式调用sqlSession.clearCache()会清空提交事务或关闭会话也会清空。这些行为不需要刻意记只需要记住结论一级缓存是在极短生命周期内的优化不能依赖它解决跨会话的数据一致性问题。4.2 二级缓存默认为关闭打开之后还有一堆注意点二级缓存是 Mapper 级别namespace 级别的缓存生命周期跨越多个 SqlSession。它的默认状态是关闭的要在 XML 里显式开启mapper namespacecom.example.demo.mapper.UserMapper cache/ /mapper光写cache/还不够被缓存的实体类必须实现Serializable接口因为二级缓存默认的存储方式涉及序列化。这一步漏掉运行时就会报NotSerializableException。cache/标签的可配置属性也很值得了解属性默认值说明evictionLRU淘汰策略常用 LRU最近最少使用和 FIFO先进先出flushInterval无缓存刷新间隔单位毫秒不设置就没有定期刷新readOnlyfalsetrue 时返回缓存对象的直接引用false 时返回序列化副本size1024缓存可以存放的对象数量blockingfalsetrue 时使用阻塞锁避免并发下缓存击穿如果设置了flushInterval一定要结合业务更新频率来定。我见过一个项目把二级缓存开了但没配flushInterval结果用户改了头像其他人看到的还是旧头像排查了很久才发现是这个原因。二级缓存最大的坑在于多表关联。因为二级缓存是按 namespace 隔离的如果你在两个 mapper 里分别查了user和user_order这两张表关联的数据而其中一个 namespace 更新了user_order表它只会清空自己的缓存不会去清空user那个 namespace 里的缓存。这就会导致另一个 namespace 读到过期的联表结果。对这个问题的通用解法是多表关联查询要么别用二级缓存要么使用CacheRef把相关的 namespace 绑定起来让其中一个更新时同时清空另一个。4.3 缓存相关的面试高频题背答案不如懂原理Mybatis 缓存这块是面试重灾区我把自己被问到过的问题整理了一份建议按“原理 场景”两条线来理解。一级缓存的失效场景有哪些核心答SqlSession 不同、SQL 不同、参数不同、执行了增删改、手动 clearCache、事务提交或回滚后。为什么 Spring 环境下一级缓存经常不生效核心答每次 Mapper 调用默认独立 SqlSession只有事务内共享。二级缓存为什么需要实体类实现序列化核心答默认存储策略PERPETUAL会通过序列化/反序列化保存对象readOnlyfalse时要返回副本避免多个会话拿到同一个引用互相污染。二级缓存数据不一致的典型场景核心答多表 join 查询 不同 namespace 更新不同表导致某个 namespace 缓存无法感知其他表的数据变化。我的建议是回答这些问题时不要只背结论要能画一条线出来会话发起查询 - 先查二级缓存 - 没命中查一级缓存 - 再没命中查数据库 - 结果逐级回填。你把这条链路讲清楚面试官基本就认可你是真懂。5. 常见问题与排查技巧实录可以当速查表用5.1 我实际踩过的坑全部整理成一张排查表这部分内容是我在练习和做小项目时真实遇到过的报错比官方文档更有参考价值。我按报错信息排查、原因分析和解决方案列成表格现象常见原因解决办法Invalid bound statement (not found)namespace、id、接口方法名不一致核对 XML 的 namespace 和select的 id与接口全限定名和方法名一一对应Parameter xxx not found. Available parameters are [...]多参数方法没有加Param多参数一律使用Param(xxx)显式命名查询结果字段全是 null列名和属性名对不上驼峰映射没开开启map-underscore-to-camel-case或用resultMap/ SQL别名中文乱码数据库连接 URL 没指定编码URL 加useUnicodetruecharacterEncodingutf8确认表字符集为 utf8mb4SQL 语法错误但代码看起来没问题动态 SQL 生成了非法语句打开 SQL 打印看 Preparing:实际执行的完整语句LIKE 查询查不到内容用${}直接拼或没处理通配符用CONCAT(%, #{keyword}, %)实体类序列化报错二级缓存开启但实体未实现 Serializable让实体类实现Serializable并加 serialVersionUID事务不生效异常被 try-catch 吞掉或方法被同类内部调用保证异常抛出到 Spring 代理边界不内部调用同类方法5.2 两个让我印象深刻的排错过程值得完整复盘第一个是“条件不生效”的完整排查。当时我做一个搜索接口前端传status0结果查出来的是全部数据。我第一反应是打开 SQL 打印发现实际执行的 SQL 里WHERE后面根本没有 status 条件。再回头看if teststatus ! null and status ! 明白了status是Integer等于 0 时status ! 的判断在类型转换上出了问题条件判为 false。改成teststatus ! null之后立即恢复正常。这个坑很典型我印象特别深因为当时网上搜到的答案大多是针对字符串类型的判断很少有人提整数判断空字符串的陷阱。第二个是二级缓存导致的数据“幽灵”。项目里有一个配置表后台改完配置前台怎么刷新都是旧值。当时第一反应是浏览器缓存清缓存没用查了接口返回发现数据确实是旧的最后看到 Mapper XML 里有cache/而配置表的更新操作在另一个 Mapper 里因为 namespace 不同更新操作根本清不到配置查询的那份缓存。从那以后我的规则就变成了凡是没有把握保证一致性的查询一律不用二级缓存。启动简单数据对不上才是噩梦。5.3 给新手的三个调试技巧能省你大量时间第一个技巧一定要学会“看 Preparing 不看报错”。Mybatis 的报错信息很多时候只告诉你“哪一行出错了”但不告诉你“实际执行的 SQL 长什么样”。排查 SQL 问题的第一件事永远是打开 SQL 打印确认实际执行语句、参数类型和参数值。第二个技巧单元测试里跑 Mapper 比启动整个 Web 应用快得多。Spring Boot 项目里写一个SpringBootTest的测试类直接调 Mapper 方法能省掉每次 CtrlC 重启 Tomcat 的时间。我练习时基本上就是写完 XML 立刻跑一个测试方法验证。第三个技巧善用Param比其他任何传参方式都稳。不管是单参数还是多参数统一加Param标注能让 XML 里的参数引用始终可控还能规避“Available parameters are”这类报错。代价只是多写几个字收益是排错时间大幅下降。6. Mybatis 基础操作之后下一步该往哪里走如果你把上面的内容都消化了并且亲手跑通了注册功能、做了一遍动态 SQL 查询那 Mybatis 的基础操作这一关就算真正过了。接下来我的建议是按这个顺序往上走。先把resultMap的关联映射学扎实。一对多、多对一在真实项目里躲不开association和collection怎么用、什么时候用嵌套查询什么时候用嵌套结果这些搞明白之后你写联表查询会顺手很多。再去看 Mybatis 与 Spring 事务的配合。到这一步你要理解的是为什么加了Transactional之后多个 Mapper 调用会共享一个 SqlSession以及事务回滚和缓存清空之间的关系。这个知识点绕不开因为几乎所有写操作接口都会涉及。最后才是 Mybatis 的插件机制和拦截器。分页插件 PageHelper、慢 SQL 拦截、数据权限过滤这些都是基于拦截器做的。学会写一个简单的拦截器你才算真正摸到了 Mybatis 的可扩展边界。我个人在实际操作中的体会是Mybatis 这东西网上教程看一百遍不如自己把一个注册功能从零跑通一遍。你只要亲手踩过“Invalid bound statement”“条件不生效”“缓存数据不新鲜”这三个坑对框架的理解就会上一个台阶。这也是为什么我在这篇文章里写了大量报错场景和排查过程——因为这些都是我在学习阶段真正卡住过的地方。最后再分享一个小技巧把你自己常踩的坑整理成一个notes.md文件放在项目根目录按“现象 - 原因 - 解决”三列记录。这个习惯我从学 Mybatis 一直保持到现在排查问题的速度比绝大多数同事都快。技术文档会过期但你自己攒下来的避坑笔记越用越值钱。
返回列表