MyBatis 数据库配置与 SQL 操作全解析:从动态 SQL 到二级缓存

发布时间:2026/9/9 3:06:08

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 基本不会再给你造成困扰。
延伸阅读

更多相关文章

2026/9/9 3:06:08

Page Assist:让Ollama本地大模型更好用的浏览器插件

简介:Page Assist是一款面向ollama本地AI模型的Chrome浏览器插件,致力于让用户在不离开当前网页的情况下,通过侧边栏即可完成模型对话、参数配置等操作,解决切换窗口带来的效率损耗。这份资源包共含95个文件,以ttf/wof…

2026/9/9 3:01:08

Java Arrays工具类在集合中的核心应用与常见坑解析

先问个很现实的问题&#xff1a;一个int[]数组&#xff0c;想交给一个只接受List<Integer>的方法&#xff0c;你第一反应是不是Arrays.asList(arr)&#xff1f;很多人就是这么干的&#xff0c;结果拿到一个size为1的List&#xff0c;里面装的是一个长度为N的int[]。这个场…

2026/9/9 3:01:08

Windows内核驱动安装卸载与安全防御实战指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/9 4:11:14

大模型选型实战指南:Tokenizer、API错误与场景适配

1. 这不是“哪个模型更好”的玄学投票&#xff0c;而是工程师手里的工具箱选型指南你打开终端敲下第一行代码前&#xff0c;得先确认手边这把“螺丝刀”是不是真能拧开眼前这颗锈死的螺栓。DeepSeek、Claude、GPT——这三个名字最近像地铁报站一样高频刷屏&#xff0c;但它们从…

2026/9/9 4:11:14

航拍小目标检测:YOLOv8配合热力图嵌入与蛇形卷积的实践

最近在做一个无人机航拍场景的小目标检测项目&#xff0c;场景其实很朴素&#xff1a;3000x3000的大图里&#xff0c;车辆和行人往往只占十几个到几十个像素。YOLOv8跑起来以后&#xff0c;漏检率高得吓人&#xff0c;那种只有10像素左右的小目标&#xff0c;置信度永远卡在0.3…

2026/9/9 4:11:14

ESP32在线烧录全攻略:网页刷写、串口烧录与OTA升级实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/9/9 4:11:14

YOLOv26行人车辆检测实战:从训练到部署

1. 项目动机&#xff1a;为什么我盯上了“行人车辆”双目标检测做目标检测的都知道&#xff0c;COCO数据集上跑个mAP图个乐很容易&#xff0c;但真正把模型丢到真实交通场景里&#xff0c;面对白天黑夜、晴天雨天、密集人流和川流不息的车流时&#xff0c;模型的“人设”瞬间就…

2026/9/9 4:06:14

Consul与Nacos选型指南:注册中心内核、实战与混合云架构

微服务化搞了这么多年&#xff0c;服务注册与发现早就是每个团队的默认配置。但真到了选型的时候&#xff0c;很多人还是会卡在同一个问题上&#xff1a;Consul 和 Nacos&#xff0c;到底选哪个&#xff1f;这个问题我在好几个项目里反复面对过&#xff0c;一开始跟风选过 Naco…

2026/9/8 7:15:10

超人会飞不算本事:系统稳定依赖清晰规则与边界设计

开头先不绕弯子。“#斯坦李吐槽dc 所以超人是无缘无故会飞的嘛哈哈哈哈哈哈哈锤哥真是技术人才啊&#xff01;#雷神 #复联”这类调侃式短标题&#xff0c;第一波冲击力在于它把两个宇宙的角色塞进同一个吐槽箱里&#xff0c;但细想一下就能发现&#xff0c;它真正碰到的根本不是…

2026/9/8 7:15:15

超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论

把“蜘蛛侠 vs 超人”放在 CSDN 上聊&#xff0c;可能很多人第一反应是走错片场了。但如果把这两个角色看成“两个持续运营了 80 多年的文化产品”&#xff0c;你会发现&#xff0c;这场比较本质上是两个不同 IP 策略的长期结果对比&#xff1a;超人赢在定义了整个超级英雄题材…

2026/9/8 7:15:10

基于CNN的调制信号识别:MATLAB实现时频图分类实战

简介&#xff1a;本资源是一套面向通信工程与信号处理方向学习者、研究者的深度学习实践方案&#xff0c;聚焦调制信号自动检测与识别这一典型无线通信任务&#xff0c;解决传统方法依赖人工特征、低信噪比下性能下降等痛点。压缩包共12个文件&#xff08;10.73MB&#xff09;&…

2026/9/9 0:00:48

MHS模型硬件标准:让大模型像调用软件一样控制物理设备

让Claude真正看着显微镜说“这个细胞形态不太对”&#xff0c;或者让大模型自己调一版机械臂的运动轨迹&#xff0c;这事儿听上去已经很接近科幻片了。但你真上手试一次就会发现&#xff0c;模型不缺智商&#xff0c;缺的是一个能插进显微镜、机械臂、激光控制器里的“通用插座…

2026/9/9 0:00:48

AI五大核心方向详解:从机器学习到大模型,零基础转行选哪条?

会有人告诉我&#xff0c;他想转行学AI&#xff0c;但打开招聘网站一看直接傻眼&#xff1a;机器学习、深度学习、自然语言处理、计算机视觉、大模型应用……满屏都是这些词&#xff0c;好像每个都会一点&#xff0c;又好像每个都离自己很远。还有人上来就问“学Python还是学Ja…

2026/9/9 0:00:49

从50行最小循环到生产级AI引擎:工程化改造全解析

直接说干货。这一章我写的不是那种"hello world跑通某个模型"的教程&#xff0c;而是把AI引擎当做一个真正要上线、要被人调用、要扛流量的系统来聊。从最初只有50行的最小循环&#xff0c;到能够承载生产流量的AI引擎&#xff0c;中间差的不是代码量&#xff0c;而是…

2026/9/7 16:23:03

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行&#xff0c;Type-C接口算是典型的“看着简单&#xff0c;做起来全坑”的东西。光引脚就24个&#xff0c;高低速信号、电源、控制线全部塞在一个小小的连接器里&#xff0c;如果PCB布局不做规划&#xff0c;打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/7 22:46:00

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/7 22:45:59

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
咨询二维码