MyBatis 数据库配置与 SQL 操作全解析:从动态 SQL 到二级缓存 说实话在做 Java 后端这几年里MyBatis 是我用得最多的持久层框架没有之一。市面上的教程一抓一大把但大部分都停留在“怎么配能跑起来”的程度真正遇到问题的时候比如 SQL 日志打不出来、动态 SQL 拼接报错、缓存不生效、表不存在却没人告诉你很多人就卡住了。这篇内容我就围绕“MyBatis 数据库配置与 SQL 操作”这条主线把从全局配置文件到 Mapper 映射、从动态 SQL 到缓存机制、从日志打印到自动建表这些实操中绕不开的问题按我自己的使用经验完整拆一遍。这篇文章适合谁看呢刚接触 MyBatis、被各种配置折磨得头疼的新人用了半年一年但只停留在 CRUD、没搞懂底层逻辑的开发者还有准备面试、想系统梳理 MyBatis 核心知识点的同学都能从中拿到一些能直接用的东西。我尽量讲得透一点不光是给结论也会解释为什么是这个结论。1. 项目概述与整体设计思路1.1 为什么还要回头啃 MyBatis 配置很多人会有个疑问现在 SpringBoot 整合 MyBatis 不是很简单吗加个依赖、写个接口、配个扫描路径就完事了为什么还需要专门去研究配置但实际项目里当你遇到这些场景就会发现“自动配置”帮不了你生产环境数据库密码不能明文写在 application.yml 里需要从配置中心动态获取这时候就得理解 MyBatis 的 properties 加载机制多数据源切换一个项目同时连 MySQL 和 Oracle默认的自动配置根本不够用分页插件、逻辑删除、审计字段自动填充这些都要在 MyBatis 配置层面做插件拦截表结构不在代码里建但希望项目启动时自动检查表是否存在、不存在则创建就得对接 MyBatis 的执行器生命周期所以我说配置是 MyBatis 的第一道门槛也是最容易踩坑的地方。搞清楚配置等于掌握了 MyBatis 的拦截点后面做扩展就是水到渠成的事情。1.2 技术选型思路原生 MyBatis 还是 MyBatis-Plus现在很多团队直接上 MyBatis-Plus确实省事。但我的建议是先理解 MyBatis 本身再决定要不要用 Plus。原因很简单MyBatis-Plus 是对 MyBatis 的增强没有改变 MyBatis 的执行模型。你把 MyBatis 本身搞明白了用 Plus 就是锦上添花反过来如果只学 Plus 不学 MyBatis遇到报错看底层源码都无从下手。从设计思路上看MyBatis 的核心就是一个SQL 映射框架。它的整体设计可以分为三层配置层读取 mybatis-config.xml 和 Mapper XML 文件构建 Configuration 对象和 MappedStatement 对象执行层SqlSession 作为门面Executor 作为执行器StatementHandler 负责 JDBC 语句处理映射层ParameterHandler 设置参数、ResultSetHandler 处理结果集完成 Java 对象和数据库记录之间的互转这三层就是我们理解 MyBatis 配置和 SQL 操作的总纲。后面所有内容都可以归入这三层来理解配置层管“读什么”执行层管“怎么跑”映射层管“怎么转”。2. MyBatis 全局配置与核心要素拆解2.1 mybatis-config.xml 里每个标签的作用完整版的 mybatis-config.xml 长这样我先贴一个我常用的基础配置?xml version1.0 encodingUTF-8 ? !DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//EN http://mybatis.org/dtd/mybatis-3-config.dtd configuration !-- 引入外部 properties 文件 -- properties resourcedb.properties/ !-- 全局参数设置 -- settings !-- 开启驼峰命名自动映射 -- setting namemapUnderscoreToCamelCase valuetrue/ !-- 配置控制台日志输出 -- setting namelogImpl valueSTDOUT_LOGGING/ /settings !-- 类型别名 -- typeAliases package namecom.example.entity/ /typeAliases !-- 环境配置 -- environments defaultdevelopment environment iddevelopment transactionManager typeJDBC/ dataSource typePOOLED property namedriver value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ /dataSource /environment /environments !-- 映射器注册 -- mappers package namecom.example.mapper/ /mappers /configuration这里面的标签顺序是有讲究的DTD 要求properties、settings、typeAliases、environments、mappers必须按这个顺序排列写错了直接解析报错。我自己第一次写的时候把typeAliases放到了environments后面启动就抛了 XML 解析异常这个细节要注意。properties 标签的两个坑如果同时使用 resource 属性引入外部文件又在properties标签内直接写property子标签那么内部定义的优先级更高。这个优先级机制容易让人困惑建议只使用一种方式如果将来配置放到 Spring 配置中心这里的${jdbc.username}占位符就失效了。要先通过 Spring 的SqlSessionFactoryBean的configurationProperties属性注入这个我们在 SpringBoot 整合部分展开settings 标签是最值得花时间看的常用参数我整理了一个表格设置项可选值默认值说明mapUnderscoreToCamelCasetrue/falsefalse字段名下划线转驼峰强烈建议开启不然数据库user_name映射不到userNamecacheEnabledtrue/falsetrue二级缓存总开关类级别缓存开关在 Mapper XML 里单独配lazyLoadingEnabledtrue/falsefalse延迟加载总开关开启后association/collection可以按需加载aggressiveLazyLoadingtrue/falsefalse为 true 时任何方法调用都会触发完整加载建议保持 falsejdbcTypeForNull枚举OTHER插入 null 值时指定的 JDBC 类型Oracle 建议改成 NULLlogImplSLF4J/LOG4J/STDOUT_LOGGING未设置决定 SQL 日志输出框架调试利器2.2 数据源与事务管理器选型逻辑environments在 SpringBoot 整合时其实用不到Spring 自己管理事务和数据源。但在原生 MyBatis 场景下这里就是命根子。事务管理器type有两个选择JDBC和MANAGED。JDBC事务控制交给 JDBC 的commit()/rollback()需要手动开启事务MANAGED事务控制交给外部容器比如 SpringMyBatis 不参与事务管理数据源type有三个选择UNPOOLED、POOLED、JNDI。UNPOOLED每次请求都新建连接适合小项目或测试环境POOLED使用连接池复用连接生产环境基本都选这个JNDI从 J2EE 容器获取连接现在很少用了但我要提醒的是在实际 SpringBoot 项目中数据源往往不是 MyBatis 自己创建的而是通过 Spring 容器注入的。典型做法是Configuration里定义DataSourceBean然后用SqlSessionFactoryBean组装Configuration public class MyBatisConfig { Bean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception { SqlSessionFactoryBean factoryBean new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); factoryBean.setMapperLocations( new PathMatchingResourcePatternResolver() .getResources(classpath:mapper/*.xml)); factoryBean.setTypeAliasesPackage(com.example.entity); // 可以继续设置 configurationProperties、plugins 等 return factoryBean.getObject(); } }这也是一个很多人在面试时说不清楚的点SpringBoot 自动配置的 MyBatis 和原生 MyBatis 的区别是什么本质就是 Spring 帮你做了上面的步骤把 SqlSessionFactory、SqlSessionTemplate、MapperScannerConfigurer 这些 Bean 都创建好在容器里了但你依然可以通过mybatis.configuration.map-underscore-to-camel-casetrue这样的配置项覆盖全局设置。2.3 Mapper 绑定与命名空间的那些坑mappers标签用来注册 Mapper 接口对应的 XML 文件有两种主流方式!-- 方式一逐个注册 -- mappers mapper resourcecom/example/mapper/UserMapper.xml/ /mappers !-- 方式二包扫描 -- mappers package namecom.example.mapper/ /mappers方式二更省事但有个前提Mapper 接口和 XML 文件必须在同一个包路径下。如果你 XML 放在src/main/resources/mapper/下接口在com.example.mapper那么包扫描方式就找不到 XML。这时候要么把 XML 也移动到com/example/mapper/目录下要么用方式一逐个指定 resource。另外一个高频坑是namespace 写错导致启动报错。MyBatis 要求 Mapper XML 的mapper namespacecom.example.mapper.UserMapper必须和接口全限定名一致同时 XML 中的select标签的id必须和接口方法名一致。这几点对应不上启动时就会报Invalid bound statement (not found)。我排查这类问题有个固定套路确认Mapper接口是否被Mapper注解或MapperScan扫描到确认 XML 的namespace和接口类路径是否完全一致确认 XML 中id是否和接口方法名一致确认编译后 XML 是否打进了 target/classes 目录Maven 项目容易漏掉 resources 下的 XML如果都正常最后看mapper-locations配置是否匹配 XML 存放路径这个排查顺序基本能解决 90% 的Invalid bound statement报错。3. SQL 操作核心细节与动态 SQL 实战3.1 参数传递的三种方式你用对了吗Mapper 方法传参看着简单但写法不对会让代码非常恶心。我总结下来有三种情况。第一种单个简单类型参数User selectById(Long id);select idselectById resultTypeUser SELECT * FROM user WHERE id #{id} /select这种最简单#{id}里的名字随便写都能取到因为只有一个参数。但我不建议乱写保持语义清晰比较好。第二种多个参数没有使用 ParamUser selectByNameAndAge(String name, Integer age);这时候如果直接写#{name}会报错提示Parameter name not found。MyBatis 会把多个参数包装成Param1、Param2所以正确写法是select idselectByNameAndAge resultTypeUser SELECT * FROM user WHERE name #{param1} AND age #{param2} /select但这种写法可读性极差我强烈建议使用ParamUser selectByNameAndAge(Param(name) String name, Param(age) Integer age);select idselectByNameAndAge resultTypeUser SELECT * FROM user WHERE name #{name} AND age #{age} /select第三种传 POJO 或 MapListUser queryByCondition(UserQuery query);select idqueryByCondition parameterTypecom.example.query.UserQuery resultTypeUser SELECT * FROM user WHERE name #{name} AND age #{age} /selectMap 同理#{key}对应 Map 的 key。这里要注意parameterType在 MyBatis 3.5 中其实可以省略框架自动推断但写上更直观。3.2 动态 SQL 核心标签逐个过动态 SQL 是 MyBatis 最强大的能力之一没有它你就得靠 Java 代码手工拼接 SQL既容易漏空格又有 SQL 注入风险。我把核心标签按照使用频率排个序if、where、set、foreach、choose、trim。if 标签的坑——空串判断常见写法select idfindByCondition resultTypeUser SELECT * FROM user where if testname ! null and name ! AND name #{name} /if if testage ! null AND age #{age} /if /where /selecttest里的条件用的是OGNL 表达式不是 Java 语法所以比较字符串要用或!而不是equals。另外我踩过坑的是前端传了空字符串过来name ! null为 true结果 SQL 变成了AND name 查不出数据。所以字符串类型一定要同时判断空串。where 标签的原理where标签会自动去掉第一个满足条件的AND或OR。它的实现原理是判断子元素第一个 token 是不是AND/OR是就去掉。这比手动写WHERE 11要优雅得多。set 标签处理更新语句update idupdateById parameterTypeUser UPDATE user set if testname ! nullname #{name},/if if testage ! nullage #{age},/if /set WHERE id #{id} /updateset会自动去掉最后一个多余的逗号。这里还有个通用注意点如果所有字段都为 null最终的 SQL 是UPDATE user SET WHERE id ?会直接报 SQL 语法错误。所以更新操作最好在 Java 层加校验或者用if保证至少有一个字段非空。foreach 标签拼 IN 查询select idselectByIds resultTypeUser SELECT * FROM user WHERE id IN foreach collectionids itemid open( separator, close) #{id} /foreach /selectcollection属性值取决于传入参数类型List 默认是list数组是arrayMap 里的值是Map 的 key。如果用了Param(ids) ListLong ids那collection就是ids。还有一点性能建议IN 里面的元素不要太多数据库对 IN 列表长度通常有限制超过了会报错或导致索引失效建议超过 1000 个就分批查。choose 标签实现分支逻辑类似 Java 的switch - caseselect idselectByCondition resultTypeUser SELECT * FROM user where choose when testname ! null and name ! AND name #{name} /when when testage ! null AND age #{age} /when otherwise AND status 1 /otherwise /choose /where /select当多个条件同时满足时只取第一个when这是和if最本质的区别。3.3 ResultMap 结果映射字段对不上才是大问题数据库字段是下划线风格user_nameJava 属性是驼峰风格userName不处理的话查出来就是 null。开启mapUnderscoreToCamelCasetrue能解决大部分问题但有些场景必须用 ResultMap 显式映射。典型场景包括多表联查某字段来自别的表一对多结果需要嵌套集合字段名和属性名根本对不上比如数据库的user_name映射到 Java 的name基础 ResultMap 写法resultMap idUserResultMap typeUser id propertyid columnid/ result propertyuserName columnuser_name/ result propertyemail columnemail/ /resultMap select idselectById resultMapUserResultMap SELECT * FROM user WHERE id #{id} /select一对多嵌套映射是面试高频题resultMap idUserWithOrdersResultMap typeUser id propertyid columnid/ result propertyuserName columnuser_name/ collection propertyorders ofTypeOrder id propertyid columnorder_id/ result propertyorderNo columnorder_no/ result propertyamount columnamount/ /collection /resultMap select idselectUserWithOrders resultMapUserWithOrdersResultMap SELECT u.id, u.user_name, o.id AS order_id, o.order_no, o.amount FROM user u LEFT JOIN orders o ON u.id o.user_id WHERE u.id #{id} /select这里有两个细节要注意collection的子查询 SQL 里的列必须起别名和column对应否则映射不上如果启用了延迟加载lazyLoadingEnabledtrue关联对象在第一次访问时才发起额外查询这个能避开但也会造成 N1 查询问题生产环境要权衡对于 N1 我自己更推荐的方式是先分页查主表拿到 id 列表再用IN查询把关联数据一次查回来在 Java 内存里做组装。虽然多写几行代码但性能可控、逻辑清晰不会因为嵌套映射把 SQL 搞得很难调试。4. 缓存机制一级、二级缓存和命中率陷阱4.1 一级缓存默认开启却总被忽略MyBatis 的一级缓存是SqlSession 级别的默认开启且无法关闭。同一个 SqlSession 中执行两次完全相同的查询第一次会查数据库第二次直接返回缓存结果。这里有个经典面试题为什么 Spring 整合 MyBatis 后一级缓存几乎形同虚设因为 Spring 整合后默认的 SqlSession 是由SqlSessionTemplate管理的它实现了SqlSession接口但会在每次执行完 Mapper 方法后关闭底层 SqlSession。也就是说不同 Mapper 方法调用之间一级缓存已经失效了。只有在同一个方法里连续执行两次查询比如同一 Service 方法中调用两次同一个查询一级缓存才可能命中。一级缓存还有一个大坑如果在同一个 SqlSession 中先查了数据然后执行了增删改MyBatis 会清空一级缓存。这个设计是合理的为了避免脏读但如果你依赖一级缓存来减少重复查询就要小心这个行为。4.2 二级缓存真正跨会话的缓存怎么配二级缓存是Mapper 级别的跨 SqlSession 生效。配置方式mapper namespacecom.example.mapper.UserMapper !-- 开启二级缓存useCache 默认 true -- cache evictionLRU flushInterval60000 size512 readOnlyfalse/ /mapper参数解释eviction回收策略默认LRU最近最少使用可选FIFO、SOFT、WEAKflushInterval刷新间隔单位毫秒不设置则没有间隔只有执行增删改时才刷新size最多缓存对象个数默认 1024readOnlytrue 表示返回缓存对象的同一引用性能好但可能修改缓存false 表示序列化复制一份安全但性能差使用二级缓存有几个注意事项被缓存的对象必须实现 Serializable 接口因为 readOnlyfalse 时要做序列化所有通过该 Mapper 的增删改都会清空二级缓存多表联查时谨慎开启如果 UserMapper 的查询关联了 orders 表但 orders 表的更新操作走的是 OrderMapperUserMapper 的二级缓存不会感知就会导致脏读4.3 缓存命中率与常见误区我自己遇到的一个经典场景是某查询结果集很大想着加二级缓存提升性能结果因为频繁更新导致缓存一直清空缓存命中率极低反而因为序列化和反序列化拖慢了响应。所以后来 MyBatis 缓存我基本遵循两个原则查多写少、数据变动不频繁、实时性要求不高的数据才适合二级缓存比如字典表、配置表对数据一致性要求高的业务数据宁可放弃缓存直接用数据库也别为了那一点性能提升引入脏读风险面试里问“MyBatis 缓存的执行顺序”标准答案是查数据时先查二级缓存再查一级缓存最后查数据库。写数据后一级缓存和二级缓存都会被清空。5. 常见问题排查实录与热词延伸5.1 表不存在自动建表的实现思路很多人搜“springboot mybatis 表不存在自动建表”说明大家都有这个需求场景比如跑测试环境、做演示项目不想手动执行建表脚本。我的做法是这样在 SpringBoot 启动完成后用一个ApplicationRunner或ApplicationListenerApplicationReadyEvent通过 JdbcTemplate 先SHOW TABLES LIKE xxx判断表是否存在不存在就执行建表 SQL。Component public class TableAutoInitializer implements ApplicationRunner { Autowired private JdbcTemplate jdbcTemplate; Override public void run(ApplicationArguments args) { String checkSql SELECT COUNT(*) FROM information_schema.tables WHERE table_schema DATABASE() AND table_name user; Integer count jdbcTemplate.queryForObject(checkSql, Integer.class); if (count null || count 0) { String createSql CREATE TABLE user ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_name VARCHAR(50) NOT NULL, age INT DEFAULT NULL ) ENGINE InnoDB DEFAULT CHARSET utf8mb4; jdbcTemplate.execute(createSql); } } }这个方案的好处是和 MyBatis 解耦纯粹用 JdbcTemplate 就能搞定不用在 MyBatis 层面搞什么特殊配置。生产环境下建议配合 Flyway 或 Liquibase 做版本化数据库迁移更规范。5.2 MyBatis 和 MyBatis-Plus到底怎么选网上关于这个问题的答案是“MyBatis-Plus 增强 CRUD、支持 ActiveRecord、内置分页插件”但实际项目选型不能只看功能列表要从团队和维护两个角度想。我的倾向性建议纯查询复杂、SQL 以人工编写的 XML 为主、团队对 SQL 掌控很强选原生 MyBatis业务以单表 CRUD 为主、团队追求开发效率、不想写大量重复的 XML选 MyBatis-Plus表结构频繁变动、需要代码生成器快速出 Mapper/Service/ControllerMyBatis-Plus 的MybatisPlusGenerator确实方便但要注意MyBatis-Plus 里有几个东西默认行为和 MyBatis 不同容易出问题。例如逻辑删除字段默认值配置、自动填充字段的时间类型、以及selectById在逻辑删除配置下会自动拼接AND deleted 0这些如果没理解底层排查起来会非常痛苦。5.3 控制台打印 SQL 日志的几种姿势调试 SQL 是日常最高频的操作。给不同场景列一下配置方式方式配置适用场景MyBatis 标准输出mybatis.configuration.log-implorg.apache.ibatis.logging.stdout.StdOutImpl本地测试最直接logback 输出配置 logger 级别为 DEBUG生产环境按需开关IDEA 插件MyBatis Log Free自动把占位符参数填充成完整 SQL强烈推荐我的习惯是本地开发用MyBatis Log Free它能直接看到完整的、可以复制执行的 SQL 语句非常方便排查参数问题。生产环境用 logback 配 mapper 包的 logger 为 DEBUG输出带参数占位符的 SQL再配合慢查询日志做性能分析。5.4 高频面试题#{}和${}的区别这个题几乎逢面必问。标准回答是#{}是预编译参数占位符MyBatis 会将其替换为?然后通过PreparedStatement设置参数值能防 SQL 注入${}是字符串直接替换MyBatis 会把它直接拼接到 SQL 中存在 SQL 注入风险实际使用中${}只适合做无法用占位符代替的场景比如动态表名、动态排序字段ORDER BY ${sortField} ${sortOrder}。这些场景如果用#{}生成的 SQL 会是ORDER BY create_time DESC字符串值会导致排序失效。另外补充一个冷知识MyBatis 对#{}的参数占位如果参数值为 null默认 JDBC 类型是OTHEROracle 下会报错。需要配置jdbcTypeForNullNULL或在 SQL 里显式指定#{param, jdbcTypeOTHER}这是 Oracle 使用者常见坑。6. 源码级扩展从一条 SQL 看 MyBatis 执行全流程6.1 从 Mapper 接口到数据库中间发生了什么要真正理解 MyBatis 配置和 SQL 操作我强烈建议读一下源码中核心几个类的调用链路。它没有想象中复杂一条 SQL 的执行路径大致是MapperProxyJDK 动态代理拦截接口方法调用找到对应的MappedStatement从 Configuration 中按 id 获取交给SqlSession的selectOne/selectList方法SqlSession把任务委托给Executor默认是CachingExecutor包装BaseExecutorExecutor创建StatementHandlerParameterHandler处理 SQL 参数StatementHandler通过 JDBC 的Statement执行 SQLResultSetHandler把结果集映射成 Java 对象理解这条链路的最大意义在于你知道在哪些节点可以拦截扩展。MyBatis 的Interceptor就是围绕Executor、StatementHandler、ParameterHandler、ResultSetHandler这四个组件的。分页插件 PageHelper 的本质就是拦Executor改写 SQL 加入LIMITMyBatis-Plus 的乐观锁插件、多租户插件也是同样的原理。6.2 插件拦截器配置层面的高级玩法Intercepts({ Signature( type StatementHandler.class, method prepare, args {Connection.class, Integer.class} ) }) public class MySqlInterceptor implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { // 获取原始 SQL StatementHandler statementHandler (StatementHandler) invocation.getTarget(); BoundSql boundSql statementHandler.getBoundSql(); String sql boundSql.getSql(); // 在这里可以做 SQL 改写、参数打印、性能统计等 long start System.currentTimeMillis(); try { return invocation.proceed(); } finally { long cost System.currentTimeMillis() - start; if (cost 1000) { // 记录慢 SQL 日志 } } } }使用插件时要在 mybatis-config.xml 里注册或者 SpringBoot 中用Bean交给SqlSessionFactory。注意插件最好不要拦截太过频繁的方法否则会影响性能。我自己写自定义插件只做两件事慢 SQL 统计和特殊 SQL 改写其他需求尽量用 MyBatis-Plus 现成的插件。6.3 一个 TableHandler 工具类的设计思路做报表查询这类项目时经常要根据参数动态选择表名比如按月份分表order_202501、order_202502。这时候用${tableName}比较直接但要严格校验白名单避免 SQL 注入。public String resolveTableName(String suffix) { // 白名单校验只允许数字和指定前缀 if (!suffix.matches(\\d{6})) { throw new IllegalArgumentException(非法表名后缀: suffix); } return order_ suffix; }select idselectByMonth resultTypeOrder SELECT * FROM ${tableName} WHERE user_id #{userId} /select这段代码想说明的是${}不是完全不能用但使用者必须自己保证传入值的安全性。能用#{}就用#{}实在不行用${}时一定要在代码层做白名单校验这是底线。6.4 数据库工具与配置延伸热词里频繁出现的“dbx 数据库工具官网”“mysql 安装配置教程”这类搜索说明很多人在做环境搭建时一头雾水。我用过的数据库客户端也不少简单说几句体会日常开发我习惯用 DataGrip跨数据库支持好SQL 提示和重构功能强dbx 这类国产轻量工具我也体验过界面更简洁对国内开发者来说上手门槛低环境搭建方面MySQL 8.x 的安装主要注意字符集选 utf8mb4、认证插件选caching_sha2_password或mysql_native_password的兼容性问题这些工具层面的东西不影响 MyBatis 本身但是配好一个称手的数据库客户端排查 SQL 问题的效率会高很多。每次 MyBatis 报 SQL 语法错误我都是把日志里的 SQL 复制到客户端里直接执行比在代码里干瞪眼快得多。6.5 面试高频MyBatis 执行器和懒加载关于执行器MyBatis 默认有三种SimpleExecutor每执行一次 SQL 就创建一个 Statement 对象ReuseExecutor复用预处理语句PreparedStatement减少编译开销BatchExecutor批量执行增删改配合ExecutorType.BATCH使用SpringBoot 整合时默认是SimpleExecutor。批量插入大批量数据时可以考虑用SqlSessionTemplate配合ExecutorType.BATCH性能提升非常明显。我自己实测过插入 10 万条数据BatchExecutor 比逐条插入至少快 5-10 倍但要注意批量操作后要手动 flush并且事务边界要控制好。懒加载的实现原理是查询主对象时返回一个代理对象关联对象的加载逻辑写在代理对象里真正调用关联对象的方法时才去执行额外的 SQL。理解了这一点你就知道懒加载和缓存是可以叠加使用的但也会引入 N1 查询问题系统设计时要全局考虑。7. 实操总结我的一点个人体会写到这里基本上把 MyBatis 从配置到 SQL 操作到缓存到源码阅读的路径捋了一遍。最后分享几个我这几年在实际项目中的体会。第一不要跳过基础配置直接追求花哨功能。很多 MyBatis 相关的疑难杂症根源都在 mybatis-config.xml 的 settings 没配置对或者 Mapper 注册方式有问题。把这些基础配置吃透比会写十个高级 SQL 有用得多。第二动态 SQL 能力直接决定了你在团队里的地位。复杂业务报表、条件组合查询、多表关联更新能写出一套清晰、高效、参数可维护的动态 SQL解决的是别人搞不定的问题。所以我建议把if、where、foreach、choose加上bind绑定变量复用这些标签练到条件反射。第三遇到问题先看完整 SQL再看缓存再调性能。我在公司带人的时候给新人强调最多的就是MyBatis 报错第一时间把日志里的 SQL 拿出来看绝大多数问题 SQL 本身就能告诉你答案。日志里没有 SQL那就先解决日志配置问题。如果你正准备系统学习 MyBatis建议按照这个顺序先把 mybatis-config.xml 的每个标签过一遍然后手写一个 CRUD 的 mapper XML再尝试写一个多表联查的 ResultMap接着把动态 SQL 全部用一遍最后看一遍缓存和插件源码。这个过程走下来MyBatis 基本不会再给你造成困扰。

