MyBatis内部链路全解析:从Builder到查询的完整流程与避坑指南
1. 从一次线上排查说起为什么需要搞懂MyBatis内部链路去年帮一个朋友排查一个诡异问题他们的服务在压测时某个查询接口的响应时间会周期性飙高但数据库侧的慢查询日志里却什么都看不到。Druid连接池监控显示活跃连接数正常GC日志也没有明显异常。最后定位到问题出在MyBatis的SqlSession没有及时关闭导致连接被长时间占用连接池在高峰期频繁触发创建新连接的开销。这个案例让我意识到很多人用MyBatis停留在“写Mapper接口、配XML、调方法”的层面对builder → factory → session → mapper → 查询这条完整链路缺乏系统认知。一旦出问题排查方向全靠猜。这篇文章就把这条链路从头到尾拆一遍把每个环节的职责、关键对象、常见坑点讲清楚。不管你是刚接触MyBatis的新手还是用了几年但没深入过源码的老手应该都能从中找到对自己有用的东西。整条链路的核心角色有五个SqlSessionFactoryBuilder、SqlSessionFactory、SqlSession、Mapper接口代理对象、以及最终的查询执行器。它们之间的关系可以类比成一条生产线Builder是建厂房的施工队Factory是厂房本身Session是每次开工时领到的工位Mapper是工位上的操作面板Executor则是真正干活的机器。下面逐层拆解。2. 全局视角五个核心角色的职责与生命周期2.1 一张表看清各组件定位在深入细节之前先用一张表把五个核心角色的职责、生命周期和线程安全性说清楚。这张表是我自己在学习源码时整理的放在手边随时对照理解起来会快很多。组件核心职责生命周期线程安全SqlSessionFactoryBuilder解析配置文件构建Factory方法级用完即弃安全局部变量SqlSessionFactory生产SqlSession的工厂应用级全局唯一安全构建后不可变SqlSession一次数据库会话请求级/方法级不安全Mapper代理对象将接口方法调用转为SQL执行随Session创建不安全绑定SessionExecutor执行SQL、处理缓存和结果映射随Session创建不安全这张表里最关键的信息是生命周期和线程安全两列。很多线上问题追根溯源都是因为把Session或Mapper代理对象当成了可以跨线程共享的东西。Factory之所以安全是因为它在构建完成后内部状态不再变化而Session之所以不安全是因为它内部持有Connection、事务状态、一级缓存等可变状态。2.2 为什么这样分层你可能会问为什么不直接用一个类搞定所有事情非要拆成Builder、Factory、Session这么多层这其实是建造者模式和工厂模式的经典组合应用。Builder负责“怎么造”把复杂的解析逻辑XML解析、注解扫描、配置合并封装起来Factory负责“造出来的东西长什么样”它是一个不可变的配置快照Session负责“怎么用”每次数据库交互都需要独立的会话上下文。这种拆分带来的好处是配置解析只做一次Factory可以复用Session按需创建和销毁职责边界非常清晰。如果不用Builder模式每次创建Factory都要重复写一大堆解析代码如果不用Factory模式Session的创建逻辑就会散落在各处。分层之后每一层只关心自己的事修改配置解析逻辑不会影响Session的行为替换Session实现也不会影响Factory的构建。3. 第一步SqlSessionFactoryBuilder如何解析配置3.1 入口方法与核心流程SqlSessionFactoryBuilder的入口方法通常是build(InputStream inputStream)。这个方法内部会创建一个XMLConfigBuilder然后调用它的parse()方法。整个解析过程大致分三步解析configuration节点读取configuration下的所有子节点包括properties、settings、typeAliases、environments、mappers等。构建Configuration对象把解析结果填充到一个Configuration实例中这个对象是MyBatis的全局配置中心。返回DefaultSqlSessionFactory用填充好的Configuration创建Factory实例。这里有个细节值得注意XMLConfigBuilder在解析mappers节点时会进一步创建XMLMapperBuilder来解析每个Mapper XML文件。也就是说配置解析是一个递归的过程主配置解析到Mapper映射时会触发Mapper文件的解析。3.2 解析过程中的关键决策在解析settings节点时有几个参数对后续行为影响很大我列出来供参考cacheEnabled是否开启二级缓存默认true。如果项目里没用二级缓存建议显式设为false避免不必要的开销。lazyLoadingEnabled是否开启延迟加载默认false。关联查询多的时候可以开启但要注意N1问题。defaultExecutorType执行器类型默认SIMPLE。可选REUSE和BATCH批量操作场景下BATCH能显著提升性能。mapUnderscoreToCamelCase下划线转驼峰默认false。现在大部分项目都会设为true省去手写resultMap的麻烦。这些参数在解析时会被存入Configuration对象后续创建Executor和映射结果时都会用到。我个人的习惯是在项目初期就把这些参数明确配置好不要依赖默认值因为默认值不一定适合你的业务场景。3.3 实操心得Builder用完就丢SqlSessionFactoryBuilder的生命周期非常短它的使命就是构建出Factory构建完成后就可以被GC回收。所以标准的写法是这样的String resource mybatis-config.xml; InputStream inputStream Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory new SqlSessionFactoryBuilder().build(inputStream);注意这里new SqlSessionFactoryBuilder()是临时的不需要保存引用。有些初学者会把Builder存成成员变量这完全没有必要反而占内存。Factory才是需要全局保存的那个对象通常用单例模式管理。提示如果项目中同时存在多个数据源可以创建多个SqlSessionFactory每个对应一个数据源。但要注意每个Factory的Configuration是独立的二级缓存也是独立的。4. 第二步SqlSessionFactory的构建与配置快照4.1 DefaultSqlSessionFactory内部结构DefaultSqlSessionFactory是SqlSessionFactory接口的默认实现它的内部结构非常简单只持有一个Configuration对象的引用。这个Configuration就是Builder阶段解析出来的全局配置快照。Factory的核心方法是openSession()它有多个重载版本主要区别在于三个参数ExecutorType、TransactionIsolationLevel、autoCommit。这些参数决定了创建的Session的行为特征。Factory本身不持有任何数据库连接连接是在Session创建时才从DataSource获取的。这一点很关键Factory是轻量的创建Factory不会消耗数据库资源真正消耗资源的是Session。4.2 openSession的参数选择openSession()的几个重载版本在实际项目中的使用场景不太一样我整理了一个对照表方法签名适用场景注意事项openSession()默认手动提交事务需要显式commitopenSession(boolean autoCommit)自动提交每条SQL独立事务性能差openSession(ExecutorType)指定执行器类型BATCH模式需注意flush时机openSession(ExecutorType, boolean)批量自动提交一般不推荐组合使用我个人的经验是在Spring集成环境下这些参数通常由Spring的SqlSessionTemplate管理不需要手动调用openSession()。但理解这些参数的含义对于排查事务相关问题非常有帮助。比如当你在Spring中配置了ExecutorType.BATCH但发现批量插入没有生效很可能是因为没有在合适的时机调用flushStatements()。4.3 配置快照的不可变性Factory持有的Configuration对象在构建完成后理论上不应该再被修改。但MyBatis并没有用final修饰Configuration的字段所以如果你在运行时通过Factory获取Configuration并修改它是能改成功的。但这种做法非常危险因为Configuration是全局共享的修改会影响所有Session。我踩过一次坑在运行时动态添加了一个Mapper映射结果导致二级缓存出现脏数据。后来改成在启动阶段就完成所有配置运行时不再修改Configuration问题就消失了。所以我的建议是把Configuration当成不可变对象来用所有配置在启动时确定运行时只读。5. 第三步SqlSession的创建与事务管理5.1 Session创建时发生了什么当调用factory.openSession()时内部会执行以下步骤从Configuration中获取Environment对象进而获取TransactionFactory和DataSource。通过TransactionFactory创建一个Transaction对象Transaction内部会从DataSource获取一个Connection。根据ExecutorType创建对应的ExecutorSIMPLE、REUSE或BATCH。用Executor和Configuration创建DefaultSqlSession实例。这个过程里最耗资源的是第2步从DataSource获取Connection。如果使用连接池这一步可能涉及连接的创建、验证或复用。在高并发场景下连接池的配置直接影响Session创建的效率。5.2 一级缓存的秘密每个SqlSession内部都有一个一级缓存它是一个PerpetualCache底层用HashMap实现。一级缓存的作用域是Session级别同一个Session内执行相同的查询相同的statementId、相同的参数、相同的SQL第二次会直接从缓存返回不再查数据库。这里有个容易踩的坑一级缓存不会自动失效。如果在同一个Session中先查询了一次然后通过其他方式比如另一个Session或直接操作数据库修改了数据再查询时拿到的还是旧数据。解决办法是在修改操作后调用session.clearCache()或者干脆让Session的生命周期足够短。在Spring集成环境下如果不开启事务每次Mapper方法调用都会创建一个新的Session一级缓存实际上不会生效。只有在事务范围内多个Mapper调用共享同一个Session一级缓存才会起作用。这也是为什么有些开发者觉得一级缓存“时灵时不灵”的原因。5.3 事务管理的正确姿势MyBatis的事务管理有两种方式JdbcTransaction和ManagedTransaction。前者由MyBatis自己管理commit和rollback后者把事务控制权交给外部容器如Spring。在Spring集成环境下通常使用ManagedTransaction事务的提交和回滚由Spring的DataSourceTransactionManager负责。这时候如果你手动调用session.commit()可能会抛出异常因为事务已经由Spring接管了。我见过一个典型的错误用法在Spring管理的Service方法中手动获取SqlSession并调用commit结果导致事务状态混乱。正确的做法是在Spring环境下不要手动管理Session和事务交给Spring的SqlSessionTemplate和事务管理器处理。注意SqlSession不是线程安全的绝对不能在多线程之间共享。每个线程应该有自己的Session实例。在Web应用中Session的生命周期通常与请求或事务绑定。6. 第四步Mapper接口代理对象的生成机制6.1 从接口到代理对象当你调用sqlSession.getMapper(UserMapper.class)时MyBatis会通过JDK动态代理为这个接口生成一个代理对象。这个代理对象的核心逻辑在MapperProxy类中它实现了InvocationHandler接口。代理对象的工作流程是这样的调用Mapper接口的任意方法时会触发MapperProxy.invoke()方法。invoke方法根据方法签名查找对应的MapperMethod对象如果缓存中没有则创建。MapperMethod根据方法类型SELECT/INSERT/UPDATE/DELETE执行相应操作。对于查询方法最终会调用Executor的query方法。这里有个性能优化点MapperMethod对象会被缓存在MapperProxy的methodCache中避免每次调用都重新解析方法签名。这个缓存用ConcurrentHashMap实现是线程安全的。6.2 MapperMethod的内部结构MapperMethod内部有两个核心属性SqlCommand和MethodSignature。SqlCommand封装了SQL语句的ID和类型。ID的格式是“接口全限定名.方法名”类型由方法上的注解或XML配置决定。如果同一个方法既有Select注解又有XML配置MyBatis会抛出异常因为无法确定用哪个。MethodSignature封装了方法的参数信息、返回类型、是否返回多结果等。它负责把Java方法的参数转换成MyBatis能识别的参数对象。对于单参数方法直接传参对于多参数方法会封装成Map或使用Param注解指定的名称。我遇到过一个问题Mapper接口方法有多个参数但没有加Param注解XML中直接用#{param1}、#{param2}引用结果在Java 8编译时因为参数名丢失导致BindingException。解决办法是加Param注解或者在编译时加上-parameters参数保留参数名。推荐前者更明确。6.3 代理对象的生命周期Mapper代理对象是绑定到SqlSession的它的生命周期不能超过Session。如果Session关闭了代理对象再调用方法就会抛出ExecutorException。在Spring集成环境下Mapper代理对象的生命周期由Spring管理。每次注入的Mapper接口实际上是一个代理的代理Spring创建了一个代理内部再通过SqlSessionTemplate获取真正的Mapper代理。这种双重代理机制让开发者不需要手动管理Session但也增加了理解成本。提示不要在类的成员变量中保存Mapper代理对象除非你能确保它的生命周期与类的生命周期一致。在Web应用中通常每次请求注入一次即可。7. 第五步查询执行的完整链路7.1 从Mapper方法到SQL执行一次完整的查询调用从Mapper接口方法开始到返回结果大致经过以下环节MapperProxy.invoke()拦截方法调用找到对应的MapperMethod。MapperMethod.execute()根据SqlCommand的类型调用SqlSession的selectOne、selectList等方法。DefaultSqlSession.selectList()从Configuration中获取MappedStatement然后调用Executor的query方法。Executor.query()先查一级缓存命中则返回未命中则查二级缓存再未命中则查数据库。StatementHandler创建Statement对象设置参数执行SQL。ResultSetHandler处理结果集映射为Java对象。返回结果逐层返回最终由MapperMethod处理返回类型单个对象、List、Map等。这条链路里Executor是最核心的组件它负责缓存管理和查询调度。StatementHandler负责与JDBC打交道ResultSetHandler负责结果映射。7.2 一级缓存和二级缓存的查询顺序查询时的缓存查找顺序是一级缓存 → 二级缓存 → 数据库。一级缓存是Session级别的用PerpetualCache实现key由statementId、参数、SQL、分页信息等组成。二级缓存是Namespace级别的需要显式配置cache标签才会生效。二级缓存的key与一级缓存类似但作用域更大可以跨Session共享。这里有个细节二级缓存的数据在Session提交或回滚时才会写入。也就是说在同一个Session中即使配置了二级缓存查询结果也不会立即进入二级缓存要等事务提交后才行。这个设计是为了保证缓存数据的一致性。7.3 参数映射与结果映射参数映射由ParameterHandler负责它把Java对象的值设置到PreparedStatement的占位符上。对于简单类型直接设置对于复杂对象通过OGNL表达式或反射获取属性值。结果映射由ResultSetHandler负责它根据ResultMap的定义把ResultSet的列映射到Java对象的属性上。如果没有配置ResultMapMyBatis会自动根据列名和属性名进行映射开启mapUnderscoreToCamelCase后支持下划线转驼峰。我踩过的一个坑查询返回的列名有重复时MyBatis只会映射第一个匹配的列。比如关联查询中两张表都有id列如果不加别名映射结果可能不符合预期。解决办法是在SQL中给列加别名或者在ResultMap中明确指定column属性。7.4 实操记录一次完整的查询链路追踪为了更直观地理解这条链路我写了一个简单的示例在关键节点打上日志观察执行顺序// 1. 构建Factory SqlSessionFactory factory new SqlSessionFactoryBuilder() .build(Resources.getResourceAsStream(mybatis-config.xml)); // 2. 打开Session try (SqlSession session factory.openSession()) { // 3. 获取Mapper代理 UserMapper mapper session.getMapper(UserMapper.class); // 4. 执行查询 User user mapper.selectById(1L); System.out.println(第一次查询 user); // 5. 再次查询观察一级缓存 User user2 mapper.selectById(1L); System.out.println(第二次查询 user2); // 6. 提交事务 session.commit(); }运行这段代码观察日志输出可以看到第二次查询没有发出SQL说明一级缓存生效了。如果在两次查询之间调用session.clearCache()第二次查询会重新发出SQL。这个实验虽然简单但能帮助理解Session、Mapper、Executor之间的协作关系。建议每个刚接触MyBatis源码的开发者都动手跑一遍。8. 常见问题与排查技巧实录8.1 问题速查表在实际开发和运维中与MyBatis链路相关的问题主要集中在以下几个方面。我整理了一个速查表方便快速定位现象可能原因排查方向查询结果与数据库不一致一级/二级缓存未失效检查Session生命周期、clearCache调用连接池耗尽Session未关闭检查try-with-resources、Spring事务配置批量插入性能差ExecutorType不是BATCH检查openSession参数或Spring配置参数绑定失败多参数未加Param检查Mapper方法签名和XML引用结果映射为null列名与属性名不匹配检查mapUnderscoreToCamelCase和ResultMap事务不回滚异常被吞或事务管理器配置错误检查异常类型和Transactional配置8.2 独家避坑技巧技巧一Session一定要用try-with-resources。我见过太多因为忘记关闭Session导致连接泄漏的案例。用try-with-resources可以确保Session在方法结束时自动关闭即使发生异常也不会泄漏。技巧二在Spring环境下不要手动openSession。Spring的SqlSessionTemplate已经帮你管理了Session的创建和关闭手动openSession会绕过Spring的事务管理导致事务不一致。技巧三批量操作时注意flush时机。BATCH模式的Executor会把SQL攒在内存中直到调用flushStatements或Session关闭时才批量发送。如果攒的SQL太多可能导致内存溢出。建议每500条左右flush一次。技巧四二级缓存慎用。二级缓存虽然能提升性能但一致性维护成本很高。如果多个Namespace共享同一张表或者有跨表的关联查询二级缓存很容易出现脏数据。我的建议是除非有明确的性能需求和一致性方案否则不要开启二级缓存。技巧五Mapper方法参数超过3个时考虑封装成对象。虽然可以用Param注解但参数太多时方法签名会很长可读性差。封装成DTO对象既清晰又方便扩展。8.3 一个真实的排查案例回到开头提到的那个压测问题。最终定位到原因是某个Service方法中手动获取了SqlSession但在异常分支中没有关闭。压测时异常频繁触发导致大量Session泄漏连接池被耗尽。修复方案很简单把手动获取Session的代码改成try-with-resources并确保异常时也能关闭。修复后压测通过响应时间恢复正常。这个案例给我的教训是MyBatis的Session管理必须严格遵循“谁创建谁关闭”的原则。在Spring环境下尽量交给Spring管理在非Spring环境下用try-with-resources确保关闭。不要依赖GC来回收Session因为GC不会主动关闭Connection。9. 关于Executor的补充说明9.1 三种Executor的区别MyBatis提供了三种Executor实现它们的行为差异直接影响性能SimpleExecutor每次执行SQL都创建新的Statement用完即关。适合大多数场景。ReuseExecutor复用Statement以SQL为key缓存Statement。适合相同SQL频繁执行的场景。BatchExecutor批量执行SQL把多条SQL攒在一起发送。适合批量插入、更新场景。选择哪种Executor取决于你的业务特征。如果大部分是单条查询用SimpleExecutor如果有大量重复SQL可以考虑ReuseExecutor如果是批量写入用BatchExecutor。9.2 Executor的包装器MyBatis的Executor用了装饰器模式在基础Executor之上包装了CachingExecutor二级缓存和LazyLoader延迟加载。所以实际创建的Executor对象是一个包装后的对象调用链是CachingExecutor → BaseExecutor → 具体的Executor实现。理解这个包装结构对于排查缓存和延迟加载相关的问题很有帮助。比如当你发现二级缓存没有生效时可以检查CachingExecutor是否被正确包装。9.3 自定义Executor的扩展点如果MyBatis内置的Executor不能满足需求可以通过实现Executor接口或继承BaseExecutor来扩展。常见的扩展场景包括SQL执行监控、慢查询日志、分库分表路由等。自定义Executor需要在Configuration中注册通常通过Intercepts注解或插件机制实现。插件机制是MyBatis提供的更灵活的扩展方式可以拦截Executor、StatementHandler、ParameterHandler、ResultSetHandler四个组件的任意方法。我在一个项目中用插件机制实现了SQL执行时间监控拦截Executor的query方法记录执行时间超过阈值的SQL。这个方案对业务代码零侵入非常实用。10. 关于Mapper代理的深入理解10.1 为什么Mapper接口不需要实现类这是很多初学者最困惑的地方Mapper接口没有实现类为什么能直接调用答案就在JDK动态代理。MyBatis在运行时为Mapper接口生成了一个代理对象所有方法调用都被MapperProxy拦截然后根据方法签名找到对应的SQL并执行。这种设计的巧妙之处在于开发者只需要定义接口不需要写实现类SQL配置在XML或注解中。接口和SQL的绑定关系由MyBatis在启动时建立运行时通过代理对象调用。10.2 Mapper注册的两种方式Mapper接口的注册有两种方式XML配置和注解扫描。XML方式是在mybatis-config.xml中添加mapper resource.../或mapper class.../。注解方式是在接口上添加Mapper注解或者在Spring配置中指定basePackage进行扫描。两种方式各有优劣XML方式更灵活支持动态SQL和复杂映射注解方式更简洁适合简单的CRUD。实际项目中通常会混用简单查询用注解复杂查询用XML。10.3 Mapper代理的线程安全问题Mapper代理对象本身是线程安全的吗严格来说不是。因为Mapper代理对象绑定了SqlSession而SqlSession不是线程安全的。如果多个线程共享同一个Mapper代理对象实际上就是共享了同一个SqlSession会导致事务混乱和数据不一致。在Spring环境下每次注入的Mapper代理对象实际上是不同的实例通过SqlSessionTemplate动态获取所以看起来是线程安全的。但本质上线程安全的是Spring的管理机制而不是Mapper代理对象本身。注意不要在多线程之间传递Mapper代理对象或SqlSession。每个线程应该独立获取自己的Session和Mapper。11. 从源码角度看查询的完整调用栈11.1 调用栈的层次结构一次Mapper查询的完整调用栈从上层到下层大致是这样的MapperProxy.invoke() → MapperMethod.execute() → DefaultSqlSession.selectOne() / selectList() → CachingExecutor.query() → BaseExecutor.query() → SimpleExecutor.doQuery() → StatementHandler.query() → ResultSetHandler.handleResultSets()每一层都有明确的职责MapperProxy负责方法拦截MapperMethod负责命令解析SqlSession负责会话管理Executor负责缓存和调度StatementHandler负责JDBC交互ResultSetHandler负责结果映射。11.2 关键源码片段解读在MapperMethod.execute()方法中有一个switch语句根据SqlCommand的类型分发到不同的SqlSession方法。对于SELECT类型会根据返回类型决定调用selectOne还是selectList。这个逻辑决定了Mapper方法的返回值处理方式。在BaseExecutor.query()方法中会先查一级缓存如果命中则直接返回否则调用queryFromDatabase()再查二级缓存最后查数据库。查询数据库后结果会写入一级缓存。在SimpleExecutor.doQuery()方法中会创建StatementHandler然后调用它的query方法。StatementHandler内部会创建PreparedStatement设置参数执行SQL最后交给ResultSetHandler处理结果。11.3 源码阅读的建议阅读MyBatis源码时建议从MapperProxy开始沿着调用链一路往下看。不要一开始就陷入细节先把握整体流程再深入关键环节。重点关注缓存查找逻辑、参数映射逻辑、结果映射逻辑。这三个环节是问题高发区也是面试常问的点。我个人的习惯是在关键方法上打断点用调试模式跑一个简单的查询观察每一步的变量变化。这种方式比单纯看源码更直观也更容易记住。12. 性能优化的几个切入点12.1 减少Session创建开销Session创建的主要开销在于获取Connection。如果使用连接池这个开销可以降到很低。但如果连接池配置不当比如最大连接数太小Session创建就会成为瓶颈。优化建议合理配置连接池的初始连接数、最大连接数、最小空闲连接数。根据压测结果调整不要凭感觉设置。12.2 合理使用缓存一级缓存默认开启不需要额外配置。二级缓存需要显式配置且要注意一致性。如果业务允许短暂的数据不一致可以考虑开启二级缓存如果要求强一致建议关闭。对于查询频繁但更新很少的数据如字典表二级缓存效果很好。对于更新频繁的数据二级缓存反而会增加维护成本。12.3 批量操作的正确姿势批量插入时使用ExecutorType.BATCH可以显著提升性能。但要注意BATCH模式下SQL不会立即执行而是攒到一定数量后批量发送。如果攒的太多可能导致内存溢出如果攒的太少批量效果不明显。我的经验值是每500条flush一次。具体数值需要根据SQL的大小和可用内存调整。可以通过session.flushStatements()手动触发flush。12.4 结果映射的优化结果映射的开销主要在于反射和类型转换。如果查询返回大量数据结果映射可能成为瓶颈。优化方式包括使用ResultMap明确指定映射关系避免自动映射的开销使用基本类型而不是包装类型避免在结果映射中使用复杂的OGNL表达式。另外如果查询结果不需要映射为Java对象可以直接返回Map或使用MapKey注解减少映射开销。13. 一些容易忽略的细节13.1 Configuration的全局性Configuration对象是全局唯一的它持有所有Mapper映射、缓存、类型处理器等。这意味着如果你在运行时动态修改Configuration会影响所有Session。所以不要在运行时修改Configuration所有配置在启动时确定。13.2 TypeHandler的注册时机TypeHandler负责Java类型和JDBC类型之间的转换。MyBatis内置了很多TypeHandler也支持自定义。自定义TypeHandler需要在Configuration中注册注册时机是在Builder阶段。如果注册晚了可能不生效。13.3 插件的执行顺序MyBatis插件可以拦截Executor、StatementHandler、ParameterHandler、ResultSetHandler。多个插件同时存在时执行顺序由配置顺序决定。后配置的插件先执行类似栈的结构。理解这个顺序对于排查插件相关的问题很重要。13.4 日志配置的影响MyBatis支持多种日志实现SLF4J、Log4j、Log4j2、JDK Logging等。日志级别设为DEBUG时会打印SQL和参数方便调试。但在生产环境DEBUG级别会带来性能开销建议设为INFO或WARN。我见过一个案例生产环境误开了DEBUG日志导致大量SQL日志写入磁盘IO飙升响应变慢。后来把日志级别调回INFO问题解决。所以生产环境的日志级别一定要检查。14. 关于版本差异的提醒不同版本的MyBatis在内部实现上有一些差异。比如MyBatis 3.5.x相比3.4.x在Executor和缓存实现上有调整。如果你在阅读源码或排查问题时发现行为和文档不一致先确认版本。另外MyBatis和MyBatis-Spring的版本需要匹配。版本不匹配可能导致事务管理异常或Mapper注入失败。建议使用官方推荐的版本组合不要随意混搭。我在一个项目中遇到过MyBatis 3.5.6和MyBatis-Spring 2.0.6不兼容的问题表现为事务不回滚。升级MyBatis-Spring到2.0.7后问题解决。所以版本兼容性检查是排查问题的第一步。15. 最后分享几个实用技巧技巧一用MapperScan替代逐个注册。在Spring Boot项目中用MapperScan(com.example.mapper)可以批量扫描Mapper接口省去逐个配置的麻烦。技巧二用Options注解控制主键回填。插入数据后需要获取自增主键时用Options(useGeneratedKeys true, keyProperty id)可以自动回填不需要手动查询。技巧三用SelectProvider实现动态SQL。对于复杂的动态SQL注解方式写起来很痛苦可以用SelectProvider指定一个Provider类在Java代码中拼接SQL。这种方式比XML更灵活但可读性差一些。技巧四用session.selectCursor()处理大结果集。如果查询返回百万级数据一次性加载到内存会OOM。用Cursor可以流式处理逐条读取内存占用恒定。技巧五用Interceptor实现SQL监控。通过拦截Executor的query方法可以记录每条SQL的执行时间、参数、返回行数等信息。这对于性能分析和慢查询排查非常有用。这些技巧都是我在实际项目中用过并且觉得有效的你可以根据自己的场景选择使用。MyBatis的灵活性很高不同的用法适合不同的场景没有绝对的好坏关键是理解原理做出适合自己项目的选择。

