
从做javaWeb项目到现在我越来越觉得很多人不是不会写SQL而是卡在“框架到底怎么把SQL跑起来的”这个坎上。Mybatis恰恰是理解这条链路最好的切入点也是javaWeb岗位面试里几乎绕不开的话题。这篇内容不打算讲什么高深理论就按我从入门到进阶实际走过的一条路把Mybatis基础操作、XML配置、初始化原理、缓存机制和常见坑一次说清楚。新手可以照着搭环境、写增删改查有点经验的朋友可以直接跳到第4章看原理和第5章看排错经验。1. 项目背景与学习路径设计1.1 先聊清楚一个问题javaWeb项目为什么需要Mybatis记得最早做javaWeb项目时用的是纯JDBC。写一个简单查询从Class.forName注册驱动开始接着是DriverManager.getConnection然后PreparedStatement、ResultSet最后还要手动关闭连接。一个查询写下来十几行其中真正有业务含义的只有SQL那一两行其他全是重复模板。更头疼的是表一多、字段一多这些代码几乎全是复制粘贴连接池管理稍不注意还会泄漏连接。Mybatis解决了两件最实际的事。第一件是把数据库连接管理、参数设置、结果集封装这些机械工作从业务代码里剥离出去你只需要关注SQL本身和参数映射。原来JDBC里setString、setInt那十几行代码在Mybatis里变成一个#{}占位符就完成原来ResultSet逐行取值封装的循环变成resultType自动映射。第二件是让SQL有了一个相对独立的管理位置。你可以把SQL写在XML映射文件里也可以写在注解里。XML方式的好处是SQL和Java代码分离DBA要review、要调优都方便改SQL不用重新编译Java代码。我在项目里基本都用XML方式原因很简单复杂SQL好维护动态SQL写起来也舒服。这里也想纠正一个新手常见误解Mybatis不是那种“全自动”ORM框架它更准确的定义是半自动ORM。Hibernate可以从实体类自动生成表结构、自动帮你拼SQLMybatis则是SQL你自己写框架负责参数绑定、执行和结果映射。正因为SQL完全可控遇到复杂多表联查、报表统计这些场景Mybatis反而更好用。1.2 从“能跑通”到“懂原理”的三个阶段接触过很多学javaWeb的朋友我一般建议把Mybatis学习分成三个阶段。第一阶段是“能跑通”。目标很简单搭起一个Maven项目引入MySQL驱动和Mybatis依赖写一份配置文件跑通一次最简单的查询。很多人卡在这一步不是因为代码复杂而是因为版本、依赖、配置路径对不上。你只要把这套骨架跑通一次后面所有操作都在这个骨架上加东西。第二阶段是“能写全”。增删改查全部过一遍知道参数怎么传、结果怎么收理解#{}和${}的区别会写if、where、foreach动态SQL。到这个阶段你已经能应付日常开发里70%左右的持久层需求。第三阶段是“能说清”。打开源码看清楚SqlSessionFactoryBuilder怎么构建ConfigurationXMLConfigBuilder如何把配置文件解析成MappedStatement弄清楚一级缓存和二级缓存到底存在于哪个范围为什么在Spring项目里二级缓存经常“看起来没生效”。这个阶段对应的是面试和真正的项目调优。我见过不少停在第二阶段的开发上手项目很快但一旦遇到“缓存不生效”“条件拼错”“连接被关闭”这类问题就开始乱试。根因是他们不知道框架底层做了什么。所以这篇文章的节奏就是前两章帮你到第二阶段后三章专门拉你往第三阶段走。2. 环境准备与核心配置先让第一个Mybatis项目跑起来2.1 IDEA上运行javaWeb项目的环境配置严格来说Mybatis可以单独跑不依赖Web容器。但这个标题既然叫javaWeb从入门到进阶我建议一开始就按Web项目的标准来建工程省得后面学Servlet和SpringMVC时又要换工程结构。我常用的组合是IDEA 2023.x JDK8或JDK17 Maven 3.8 MySQL 8 Tomcat 9。如果只是为了学Mybatis本身可以暂时不配Tomcat先用一个带main方法的原生Demo把Mybatis跑通等到学SpringMVC阶段再补Web容器。IDEA里运行javaWeb项目有几个容易踩的点这里逐个说。第二Project Structure里要把Web模块勾上并在Modules面板里指定Web资源目录。很多人新建项目时选了Maven骨架但忘了把webapp目录标记成Web资源目录结果启动Tomcat后访问不到页面和Servlet。第二Tomcat的Deployment里要配置热部署。在Run/Debug Configurations的Server选项卡里把On “Update” action和On frame deactivation都设为Update classes and resources这样改完Java代码或XML不用反复重启Tomcat。这个配置能明显提升开发效率尤其是在XML映射文件改得频繁的时候。第三依赖不要全堆在WEB-INF/lib里。用Maven管理依赖时servlet-api这类scope标记为provided的只在编译时需要mysql驱动、mybatis这类运行期必需的依赖只要在pom.xml里声明Maven会自动打包进去。如果你看到ClassNotFoundException: org.apache.ibatis.io.Resources先检查依赖到底有没有被打进产物里。兼容性上MySQL 8的JDBC驱动类名已经变成了com.mysql.cj.jdbc.Driver老版本教程里写的com.mysql.jdbc.Driver虽然也能兼容但会有弃用警告。配置数据源时写成新版能少很多不必要的报错。2.2 mybatis-config.xml核心配置解析Mybatis的全局配置文件通常习惯命名为mybatis-config.xml。在resources目录下新建这个文件把一个最小可用版本放出来?xml version1.0 encodingUTF-8 ?!DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//EN http://mybatis.org/dtd/mybatis-3-config.dtd configuration environments defaultdevelopment environment iddevelopment transactionManager typeJDBC/ dataSource typePOOLED property namedriver valuecom.mysql.cj.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/demo?useUnicodetrueamp;characterEncodingutf8/ property nameusername valueroot/ property namepassword value123456/ /dataSource /environment /environments mappers mapper resourcemapper/UserMapper.xml/ /mappers /configuration这里要解释几个日常容易忽略的配置意图。environments可以配置多套环境比如development、test、prod用default属性指定默认用哪套。现在做微服务中间件项目经常需要切换配置原生Mybatis保留这个结构就是为了让多环境切换不用改代码只改default指向。transactionManager类型选JDBC表示由Mybatis自己管理事务的提交和回滚。新手最容易踩的坑就在这里写insert/update/delete后如果代码里没有执行sqlSession.commit()数据表面上看是执行了但数据库里根本没落盘。这个特性一开始很让人困惑但其实是在引导你建立显式事务意识后面切到Spring管理事务时你会感谢现在踩过的这一脚。dataSource的type选POOLED表示用连接池。Mybatis内置了一个很简单的连接池实现入门阶段用它就够了。生产项目后面都会交给Spring管理数据源比如HikariCP但原生阶段的代码改动可以非常小就是换个数据源实现。mappers节点的作用是告诉框架去哪加载SQL映射文件。有个细节特别容易坑如果映射文件放在resources目录下直接用resource属性写classpath相对路径如果你把XML和Mapper接口放在同一个包目录比如com.example.mapper需要到pom.xml里额外配置resources否则编译时XML不会复制到target/classes运行一调Mapper就报找不到绑定语句。2.3 映射文件XML的书写规范UserMapper.xml是核心学习对象。第一行是DTD声明映射文件中的namespace属性必须与对应Mapper接口的全限定名一致这是Mybatis实现接口动态代理的关键。先看最简单的查询写法?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 idfindById resultTypecom.example.pojo.User select id, username, email from user where id #{id} /select /mapper同时要有对应的Mapper接口package com.example.mapper; import com.example.pojo.User; public interface UserMapper { User findById(Integer id); }要记住一条硬规矩接口方法名必须和XML里的id一致参数类型、返回类型要能对应。Mybatis启动时会通过namespace加id定位到MappedStatement如果接口方法在XML里找不到对应id调用时会直接抛BindingException。这个阶段你会频繁看到resultType和resultMap两个属性。resultType属于简化映射要求数据库列名和Java属性名能直接对应。如果表字段是下划线风格比如create_time而Java属性是驼峰风格createTime你需要先配置mapUnderscoreToCamelCase等于告诉框架自动做下划线转驼峰这样select *返回的结果也能正确封装。resultMap则是显式定义列和属性的映射关系适合数据库列名和实体类属性差异较大、多表联查字段不一致的场景第3章里我会再展开。3. 基础操作实战从增删改查到动态SQL3.1 查询操作参数传递与模糊查询查询是日常开发里最高频的操作先把查询的各种写法吃透后面的学习负担会轻很多。先说参数传递。Mybatis里传单个简单参数可以直接用#{}花括号里的名字怎么起都行因为框架只看位置但多个参数时如果不加Param注解框架会默认用param1、param2来引用代码可读性很差。我的习惯是统一加ParamUser findByUsernameAndEmail(Param(username) String username, Param(email) String email);对应XMLselect idfindByUsernameAndEmail resultTypecom.example.pojo.User select id, username, email from user where username #{username} and email #{email} /select新手最容易报的一个错是“Parameter ‘username’ not found”原因就是多个参数时XML里写了名字但接口方法上没加Param框架不知道这个名字对应哪个实参。模糊查询也很有讲究。早期教程喜欢在Java代码里拼好“%xx%”把整个模糊串传进去。这种方式能用但通配符写死在代码里复用性差还容易因为硬编码导致SQL拼接混乱。我更推荐用concat函数在SQL里拼接select idsearchByKeyword resultTypecom.example.pojo.User select id, username, email from user where username like concat(%, #{keyword}, %) /select这样能让同一个接口在调用时传不带通配符的关键字既安全又灵活是真实项目里比较规范的写法。3.2 新增操作与主键回填Mybatis的insert语句本身不复杂但两个高频需求容易出问题一个是要拿到数据库自增主键的值另一个是保存后要回填这个主键到实体对象里。数据库自增主键的情况使用useGeneratedKeysinsert idinsertUser parameterTypecom.example.pojo.User useGeneratedKeystrue keyPropertyid insert into user(username, email, password) values (#{username}, #{email}, #{password}) /insert几个要点拆开讲。keyProperty写的是Java实体类的属性名不是数据库列名。这个很多人搞反导致insert之后拿到id还是null。useGeneratedKeystrue会让Mybatis在执行完insert后把JDBC返回的自增主键值填到传入对象对应的属性上。好处很明显插入后的业务代码可以直接用user.getId()拿到主键不需要再单独执行一次select last_insert_id()。如果主键不是自增而是由应用生成比如雪花ID、UUID那就在insert之前给实体属性赋值然后用#{id}正常写入即可不存在“回填”需求。还有一个小坑如果你用 显式指定要插入的字段千万别漏掉某个非空字段否则数据库非空约束会报错。这种情况在测试环境最频繁排查方式就是把生成的SQL打印出来逐字段比对下一节第5章会讲怎么配置SQL打印。3.3 更新与删除动态条件与批量删除更新操作通常有两种场景按主键更新固定字段以及按条件更新非固定字段。前一种直接写死SQL就好update idupdateEmail update user set email #{email} where id #{id} /update第二种更接近真实业务场景因为前端传来的往往只是一部分字段。老手都知道不能先查再改既慢又有并发风险。正确做法是动态SQL的set标签update idupdateSelective update user set if testusername ! null and username ! username #{username}, /if if testemail ! null and email ! email #{email}, /if /set where id #{id} /updateset标签会自动处理多余的逗号。如果自己手工拼很容易在最后一个字段后面多个逗号导致语法错误。还要提醒一点if判断不等于空串时要注意Java属性类型如果是Integer这种数字类型判断! 会抛异常数字字段建议只判断! null。删除操作相对简单但一定要确认条件唯一。按id删除没问题按username删除时如果有同名记录会删掉多条。批量删除用foreach拼in条件delete iddeleteByIds delete from user where id in foreach collectionids itemid open( separator, close) #{id} /foreach /delete3.4 动态SQL组合拳if、where、foreach动态SQL是Mybatis区别于其他框架的核心卖点也是真实项目里避免重复SQL的关键。掌握if、where、foreach基本就够覆盖绝大多数场景。where和if组合时用where标签会自动去掉第一个条件前面的and比写“where 11”优雅得多。典型查询select idlistByCondition resultTypecom.example.pojo.User select id, username, email 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注意通常每个if里写的是“and条件”最后都靠where标签自动去掉第一个and。如果新手手动写成“where and”就是典型语法错误。foreach除了用在delete的in条件还经常用于批量insertinsert idbatchInsert insert into user(username, email, password) values foreach collectionlist itemitem separator, (#{item.username}, #{item.email}, #{item.password}) /foreach /insert批量insert会把整个列表拼成一条大SQL如果列表上万条很容易超过数据库的max_allowed_packet限制。实践上要控制批次大小比如每500条执行一次。这里collection属性也有讲究单参且没加Param时list或collection可以用多参数且加了Param时必须用Param里的名字来引用。再补充一个choose/when/otherwise相当于Java的switch适合“多个条件里选一个”的场景。比如前端传查询类型type1按用户名2按邮箱3按手机号写起来直观又清晰。4. 进阶Mybatis初始化原理与缓存机制4.1 XmlConfigBuilder与MappedStatement的初始化流程很多读者搜过一个热词叫“xmlconfigbuilser”正确拼写应该是XMLConfigBuilder。这个名字出现在Mybatis初始化流程里搞清楚它就搞懂了“配置如何变成可执行SQL”的完整链路。初始化链路由三部分组成SqlSessionFactoryBuilder.build(InputStream) → XMLConfigBuilder.parse() → Configuration → DefaultSqlSessionFactory → SqlSession。第一步SqlSessionFactoryBuilder读取mybatis-config.xml输入流创建XMLConfigBuilder。XMLConfigBuilder内部通过XPathParser解析XML节点把settings、environments、typeAliases、mappers等逐项解析进Configuration对象。Configuration是Mybatis的“全家桶”各类配置项、别名注册表、类型处理器注册表、Mapper注册信息都在里面。你在XML里配的mapUnderscoreToCamelCasetrue本质上是给Configuration设置对应属性每配置一个typeAlias就是在别名注册表里加一项每加载一个mapper就会解析映射文件所有select、insert等标签生成MappedStatement对象缓存到Configuration。MappedStatement很好理解一条被解析好的SQL语句的全部信息包括SQL模板、参数类型、结果映射、是否回填主键等。后续每次执行SQLSqlSession实际上是取出这个MappedStatement交给Executor去执行。经常有人问“Mapper接口没有实现类为什么能调用”答案就在动态代理里。Mybatis会为Mapper接口生成代理对象invoke时根据方法名找到Configuration里对应的MappedStatement然后走JDBC执行流程。这也是为什么接口方法名和XML的id必须一一对应任何一边缺失都会在启动阶段或第一次调用时暴露问题。4.2 一级缓存SqlSession范围内的本地缓存缓存是Mybatis面试高频话题。先从范围说起一级缓存是SqlSession级别的也就是同一个数据库会话内有效。工作流程是同一个SqlSession里连续执行两条完全相同的SQL第一次查询结果会缓存在本地第二次执行时不再真正访问数据库直接返回缓存结果。前提是查询条件完全一致并且中间没有发生增删改。这个设计是为了避免同一个会话里的重复查询省掉数据库往返时间。但它有几个大坑。坑一是Spring整合后SqlSession因为动态代理每次操作都可能重新创建一级缓存几乎等于失效。很多人在Spring Boot项目里测试一级缓存测不出来这是正常的因为框架层面没有用一个长生命周期的SqlSession包住一次业务操作。坑二是同一个SqlSession里先查后改再查Mybatis会在执行update、insert、delete后自动清理一级缓存防止读脏数据。这是缓存一致性的最后防线。坑三是如果你用同一个SqlSession做跨事务的多步查询可能读到别人未提交的数据。这其实不完全是缓存问题跟事务隔离级别有关但面试时能把缓存和事务联动起来讲会显得底层理解更透。4.3 二级缓存namespace级别缓存与失效分析二级缓存的边界是Mapper也就是namespace级别比一级缓存范围大多SqlSession之间可以共享。默认情况下MySQL方言下二级缓存是关闭的需要显式开启。开启包括两步。第一步在mybatis-config.xml中settings setting namecacheEnabled valuetrue/ /settings第二步在对应映射文件中加上cache/加了cache标签后该Mapper下所有查询结果会进入应用层缓存多个SqlSession共享。但这里藏着一个经典误区以为缓存开了就一定快最后反而搞出脏数据。比如一个Mapper查了user表并缓存另一个Mapper直接改了user表二级缓存根本不知道数据变化继续返回旧数据。根因是二级缓存默认只感知当前Mapper内部发生的增删改无法感知其他Mapper的写操作。Spring Boot Mybatis项目里二级缓存往往“看着美、用着险”。因为Spring容器管理事务跨多个Mapper的业务会把缓存一致性问题放大。我的实践建议是只有单表单Mapper、读多写少、且能接受短期不一致的业务才考虑二级缓存一旦涉及多表关联和跨Mapper写入优先关闭用本地缓存或分布式缓存替代。还需要注意开启二级缓存后查询结果对应的实体类必须实现Serializable接口因为Mybatis在某些情况下会对缓存对象做序列化。不实现运行时写入缓存就会直接抛异常。4.4 自定义Configuration的扩展思路热词里有一条“mybatis中自定义configuration”这里补充一点。Configuration是Mybatis配置的容器想对它做扩展最简单的方式是继承Configuration覆写你关心的某个方法再传入SqlSessionFactoryBuilder的build方法重载里。实际业务里很少这么干但有一些场景比如统一给所有MappedStatement加缓存、统一注册类型处理器会用到。大多数项目里更常见的方式是直接修改XML配置让Configuration的属性按需变化。比如数据库下划线转驼峰、开启懒加载、设置默认ExecutorType为BATCH。在Spring Boot里这些也可以通过mybatis.configuration.*前缀直接配置原理是把属性值注入自动创建的Configuration对象。理解这条链路后再看到各种“mybatis配置不生效”的问题排查起来就有方向了。5. 工程化实践Spring Boot MyBatis整合要点5.1 依赖引入与自动配置的核心逻辑企业级项目现在基本都用Spring Boot整合Mybatis这也是“第1关项目整合 - springboot mybatis”的由来。整合之后开发体验确实好很多只需要一个起步依赖dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version /dependencymybatis-spring-boot-starter会自动把Mybatis核心、Spring集成包、自动配置类带进来。Spring Boot的自动配置会读取spring.datasource.*配置创建数据源并自动生成SqlSessionFactory和SqlSessionTemplate注册到Spring容器。配置文件的写法spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.pojo configuration: map-underscore-to-camel-case: true这段配置里最关键的是mapper-locations它告诉Mybatis去哪找映射XML。很多初学者把XML放在src/main/java目录下却忘了配置mapper-locations启动时不报错一调用Mapper就报Invalid bound statement (not found)。解决方法是把XML移到resources/mapper目录或者配置好resources的编译路径。5.2 把SQL打印出来配置打印与日志开发阶段必须能快速看到SQL否则出了问题光靠肉眼猜是猜不出来的。我在Spring Boot的application.yml中常用两种方式。第一种是直接把Mybatis的日志实现切换为StdOutImplmybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl控制台会打印Preparing: select id...、Parameters: 1 (Integer)、Total: 1等关键信息。你可以清楚看到参数的绑定值和返回行数。第二种是更规范的logging方式把指定mapper包级别设为debuglogging: level: com.example.mapper: debug这种方式输出格式更标准生产环境还能通过调整级别控制是否打印SQL。我一般在开发环境用第二种临时排查时用第一种。两种都建议掌握。5.3 事务管理与常见坑Spring整合后事务控制权交给Spring默认一个Service方法就是一个事务边界。在Spring Boot中给Service方法标注Transactional即可。为什么要标在Service层而不是Mapper层因为事务控制的是业务调用链一个方法里往往包含多个Mapper操作它们的提交和回滚必须绑定在一次事务里。如果只在Mapper接口上加Transactional意义很小那只是单次SQL操作而且默认自动提交。事务不生效的经典三种情况方法被private修饰、同类内部方法调用绕过代理、类本身没有被Spring管理。排查顺序就是这三条。还有一种情况是事务确实生效但Mybatis的insert后没看到数据。这可能和数据源配置有关也可能是连接持有了未提交事务。遇到这种问题优先确认是不是同一个事务连接再看隔离级别。5.4 用Spring Boot MyBatis实现一个最简单的注册功能热词里有一条“第2关使用springboot mybatis实现一个最简单的注册功能”我顺手把这条链路串一遍新手可以照着实现。注册功能涉及三层Controller接收请求Service做校验Mapper执行SQL。Controller层RestController public class UserController { Resource private UserService userService; PostMapping(/register) public Result register(RequestBody User user) { boolean success userService.register(user); return success ? Result.success() : Result.error(用户名已存在); } }Service层Service public class UserServiceImpl implements UserService { Resource private UserMapper userMapper; Transactional(rollbackFor Exception.class) public boolean register(User user) { int count userMapper.countByUsername(user.getUsername()); if (count 0) { return false; } userMapper.insertUser(user); return true; } }Mapper接口里准备countByUsername和insertUser两个方法XML按前面的写法实现即可。这样注册功能就串起来了。你真正要体会的是分层Mybatis只负责“查重”和“插入”两次数据库操作业务校验、事务边界都在Service层Controller只做参数接收和结果返回。6. 高频问题排查与面试考点速查6.1 我在项目里遇到过的常见问题第一个高频问题是“Invalid bound statement (not found)”。这个报错我在新手群里见得太多了原因其实很集中Mapper接口找到但XML里的namespace或id和接口对不上。排查方向很直接看target/classes下有没有XML文件再看namespace是不是接口全限定名最后看方法名是否一致。第二个非常坑的是“where条件不生效”。最常见根源是if test里把Java属性名写错。比如数据库列名是user_nameJava属性是userNameif test里却写成user_name。框架不会报错只是条件永远为false查出来的结果就是全表数据。这种问题很隐蔽尤其在数据量大时线上会有性能事故风险。第三个是“#{}和${}搞混”。#{}是预编译占位符值会被当作参数并通过setObject设置能有效防止SQL注入${}是字符串直接替换常用于表名、列名等动态部分。新手如果忍不住用${}拼字符串必须确保传入值经过严格校验。我在项目里的约定是能用#{}绝不用${}只有表名、排序字段这种没法预编译的地方才允许${}。第四个是主键回填失效。很多人明明写了useGeneratedKeystrue但insert之后拿到的id还是null通常是keyProperty写成了数据库列名id而实体类属性名是userId。调整keyProperty为实体属性名即可。把这些问题做成速查表现象最常见原因优先排查方向Invalid bound statementnamespace或id不匹配检查接口全限定名、方法与XML id条件不生效if里属性名写错比对Java属性名与数据库列名映射SQL注入风险误用${}拼接用户输入改写为#{}或做白名单校验主键没回填keyProperty属性名错误改成实体类属性名新增没入库忘了commit或事务问题确认事务边界和SqlSession提交6.2 面试里关于Mybatis的高频考点结合我自己的面试经验Mybatis的核心考点可以浓缩成几个连环问。第一问通常是“Mybatis和JDBC的关系”。答案要落到Mybatis封装了JDBC的Connection、PreparedStatement、ResultSet等操作但SQL本身还是自己写。紧接着就会问预编译这时候就到了#{}和${}区别的考点。第二问是“一级缓存和二级缓存的区别各自有哪些失效场景”。一级缓存范围是SqlSession默认开启增删改会清空跨SqlSession不共享。二级缓存范围是namespace默认关闭开启后要序列化多Mapper操作同一张表会有脏读风险。第三问是“Mybatis初始化流程”。从SqlSessionFactoryBuilder到XMLConfigBuilder到Configuration到MappedStatement整个过程就是把XML配置变成Java对象。如果还能把动态代理和Executor串联进来说面试官基本就会点头了。这几个问题如果能在纸上画出来讲清楚“请求进来之后从接口到SQL真正执行的完整路径”在面试里的印象分会明显提高。6.3 我的实操体会带过几次新人之后我最大的感触是Mybatis入门不难但很多人的基础操作在“能跑”就停住了。有些人增删改查写得飞快但一问useGeneratedKeys是干什么的、二级缓存开在哪个范围就支支吾吾。所以我建议所有学javaWeb的人把第4章的内容认真看两遍然后自己写一个demo打断点走一遍初始化流程。源码其实没有想象中那么复杂核心就是Configuration和MappedStatement这两个类。在实际项目中我还会强制自己养成一个习惯每次写完一个Mapper方法先看一眼控制台打印的SQL。训练个两周你基本就能通过观察SQL形态快速预判参数绑定、动态条件、事务提交可能出的问题。这一步能帮你在开发阶段消灭掉九成以上的Mapper问题。最后分享一个经验给所有Mapper方法写清注释尤其是复杂查询的注释。半年后你自己维护时看到的不再是亲切的代码而是“这段SQL到底在查什么”的谜题。现在多写两句后面的人会真心感谢你。