相关新闻

最新新闻

消息代理实战:用hermes-agent构建统一消息分发管道

消息代理实战:用hermes-agent构建统一消息分发管道

我是在第七次因为同一个线上告警被不同渠道轮番轰炸之后,才决定认真对待“消息分发”这个问题的。那天下午,同一份故障通知以三条完全不同的格式分别从工单系统、IM群机器人、邮件列表涌进来,而我却花了十几分钟才把三份信息对起来&#xff0…

2026/9/9 3:46:15
六大Pro Max旗舰横评:2025年机皇花落谁家?

六大Pro Max旗舰横评:2025年机皇花落谁家?

9月确实是机圈一年里最热闹的月份,苹果、华为、三星这几家固定要在这个时间点碰头,安卓阵营的旗舰也跟着凑热闹。今年尤其夸张,光带“Pro Max”后缀的顶配大屏机,就有六款挤在一起,媒体朋友们管这叫“六大派围攻光明顶…

2026/9/9 3:46:15
VC++结合ZPL指令实现Zebra GT800条码打印机USB打印详解

VC++结合ZPL指令实现Zebra GT800条码打印机USB打印详解

简介:一套面向VC开发者的ZPL条形码打印工程源码,演示通过USB接口连接斑马GT800打印机并发送ZPL指令生成条码,重点解决Windows环境下调用winspool驱动实现底层打印通信的问题。资源围绕SetupDi系列API与CreateFile/WriteFile调用展开&#xff…