相关新闻

爱立信Moshell实战手册:MOM/PM/Log命令式运维指南

爱立信Moshell实战手册:MOM/PM/Log命令式运维指南

简介:本资源是一份面向通信网络运维工程师、爱立信设备初/中级维护人员的4G/5G指令速查手册,聚焦实际网管操作场景,系统梳理MOM、MO-read、MO-write、PM、Log、Sw Management、Inventory/COLI、Transport Network及LTE/NR九大类核心命令。内容…

2026/10/12 9:34:10 阅读更多 →
C#线程池从入门到调优:复用、调度机制与避坑实战

C#线程池从入门到调优:复用、调度机制与避坑实战

从new Thread到慢慢拥抱线程池,几乎是每个 C# 并发开发者的必经之路。前几篇系列文章里我们亲手创建线程、管理线程、处理退出与协作,这些底层直觉非常宝贵;可一旦任务量上到几十、几百甚至几千,手工线程的创建开销、不可控的并发…

2026/10/12 9:33:09 阅读更多 →
KVM虚拟化平台搭建实战:从硬件原理到性能调优

KVM虚拟化平台搭建实战:从硬件原理到性能调优

前阵子帮一个团队搭测试环境,一台四核物理机既要跑业务接口、又要跑数据库,晚上还有定时任务。一开始图省事,所有服务直接装在宿主机上,结果某个报表任务把 CPU 吃满,接口超时一片,数据库也跟着遭殃。后来又…

