尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

Spring Boot + MyBatis 黄金搭档:从分页配置到源码原理实战详解

发布时间:2026/9/24 19:02:13

资讯中心
01
ARTICLE

Spring Boot + MyBatis 黄金搭档:从分页配置到源码原理实战详解

Spring Boot + MyBatis 黄金搭档:从分页配置到源码原理实战详解
做后端开发这些年Spring Boot和MyBatis这对组合在Java企业级项目里的出场率有多高不用我多说。打开GitHub随便翻几个Java项目十有七八是这套技术栈翻招聘网站岗位要求里也常驻这两个关键词。但很多人其实是一边用一边懵分页插件到底该怎么配才不踩坑、Mapper接口和XML的映射关系底层的原理是什么、缓存哪一级该开哪一级不能开、项目跑起来IDEA里看不到端口号又是怎么回事。这篇文章我想系统梳理一下我在实际项目里使用Spring Boot与MyBatis的经验包括方案选型、配置细节、实战代码、常见坑点和面试高频题争取让刚接触这套组合的兄弟少走弯路也让有经验的同行能查漏补缺。这套组合的核心价值其实就一句话Spring Boot用自动配置把项目的“骨架”搭好MyBatis用灵活的SQL映射把数据访问层的手脚放开。你不需要像早期SSH那样写一堆XML配置也不用为了一个简单的列表查询去拼JPA那套复杂的Criteria API。上手快、SQL可控、优化空间大这是它能长期占据企业级开发主流位置的根本原因。我不会在这篇文章里把官方文档翻译一遍咱就实打实地讲实操把那些文档里不会写、但你在项目里一定会碰到的东西聊透。1. 内容整体设计与思路拆解1.1 为什么Spring Boot和MyBatis能成为黄金搭档先说Spring Boot。它的核心思想是“约定优于配置”把以前Spring MVC XML那一大堆配置全部收编进自动配置机制里。你引入一个spring-boot-starter-web内嵌的Tomcat、DispatcherServlet、Jackson全都给你准备好引入mybatis-spring-boot-starterSqlSessionFactory、DataSource、SqlSessionTemplate自动注册。这对开发效率的提升是革命性的。再说MyBatis。它本质上是一个“半自动ORM框架”。所谓半自动就是SQL我们自己写结果集的映射由框架帮我们做。这和Hibernate这类全自动框架有本质区别全自动框架让你不写SQL但SQL是自动生成的遇到复杂查询、多表关联、分页优化就抓瞎MyBatis则把SQL控制权完全交回开发者手里你能精确控制每一条SQL长什么样子。这两者结合在一起意味着你可以用最少的配置搭建出一个项目骨架同时在数据访问层拥有最大的灵活度。在业务逻辑复杂、SQL优化要求高的企业级系统比如工时管理、校园预约这类带大量统计报表的系统里这一优势极其明显。1.2 原生MyBatis和MyBatis-Plus到底怎么选这是近年讨论最多的问题。MyBatis-Plus是在MyBatis基础上做的增强提供BaseMapper通用CRUD、条件构造器、分页插件、代码生成器等等。我的观点很直接新项目、业务以单表操作为主、追求开发效率选MyBatis-Plus。它的LambdaQueryWrapper写起来确实爽泛型安全、还能少写大量XML。有历史包袱的项目、SQL复杂度极高、团队对原生MyBatis已经非常熟悉继续用原生MyBatis也完全没问题。Plus并没有替换MyBatis它只是在上面做增强你完全可以只把Plus当MyBatis用只用到它的BaseMapper。我在实际项目里通常的做法是集成MyBatis-Plus但复杂查询依然写XML只有简单CRUD交给BaseMapper。这样既享受了Plus带来的开发效率也不丢SQL掌控力。1.3 技术选型时要考虑的环境因素这里特别提醒一个容易忽略的点JDK版本和Spring Boot版本之间的兼容关系。Java 21配合Spring Boot 3.5已经可以启用虚拟线程Virtual Threads这是JDK 21引入的正式特性能大幅提升高并发场景下的吞吐量。如果你是新项目我强烈建议直接上这个组合。启用方式非常简单在application.yaml里加一行配置spring: threads: virtual: enabled: true不过要注意MyBatis自身和数据库连接池HikariCP都要兼容虚拟线程。好在Java 21和Spring Boot 3.5目前的主流版本都已经适配实测下来没有遇到线程相关的问题。但如果你的项目里用了比较老的连接池或者自定义线程池还是小心为妙。2. 核心细节解析与实操要点2.1 分页插件PageHelper和MyBatis-Plus分页的正确姿势分页是业务系统里逃不掉的需求而MyBatis本身并没有内置分页能力需要我们接插件。市面上最常用的分页插件有两套PageHelper和MyBatis-Plus的分页插件。PageHelper的用法是拦截器机制。它在Executor层拦截SQL自动拼接LIMIT语句。但PageHelper有两个坑必须注意必须在使用PageHelper.startPage()之后、紧跟其后的第一条SQL语句执行之前设置分页参数。如果你在中间穿插了其他查询分页参数会作用到错误的SQL上。多数据源或者SQL里本身带LIMIT时PageHelper可能出现分页失效或者SQL被二次拼接的错误。我个人现在的项目大多用MyBatis-Plus它的分页插件配合Page对象使用代码清爽很多。配置方式如下Configuration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }查询侧代码PageUserVO page new Page(current, size); LambdaQueryWrapperUser wrapper new LambdaQueryWrapper(); wrapper.eq(User::getStatus, 1).orderByDesc(User::getCreateTime); PageUserVO result userMapper.selectPage(page, wrapper);分页插件的底层实现其实就是拦截Executor.query()方法拿到Page参数之后自动改写SQL。理解这一点你就知道为什么分页插件必须配置在拦截器链里也就能解释为什么自定义拦截器的顺序会影响分页是否生效。2.2 MyBatis缓存哪一级能开哪一级建议关掉MyBatis的缓存机制分两级一级缓存是SqlSession级别的默认开启二级缓存是Mapper级别的需要手动配置。一级缓存大家天天用但未必注意到。同一个SqlSession里执行两次完全相同的查询第二次不会真正访问数据库而是直接返回缓存结果。但在Spring Boot集成MyBatis后每次Mapper方法调用如果不在同一个事务里SqlSession是会被重新创建、用完即关的。所以一级缓存在Spring环境里经常“形同虚设”只有在同一个事务内连续执行相同查询时才会命中缓存。二级缓存是跨SqlSession的默认关闭。配置方式是在Mapper XML里加上cache evictionLRU flushInterval60000 size512 readOnlytrue/但我建议大多数项目直接关闭二级缓存或者干脆不配置。原因有三二级缓存的粒度是namespace级别跨表查询时容易出脏数据。比如查关联表时只刷新了主表的缓存从表数据变了缓存不知道。在分布式架构下二级缓存需要额外的同步策略否则每台机器各缓存各的数据一致性很容易出问题。遇到高并发情况下缓存失效引起的缓存击穿、穿透问题处理起来成本远高于你用Redis做专门缓存。所以在实际项目里我几乎不用MyBatis自带的二级缓存要么直接查询数据库要么用Redis做业务级缓存。MyBatis的一级缓存保持默认开启就行但如果你在一个事务里多次查询同一张表并且担心缓存影响实时性可以在Mapper方法上加flushCachetrue强制刷新。2.3 打印SQL日志不配置好排查问题效率减半开发阶段打印SQL日志属于刚需。MyBatis打印SQL的机制是日志输出SQL语句和执行参数底层依赖Java的日志体系SLF4J。常见配置是把日志级别调到DEBUG且日志记录器指向Mapper接口所在的包logging: level: com.example.project.mapper: debug这样设置之后控制台会输出类似这样的日志 Preparing: SELECT * FROM user WHERE id ? Parameters: 1(Integer) Total: 1但有一点容易忽略如果配置的是整个com.example.project根包为DEBUG日志会非常嘈杂把Spring框架本身的调试日志也打印出来反而不利于定位问题。另外生产环境记得把Mapper包的日志级别调回INFO否则大量SQL日志会拖累IO性能。2.4 Mapper接口和XML放在同一个目录下的配置方案这是一个在Spring Boot项目里特别常见的问题。Spring Boot的默认资源路径是src/main/resources所以很多人把Mapper XML丢在resources/mapper/目录下然后在application.yaml里配置mybatis: mapper-locations: classpath:mapper/*.xml但如果是用MyBatis-Plus并且你希望XML和Mapper接口放在同一个包路径下比如都在com.example.project.mapper那需要在pom.xml里把Mapper目录也作为资源目录打包build resources resource directorysrc/main/java/directory includes include**/*.xml/include /includes /resource /resources /build然后在application.yaml里指定mybatis-plus: mapper-locations: classpath*:com/example/project/mapper/**/*.xml注意那个classpath*:意思是扫描所有Jar包里的匹配路径。如果你只有一个项目classpath:也行但为了稳妥我建议写classpath*:。这个配置折腾了我一下午才弄明白最初一直报Invalid bound statement原因就是打包时XML没被编译进classes目录。3. 实操过程与核心环节实现3.1 从零搭建一个Spring Boot MyBatis的接口光说不练假把式我直接带大家走一遍完整的创建流程。假设我们要做一个校园讲座预约系统里的“讲座分页查询”接口需求是前端传pageNum和pageSize后端返回讲座列表支持按标题模糊搜索和按讲座时间排序。这个需求覆盖了CRUD、分页、条件查询很典型。第一步创建Spring Boot项目。我习惯用Spring Initializr直接生成选依赖的时候勾选Spring Web、MyBatis Framework、MySQL Driver、Lombok。如果你要用MyBatis-Plus就引入dependency groupIdcom.baomidou/groupId artifactIdmybatis-plus-spring-boot3-starter/artifactId version3.5.7/version /dependency版本号这里注意一下Spring Boot 3.x必须用mybatis-plus-spring-boot3-starter老版的mybatis-plus-boot-starter是给Spring Boot 2用的混用会直接启动报错。第二步配置数据源和MyBatisspring: datasource: url: jdbc:mysql://localhost:3306/lecture_system?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver mybatis-plus: mapper-locations: classpath*:mapper/**/*.xml configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImplmap-underscore-to-camel-case这个配置非常重要。数据库字段习惯用lecture_title这种下划线风格Java实体类用lectureTitle驼峰风格不开启这个配置的话查询出来的结果是null。第三步建实体类和MapperData TableName(lecture) public class Lecture { TableId(type IdType.AUTO) private Long id; private String title; private String speaker; private LocalDateTime lectureTime; private Integer maxPeople; private Integer currentPeople; private Integer status; }public interface LectureMapper extends BaseMapperLecture { PageLecture selectLecturePage(PageLecture page, Param(title) String title); }第四步写XML里的联表分页查询。当查询需要关联另一个表比如讲座关联预约记录统计已预约人数时我会写XML因为SQL更直观select idselectLecturePage resultTypecom.example.project.entity.Lecture SELECT l.*, IFNULL(COUNT(r.id), 0) AS booked_count FROM lecture l LEFT JOIN reservation r ON r.lecture_id l.id AND r.status 1 where if testtitle ! null and title ! AND l.title LIKE CONCAT(%, #{title}, %) /if /where GROUP BY l.id ORDER BY l.lecture_time DESC /select第五步Service层的分页调用public PageLecture pageLectures(int pageNum, int pageSize, String title) { PageLecture page new Page(pageNum, pageSize); return lectureMapper.selectLecturePage(page, title); }这套流程走完一个能跑的分页查询接口就完成了。你对照着看Spring Boot把环境搭建的成本降到了最低MyBatis-Plus把简单CRUD的代码量降到了最低XML则保证复杂查询的SQL依然是完全可控的。3.2 自定义拦截器做数据权限和审计日志MyBatis的拦截器是面试里的高频考点也是实际项目中做数据权限、自动填充、SQL性能监控的利器。拦截器原理是基于JDK动态代理MyBatis允许你拦截四大核心组件的方法ParameterHandler处理参数ResultSetHandler处理结果集StatementHandler处理SQL语句Executor执行器负责整个SQL执行流程我举一个最实用的场景数据权限过滤。比如校园讲座系统里普通学生只能看到状态为“已发布”的讲座管理员才能看到所有记录。我可以通过拦截器在Executor层拦截SQL动态拼上AND status 1条件。Intercepts({ Signature(type StatementHandler.class, method prepare, args {Connection.class, Integer.class}) }) public class DataScopeInterceptor implements Interceptor { Override public Object intercept(Invocation invocation) throws Throwable { StatementHandler statementHandler (StatementHandler) invocation.getTarget(); BoundSql boundSql statementHandler.getBoundSql(); String sql boundSql.getSql(); String currentUserRole getCurrentUserRole(); if (STUDENT.equals(currentUserRole) !sql.contains(status 1)) { String newSql sql.replaceAll((?i)(order by.*|limit.*)?$, AND status 1 $1); // 通过反射修改BoundSql中的sql字段 Field sqlField BoundSql.class.getDeclaredField(sql); sqlField.setAccessible(true); sqlField.set(boundSql, newSql); } return invocation.proceed(); } }需要注意这个拦截器改SQL玩的是反射修改BoundSql里的私有字段非常依赖MyBatis内部实现升级版本有风险。生产环境建议只做查询类的改造并且做好充分的兼容性测试。拦截器要生效必须在MyBatis配置里注册Configuration public class MybatisConfig { Bean public DataScopeInterceptor dataScopeInterceptor() { return new DataScopeInterceptor(); } }如果你是MyBatis-Plus用户权限这块其实有更优雅的方案比如InterceptorIgnore注解加内置的DataPermissionInterceptor但自定义拦截器能帮你真正理解MyBatis插件机制的底层原理这个积累面试和排查问题时都很值。3.3 集成MinIO做文件存储企业项目里经常会遇到文件上传的需求比如校园讲座系统的讲师PPT、项目进度管理系统的附件。本地磁盘存文件有天然劣势扩容难、备份麻烦、多机部署时文件不同步。集成MinIO是一个轻量又合理的方案。MinIO是开源的分布式对象存储系统兼容S3协议。Spring Boot集成MinIO的步骤不复杂。先在pom.xml引入依赖dependency groupIdio.minio/groupId artifactIdminio/artifactId version8.5.10/version /dependency配置类Configuration public class MinioConfig { Bean public MinioClient minioClient(MinioProperties properties) { return MinioClient.builder() .endpoint(properties.getEndpoint()) .credentials(properties.getAccessKey(), properties.getSecretKey()) .build(); } }工具类的上传核心代码public String upload(MultipartFile file, String bucketName) { String objectName UUID.randomUUID() file.getOriginalFilename(); try { boolean exists minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build()); if (!exists) { minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build()); } minioClient.putObject(PutObjectArgs.builder() .bucket(bucketName) .object(objectName) .stream(file.getInputStream(), file.getSize(), -1) .contentType(file.getContentType()) .build()); } catch (Exception e) { throw new RuntimeException(文件上传失败, e); } return objectName; }这里有个小细节上传流参数partSize设为-1表示由MinIO自动分片但如果你用的文件很大建议自己设定分片大小能显著提升上传稳定性。3.4 引入工作流引擎Deer-Flow热词里有“基于Spring Boot服务接入工作流 :deer-flow”这样的检索词。确实很多企业级系统发展到一定阶段就会冒出审批流的需求项目工时审批、讲座申请审批、报销流程审批。这时候自己从零写一套工作流引擎成本太高、坑太多直接用现成的开源工作流引擎是明智的选择。Deer-Flow是国人在Flowable基础上精简封装的工作流引擎专为Spring Boot设计API更贴合国内开发者的使用习惯。集成起来比较轻量引入依赖后配置数据源即可dependency groupIdcom.deerflow/groupId artifactIddeer-flow-starter/artifactId version2.1.0/version /dependency工作流的表结构会自动建好核心API包括// 启动流程实例 runtimeService.startProcessInstanceByKey(leaveProcess, businessKey); // 完成任务 taskService.complete(taskId, variables); // 查询待办 taskService.createTaskQuery().taskAssignee(userId).list();不过要提醒的是工作流引擎的引入会显著增加系统的复杂度多实例、会签、驳回、超时自动处理这些功能都要仔细设计。不要一开始就追求把所有业务都塞进流程审批先从一个最简单的线性流程跑通再逐步加规则。3.5 Update执行慢的排查实战热词里有“mybatis update 执行慢”这我在项目里真实踩过。当时是一个更新任务进度的接口单次更新居然要几百毫秒。排查的顺序我建议是这样的第一步看是不是索引没走。用EXPLAIN看执行计划发现SQL里WHERE条件字段没有索引全表扫描。给字段加了索引后快了一些。第二步看是不是事务太大。接口方法上加了Transactional里面先查了一堆数据、又调了两个外部接口、最后才更新。整个事务期间数据库连接一直被占用锁也一直被持有并发一起来就更慢。拆事务就解决了。第三步看是不是在循环里更新。有段代码在for循环里调用了十几次Mapper的Update方法每次更新都独立提交一次事务这就是典型的1N问题。改成批量更新后性能直接起飞。public void batchUpdateStatus(ListLong ids, Integer status) { try (SqlSession sqlSession sqlSessionFactory.openSession(ExecutorType.BATCH)) { ProjectMapper mapper sqlSession.getMapper(ProjectMapper.class); for (Long id : ids) { mapper.updateStatus(id, status); } sqlSession.commit(); } }第四步看连接池。HikariCP默认maximumPoolSize是10如果应用是单实例且并发高连接池被打满也会导致获取连接等待时间过长。这个可以好观察HikariPool的活跃连接数来确认。4. 源码复盘与面试高频题4.1 从哪几个角度读MyBatis源码效率最高我见过很多朋友下载了MyBatis源码却看不下去因为类太多、太绕。我的经验是从一次查询的完整链路入手读不要从头到尾啃。核心链路是这样的用户调用Mapper接口方法 - 经过MapperProxy动态代理 - 调用SqlSession的selectOne- 委托给Executor-Executor通过StatementHandler预处理SQL - 参数通过ParameterHandler绑定 - SQL执行 - 结果集通过ResultSetHandler映射成Java对象。你抓住这条线把十个核心类的源码读一遍MyBatis的设计思路就清楚了。推荐阅读顺序MapperProxy理解为什么Mapper接口不需要实现类SqlSessionTemplate理解它在线程安全方面做了什么BaseExecutor与CacheExecutor理解一级缓存和二级缓存如何协作StatementHandler理解PreparedStatement的预编译和参数绑定DefaultResultSetHandler理解自动映射和resultMap的构建逻辑读完源码最大的收获是你写代码时不再是“试出来的”而是能预判框架行为。比如为什么XML里的resultType要求实体类必须有默认构造方法为什么自动映射遇到嵌套对象会失败这些问题源码里都有答案。4.2 面试常考的几个点怎么答结合面试题热词我把几个高频问题整理一下给出一个“答出来有亮点”的思路第一题MyBatis的#{}和${}有什么区别谁都会说#{}是预编译占位符${}是字符串拼接。但面试官想听的是底层原因#{}最终会通过JDBC的PreparedStatement的setString等方法传入参数能有效防止SQL注入${}是直接把内容拼接进SQL再编译存在注入风险。加分回答是${}通常只在需要动态传表名、列名这类无法用占位符的场景里使用且必须白名单校验。第二题Mapper接口没有实现类Spring是怎么把它注入进来的答案是MapperScan扫描接口后Spring容器会使用FactoryBean注册MapperFactoryBean它内部通过JDK动态代理生成Mapper接口的代理对象代理对象把方法调用转发给SqlSession执行。第三题MyBatis的一级缓存为什么在Spring环境里经常不生效原因是Spring集成MyBatis后默认SqlSession的生命周期是跟着方法走的方法执行完就关闭了一级缓存是SqlSession级别的缓存SqlSession销毁缓存也就没了。如果是同事务内拿到的是同一个SqlSession缓存才会生效。第四题自定义MyBatis拦截器可以拦截哪些对象四大对象Executor、StatementHandler、ParameterHandler、ResultSetHandler。注意只能通过Signature指定拦截方法不要拦截不该拦截的方法否则可能干扰框架内部流程。第五题MyBatis-Plus的updateById会不会把字段更新为null默认策略是FieldStrategy.NOT_NULL也就是非空字段才更新null字段不会出现在SQL里。如果你确实想把一个字段置为null需要加TableField(strategy FieldStrategy.IGNORED)或者用UpdateWrapper的set方法。4.3 MyBatis-Plus逻辑删除和乐观锁MyBatis-Plus有两个非常好用的内置功能建议每个项目都用起来。逻辑删除企业系统里对数据安全敏感物理删数据是红线。配置方式是在实体类字段上加上TableLogic private Integer deleted;全局配置里设置逻辑删除字段的全局值mybatis-plus: global-config: db-config: logic-delete-field: deleted logic-delete-value: 1 logic-not-delete-value: 0启动后所有BaseMapper的deleteById都会自动变成UPDATE ... SET deleted 1 WHERE id ?查询时也会自动带上AND deleted 0。这个功能能避免很多手写漏加判断的低级错误。乐观锁并发更新场景下防止丢失更新。在实体类字段上加Version private Integer version;然后配置乐观锁插件interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());执行updateById时MyBatis-Plus会自动在SQL里拼上AND version #{旧版本}更新成功后把version加1。这样并发更新的两个请求只会有一个成功另一个更新0行你再根据更新行数决定是重试还是报错。5. 常见问题与排查技巧实录5.1 IDEA启动Spring Boot项目不显示端口号这个问题在“idea mybatis”、“idea 启动spring boot项目不显示端口号”这些热词里出现频率很高。启动日志里没有Tomcat started on port(s): 8080这行通常有几个原因项目其实没有加载Spring Boot主类而是以普通Java应用方式启动了某个测试类或工具类。检查一下IDEA的Run Configuration里的Main class配置。日志级别把org.springframework.boot.web.embedded.tomcat的INFO日志过滤掉了。查日志配置。应用里自定义了WebServerFactoryCustomizer修改了Tomcat的端口但没有打日志这种情况下虽然不打印端口但实际服务是能访问的。最常见的原因其实是第一种。解决方案是在IDEA里右键点击标注了SpringBootApplication的主类选择“Run”而不是右键单个文件运行。5.2 虚拟线程配置了但没生效Spring Boot 3.5启用虚拟线程很简单但有朋友配置了spring.threads.virtual.enabledtrue后发现线程名还是http-nio-8080-exec-*没有变成virtual-*。这时要检查几个点确认Tomcat线程池是否真的在用虚拟线程。可以打印Thread.currentThread().isVirtual()看看结果。确认有没有自定义TomcatProtocolHandlerCustomizer覆盖了默认配置。如果项目引入了旧版的spring-boot-starter-web且被覆盖了版本也可能导致配置不生效。虚拟线程适合IO密集型场景在数据库操作、HTTP调用这类长时间等待的场景下提升尤为明显。但注意虚拟线程不适合CPU密集型的计算场景比如大量复杂的加解密运算强行用反而可能由于线程迁移导致性能下降。5.3 GraalVM原生镜像打包Spring Boot 3.x开始正式支持GraalVM原生镜像能把Java应用编译成独立可执行文件启动速度从秒级降到毫秒级内存占用也大幅降低。热词里有人问“Spring Boot打包插件可以换成GraalVM吗”。答案是可以但要有心理准备。GraalVM原生镜像对反射、动态代理的支持比较弱而MyBatis的核心机制恰恰依赖反射和动态代理——Mapper接口的代理、结果集的映射、属性的反射赋值都是GraalVM的“重点监管对象”。我在测试项目里试过一次需要额外提供reflect-config.json、proxy-config.json这些元数据文件把MyBatis需要的所有反射信息都列进去。集成原生的MyBatis时要做很多适配如果用了MyBatis-Plus它内部使用的一些字节码增强手段也容易在原生镜像里炸掉。我的建议是除非你的项目对启动速度和内存占用有极端要求比如Serverless、FaaS环境否则现阶段Java应用还是用JVM模式跑稳定性和生态兼容性最好。GraalVM这条路可以关注但不用着急把生产环境迁移过去。5.4 项目遇到的其他典型问题速查现象可能原因解决方案Invalid bound statement (not found)Mapper接口和XML没有绑定成功检查mapper-locations配置XML文件里namespace和接口全限定名是否一致SQL查询返回nullmap-underscore-to-camel-case没有开启设置map-underscore-to-camel-case: true或者用resultMap显式映射分页失效分页插件未注册或者注册顺序在自定义拦截器之后确认MybatisPlusInterceptor注入赶在自定义拦截器之前加载分页插件更新语句执行慢索引缺失、事务过长、循环更新用EXPLAIN分析SQL拆分长事务改成批量更新MyBatis日志不打印日志级别太高或配置了不正确的log-impl开发环境用StdOutImpl或配logging.level.mapper包DEBUGJVM时区不对导致时间字段差8小时数据库连接URL缺serverTimezone连接串加serverTimezoneAsia/ShanghaiJackson配置time-zoneGMT8最后再分享一个经验。很多朋友遇到问题第一反应是去百度/搜索引擎搜但如果你能用好官方文档和源码排查效率会高一倍。MyBatis的报错信息通常很明确你看一眼异常堆栈的前三行基本就能锁定方向。真定位不了再带着“你做了哪些操作、配置是什么、异常堆栈是什么”去社区里提问这样才能得到有效答案。这套Spring Boot和MyBatis的组合虽然天天在用但真正把它吃透的人并不多。你要是能按这篇文章的思路把缓存、分页、拦截器、源码链路这些点逐个攻破无论是写业务代码还是应付面试都会游刃有余。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。