2026/9/9 3:46:15
Python进阶路线图:从环境搭建到系统设计的完整修炼

Python进阶路线图:从环境搭建到系统设计的完整修炼

别人学Python半年就能上手干活,你学了一年还在原地踏步?问题多半不在智商,而在你一直在用学“知识”的方式去学一门“手艺”。这个标题里的关键词拆开来看,核心不是“Python”,而是“进阶”两个字。语法、API、框架这些…

2026/9/9 3:46:15
STM32F767利用HAL库实现SBUS协议解析与遥控接收机信号处理

STM32F767利用HAL库实现SBUS协议解析与遥控接收机信号处理

简介:面向STM32开发者和航模/飞控爱好者,这份资料以完整可编译的Keil工程演示了基于HAL库的UART接收机SBUS信号解析方案,是一份可直接对照学习的实战代码工程。主控为STM32F767,通过串口接收SBUS数据帧并解析,将1-16通…

2026/9/9 3:46:15
npx skill add实战:用ponytail技能包整合AI编程工作流与工程经验

npx skill add实战:用ponytail技能包整合AI编程工作流与工程经验

在AI编程助手和工作流工具越来越普及的今天,我一直在整理自己的技能包体系。前阵子刷到一个叫“ponytail”的开源skill包,安装命令很直接:npx skill add dietrichgebert/ponytail。试了一周,它把原本散落在各种笔记、代码片段和配…

2026/9/9 3:41:15