2026/10/12 9:33:09 阅读更多 →

最新新闻

完美立方问题详解:循环边界、枚举优化与浮点比较避坑指南

完美立方问题详解:循环边界、枚举优化与浮点比较避坑指南

前阵子刷某高校《计算机概论和程序设计基础》这门MOOC课程,循环练习那一节配了一道非常经典的题——完美立方。第一眼看过去真不难:枚举四个整数,判断立方等式成立就行。可真正动手才发现,题面里那些不起眼的限制条件,…

2026/10/12 10:22:09 阅读更多 →
技术博客写作第一步:补全项目标题、关键词与摘要描述

技术博客写作第一步:补全项目标题、关键词与摘要描述

看起来这次提交里核心素材是空的:项目标题、项目正文、关键词、摘要描述都没有填写。要给你写一篇不偏题、能直接落地复现的深度博文,我得先知道你想拆解的是什么项目、解决什么问题。麻烦把下面四项补给我,哪怕只是零散的几句话也可以&#…

2026/10/12 10:22:09 阅读更多 →
Git+Repo+Gerrit代码服务器搭建实战:多仓库协同与代码评审

Git+Repo+Gerrit代码服务器搭建实战:多仓库协同与代码评审

简介:这份资源面向需要搭建代码评审服务器的开发与运维人员,围绕 Git、Repo 与 Gerrit 三大工具,提供从零构建完整代码服务器的实践指引。内容涵盖服务器端与客户端角色划分、Git 与 Gitosis 的安装配置、管理员账户与公钥生成、gitosis.conf…

2026/10/12 10:22:09 阅读更多 →
Python闭包与装饰器:从原理到实战的完整指南

Python闭包与装饰器:从原理到实战的完整指南

我还记得第一次在代码里看到 login_required 这种写法时,盯着那一行呆了好几秒——Python 里什么时候多了个 语法?后来才知道,那叫装饰器。而真正让我理解装饰器为什么是这样写的,反而是在搞明白 闭包 之后。闭包和装饰器&am…

2026/10/12 10:22:09 阅读更多 →
大规模MIMO MATLAB仿真闭环验证框架:从信道建模到预编码

大规模MIMO MATLAB仿真闭环验证框架:从信道建模到预编码

简介:本资源是一套面向通信工程专业学生与无线通信初学者的大规模MIMO系统级MATLAB仿真程序,聚焦多天线技术原理验证与性能分析,有效支撑课程设计、毕设仿真及科研入门。压缩包共11个文件,含7个核心.m脚本(如qpskmo.m信…

2026/10/12 10:22:09 阅读更多 →
Git+Repo+Gerrit代码评审服务器搭建实战:多仓库协作与权限管理

Git+Repo+Gerrit代码评审服务器搭建实战:多仓库协作与权限管理

简介:本资源是一份面向运维工程师、后端开发及代码管理初学者的代码评审服务器搭建文档,围绕 Git、Repo 与 Gerrit 三大工具,讲解如何从零构建一套功能完整的代码服务器,解决团队代码托管、多仓库统一管理与代码评审流程落地的问题…

2026/10/12 10:21:08 阅读更多 →

日新闻

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

复古胶片颗粒感噪点合成器:Canvas ImageData 像素高斯杂色注入算法

在数码相机、高清显示屏与现代矢量图形技术高度发达的今天,画面可以做到绝对的锐利、平滑与无瑕。然而,当一张秋日手账插画或拍立得照片过于“平整无瑕”时,往往会散发出一种冰冷生硬的“数码塑料感(Digital Plasticity&#xff0…

2026/10/12 0:00:59 阅读更多 →
活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

活字印刷古籍线装排版:Canvas 竖排文字与栏线自适应算法

在现代网页与移动端设计中,横排(Horizontal Layout)早已经成为了绝对的主流。然而,当我们翻开泛黄的线装古籍、宋版木刻诗集,或是欣赏一张茶道雅集的手写便签时,那种**自上而下纵向书写、自右向左逐列铺展&…

2026/10/12 0:00:59 阅读更多 →
周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

周日晚间的“精神松绑减震器”:无压力情绪倾倒箱与温和轻声陪伴

每到周日的晚上八点到十点,很多人心里都会悄悄亮起一盏警示灯。 在心理学上,这种现象有一个专门的称谓——“周日夜晚焦虑症(Sunday Scaries)”。明天又是周一,闹钟又要重新在七点响彻卧房;脑海里仿佛有一个…

2026/10/12 0:00:59 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/11 10:45:37 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/11 14:36:53 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/11 14:36:54 阅读更多 →