首页
/
行业洞察
/
正文
INDUSTRY INSIGHT · 深度
Spring整合MyBatis完整指南:配置原理、实战技巧与常见坑
📅 2026/9/10 19:21:36
✍️ 爱科研究院
👁 阅读 3,247
1. 为什么Spring整合MyBatis这件事还值得再捋一遍先说个现象。这两年Spring Boot太普及了大多数人新建项目就是spring-boot-starter-web加上mybatis-spring-boot-starter跑起来就能用以至于不少工作两三年的Java开发其实没亲手搭过一次完整的Spring MyBatis整合。但一碰到老项目的SSM架构或者面试被问到底层原理就露馅了。我这些年接过不少维护项目也帮人排查过不少整合问题发现很多坑并不是MyBatis本身难而是对整合的链条不熟。比如MapperScan到底扫描的是什么、SqlSessionFactoryBean在里面扮演什么角色、为什么接口没加Mapper就会注入失败、为什么XML文件放错目录找不到SQL——这些问题如果不理解整合的本质就算用了Spring Boot也会在换了个配置方式后翻车。这篇文章就以“Spring整合MyBatis”为主题从头到尾走一遍完整步骤。覆盖两种场景经典SSMSpring Spring MVC MyBatis用XML配置或Java Config以及Spring Boot快速整合顺便对比差异。我会把每一步为什么这么做讲清楚而不是只给一段能跑通的配置就完事。适合的读者准备手写SSM项目的人、面试前想补底层逻辑的人、以及被各种整合报错折磨过的同学。2. 整合前必须拎清的三个核心概念2.1 MyBatis和Spring分别管什么在动手写配置之前先把边界划清楚。MyBatis本身是个独立的持久层框架它自己能工作不需要Spring也能用。你手动创建SqlSessionFactory从里面拿SqlSession再通过getMapper()拿到Mapper接口的代理对象就能执行SQL。这一套流程MyBatis自己完全能闭环。但实际项目里我们不会这么干。因为我们需要Spring来管理数据源、管理事务、管理Bean的生命周期。所以“整合”的本质就一句话把MyBatis的核心对象交给Spring容器来创建和管理。具体来说就是把SqlSessionFactory变成Spring容器里的一个Bean把Mapper接口的代理对象也变成Spring容器里的Bean这样就能在Service里直接Autowired注入了。理解了这句话后面所有的配置都是在围绕这两件事展开。2.2 三种整合方案选哪种看场景我整理了一下Spring整合MyBatis常见的有三种方式各有适用场景。整合方式核心配置适用场景传统XML配置applicationContext.xml全局配置老项目维护、面试手写、对XML不排斥的团队Java Config配置BeanConfiguration类不想用XML、喜欢全注解的新项目Spring Boot自动配置mybatis-spring-boot-starter新项目首选零配置或极少配置先说结论如果是新项目无脑选Spring Boot那套如果是在维护SSM老项目就看原项目用的是哪套保持风格统一如果只是为了面试或理解原理建议把Java Config这套完整走一遍因为它是理解Spring Boot自动配置底层逻辑的最佳途径。我在后面详细展开时以“Java Config Spring Framework 5 MyBatis 3.5”为主线举例因为这套能手动控制每一个环节最能看出整合的本质。Spring Boot版本会在对应节点给出差异说明。2.3 版本选型的一个隐藏大坑版本问题说出来都是泪。MyBatis的版本、mybatis-spring适配版本、Spring版本这三者之间是有兼容关系的。举个例子Spring Framework 6.0开始基于Jakarta EE要求MyBatis 3.5.10同时要求mybatis-spring 3.0。如果Spring 6配了mybatis-spring 2.x会出现Bean创建异常错误信息还很模糊。如果你是Spring Boot项目mybatis-spring-boot-starter的版本要和Spring Boot大版本匹配。比如Spring Boot 3.x对应starter 3.xSpring Boot 2.7对应starter 2.3.x混用就会出幺蛾子。注意因为Spring Boot 3.x的包名从javax.*变成了jakarta.*这个变化会直接影响引入的坐标和代码写法网上很多老教程在Spring Boot 3上跑不通多数都是这个原因。我的习惯是不追求最新版本选成熟稳定的组合。比如Spring 5.3.x MyBatis 3.5.13 mybatis-spring 2.1.x这个组合经过大量生产验证Spring Boot 3.x项目就用mybatis-spring-boot-starter 3.0.x。不要拿着一套配置到处套看项目说话。3. 手把手搭建Java Config方式整合MyBatis3.1 项目结构与依赖准备先看目录结构这是很多新手第一步就没做对的。src/main/java ├── com/example/demo │ ├── config │ │ ├── DataSourceConfig.java │ │ └── MyBatisConfig.java │ ├── entity │ │ └── User.java │ ├── mapper │ │ ├── UserMapper.java │ │ └── UserMapper.xml │ ├── service │ │ └── UserService.java │ └── controller │ └── UserController.java src/main/resources └── mybatis ├── mapper │ └── UserMapper.xml └── mybatis-config.xml注意Mapper的XML文件有两个地方可以放放在和接口同包的com/example/demo/mapper/下或者放在resources/mybatis/mapper/下。两种都行但配置上有区别后面我会单独说。如果你用的是Maven依赖坐标长这样properties spring.version5.3.31/spring.version mybatis.version3.5.13/mybatis.version mybatis-spring.version2.1.2/mybatis-spring.version /properties !-- Spring核心 -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency !-- MyBatis核心 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version${mybatis.version}/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version${mybatis-spring.version}/version /dependency !-- 数据库驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.0.33/version /dependency !-- 连接池 -- dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.20/version /dependencymybatis-spring这个坐标是整合的桥梁一定要引入没有它MyBatis和Spring根本联系不起来。这个jar包里的核心类是SqlSessionFactoryBean和MapperScannerConfigurer或MapperScan。3.2 数据源配置数据源是第一步没有它后面全是空谈。我用Druid连接池举例也支持HikariCP等原理一样。Configuration PropertySource(classpath:jdbc.properties) public class DataSourceConfig { Value(${jdbc.driver}) private String driver; Value(${jdbc.url}) private String url; Value(${jdbc.username}) private String username; Value(${jdbc.password}) private String password; Bean public DataSource dataSource() { DruidDataSource dataSource new DruidDataSource(); dataSource.setDriverClassName(driver); dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); // 连接池初始大小、最小空闲数、最大连接数 dataSource.setInitialSize(5); dataSource.setMinIdle(5); dataSource.setMaxActive(20); return dataSource; } }对应的jdbc.propertiesjdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.password123456这里有三个细节值得说。第一MySQL驱动的类名。com.mysql.jdbc.Driver是老版本驱动5.xcom.mysql.cj.jdbc.Driver是8.x驱动。如果驱动版本和类名对不上会报ClassNotFoundException或连不上数据库。第二JDBC URL里的参数。serverTimezoneAsia/Shanghai是8.x驱动必须加的不然会报时区错误useSSLfalse是本地开发建议关闭SSL校验生产环境按需开启。第三为什么推荐用连接池而不是DriverManagerDataSource。DriverManagerDataSource每次拿连接都会重新创建物理连接性能极差。连接池的核心价值是复用连接省去频繁创建销毁的开销。生产环境别省这一步。3.3 SqlSessionFactoryBeanMyBatis与Spring的桥梁这一步是整个整合的核心一定要把SqlSessionFactoryBean理解透了。SqlSessionFactoryBean是FactoryBean的一个实现。当Spring创建Bean的时候如果发现某个Bean实现了FactoryBean接口Spring会调用它的getObject()方法拿到真正的对象——也就是SqlSessionFactory。所以你在配置类里看到SqlSessionFactoryBean实际注入到容器里的是它的getObject()产物。Configuration MapperScan(com.example.demo.mapper) public class MyBatisConfig { Autowired private DataSource dataSource; Bean public SqlSessionFactory sqlSessionFactory() throws Exception { SqlSessionFactoryBean factoryBean new SqlSessionFactoryBean(); // 1. 数据源 factoryBean.setDataSource(dataSource); // 2. 实体类别名包XML里可以用User代替com.example.demo.entity.User factoryBean.setTypeAliasesPackage(com.example.demo.entity); // 3. MyBatis全局配置文件非必须具体配置项见下文 factoryBean.setConfigLocation(new ClassPathResource(mybatis/mybatis-config.xml)); // 4. XML映射文件路径下面单独讲 factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver() .getResources(classpath:mybatis/mapper/*.xml)); return factoryBean.getObject(); } }这里面setMapperLocations是最容易出错的点。如果你的XML文件和Mapper接口放在同一个包下比如都在com.example.demo.mapper下且Maven的build配置里把*.xml一并打包进classpath那么可以不用设置mapperLocations——MyBatis会扫描接口同路径下的XML。但如果你把XML放在resources/mybatis/mapper/下我推荐这种方式就必须设置mapperLocations否则运行时报Invalid bound statement (not found)。mybatis-config.xml也不是必须的。如果只有少量配置可以在SqlSessionFactoryBean上直接通过setter设置。我的习惯是少量配置直接factoryBean.setConfiguration(configuration)或setter设置配置多了才单独用文件。3.4 MapperScan扫描原理与替代方案MapperScan(com.example.demo.mapper)这一行作用是把接口包里所有Mapper接口注册成Spring的Bean。它的底层原理是通过ClassPathMapperScanner扫描指定包把每个Mapper接口的Bean定义注册到容器中。这个Bean的真实身份是MapperFactoryBean它也是FactoryBean通过JDK动态代理为接口生成代理对象。等你注入UserMapper时Spring调用getObject()返回的是代理对象这个代理对象内部持有SqlSessionTemplate执行方法时利用sqlSession.getMapper()获取真正的Mapper。这里我遇到过不少人的疑问为什么加了MapperScan还要加Mapper注解实际上两个东西干的事不一样。MapperScan是Spring层面的扫描注册Mapper是MyBatis的注解标注这个接口是MyBatis的Mapper。在整合场景下如果你已经用了MapperScanMapper可以不写因为扫描器已经负责注册了如果不用MapperScan那么每个Mapper接口要么加Mapper注解要么在XML里通过mapper标签注册。很多老项目不用MapperScan而是在applicationContext.xml里配置mybatis:scan base-packagecom.example.demo.mapper /或者用MapperScannerConfigurerBean。这三种方式本质一样都是把Mapper接口注入Spring容器选一种即可别混用。3.5 事务管理配置数据操作基本离不开事务。Spring整合MyBatis后事务管理用的是Spring的DataSourceTransactionManager。Configuration EnableTransactionManagement public class TransactionConfig { Bean public PlatformTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }EnableTransactionManagement开启Spring的注解事务管理功能这样在Service方法上加Transactional才会生效。有个细节需要注意DataSourceTransactionManager管的是JDBC连接的事务它和MyBatis的关系是——MyBatis通过Spring管理的数据源拿连接而Spring事务管理器通过ThreadLocal绑定当前线程的连接资源。当Transactional方法开始执行时事务管理器拿到一个连接标记autoCommit(false)然后MyBatis在执行SQL时用的是同一连接最终统一提交或回滚。如果不用Spring事务管理器那MyBatis的SqlSessionTemplate默认是自动提交的或者需要手动调用sqlSession.commit()就很不方便。4. 经典SSM场景applicationContext.xml方式整合虽然Java Config是现在的主流但老项目里XML配置仍然是主要形式。这里把XML方式的关键配置列一遍很多维护任务要用到。applicationContext.xml核心片段!-- 1. 数据源 -- bean iddataSource classcom.alibaba.druid.pool.DruidDataSource init-methodinit property namedriverClassName valuecom.mysql.cj.jdbc.Driver/ property nameurl valuejdbc:mysql://localhost:3306/demo?useUnicodetrueamp;characterEncodingutf8/ property nameusername valueroot/ property namepassword value123456/ /bean !-- 2. SqlSessionFactoryBean -- bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property nametypeAliasesPackage valuecom.example.demo.entity/ property namemapperLocations valueclasspath:mybatis/mapper/*.xml/ /bean !-- 3. Mapper接口扫描 -- bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.example.demo.mapper/ property namesqlSessionFactoryBeanName valuesqlSessionFactory/ /bean !-- 4. 事务管理器 -- bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/XML方式有个常见坑MapperScannerConfigurer配置sqlSessionFactory和sqlSessionFactoryBeanName的差别。如果写sqlSessionFactoryref方式Spring会提前实例化SqlSessionFactoryBean可能引起Bean初始化顺序问题。建议用sqlSessionFactoryBeanName传字符串名称让Spring延迟解析。另外一个老的坑XML文件里property nameurl valuejdbc:mysql://localhost:3306/demo?useUnicodetruecharacterEncodingutf8/如果直接用XML解析会报错需要转义成amp;。这个错误新手必踩。Spring MVC整合部分需要在spring-mvc.xml里配置包扫描、注解驱动和视图解析器这部分和MyBatis关系不大就不展开了。需要注意的只有一点SSM项目存在两个容器applicationContext.xml负责Service、Mapper等中底层组件spring-mvc.xml负责Controller层。如果你把Mapper扫描放到了spring-mvc.xml里有时候也能跑但事务可能会不生效因为Controller容器里的Mapper实例和Service容器里的事务管理器不在一个容器上下文。5. Spring Boot方式整合为什么快快在哪说完了经典方式再看Spring Boot整合就清晰多了。Spring Boot的做法就是把上面手动做的事自动完成。先在pom.xml引入dependency groupIdorg.mybatis.spring.boot/groupId artifactIdmybatis-spring-boot-starter/artifactId version3.0.3/version /dependency配置文件application.ymlspring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/Shanghai username: root password: 123456 mybatis: type-aliases-package: com.example.demo.entity mapper-locations: classpath:mapper/*.xml然后启动类上加上MapperScan(com.example.demo.mapper)或者在每个Mapper接口上加Mapper。这就完事了。底层发生了什么MybatisAutoConfiguration这个自动配置类会在Spring上下文里注入SqlSessionFactory和SqlSessionTemplate数据源则来自DataSourceAutoConfiguration。所以你在Spring Boot项目里只需要关注自己的业务代码和YAML配置不用手写Bean定义。但有一点要注意Spring Boot的自动配置也不是万能的。如果你自定义了DataSourceBean那么SqlSessionFactory会优先使用你自定义的这个数据源。如果你自定义了SqlSessionFactory可能会导致MybatisAutoConfiguration的部分配置不生效。别问我怎么知道的自定义配置和人自动配置打架这种事生产环境见多了。提示Spring Boot整合MyBatis实际是在Spring的容器机制上加了自动配置这一层。理解了手动整合中SqlSessionFactoryBean和MapperScannerConfigurer的职责你在Spring Boot里遇到任何MyBatis配置问题时其实都能在底层找到原因。6. 实战中必须掌握的MyBatis核心用法配置跑通只是第一步项目里真正要用好MyBatis还有几个时常被问到的点这里一次性说清楚。6.1 动态SQLif标签的经典坑动态SQL是MyBatis的看家本领。但if test这个标签很多人一上来就踩坑。最典型的就是字符串比较。你可能会写select idgetUsers resultTypeUser SELECT * FROM user WHERE 1 1 if testname ! null and name admin AND name #{name} /if /select看起来没问题但运行时会报错或者不生效。原因是在OGNL表达式里单引号包起来的admin会被解析成char类型和String比较会出问题。正确写法if testname ! null and name admin用外层单引号、内层双引号的写法把admin当字符串处理。还有个常被忽略的点是indexOf用法。比如要判断某个字符串是否包含子串if testname ! null and name.indexOf(a) ! -1 AND name LIKE CONCAT(%, a, %) /ifindexOf在OGNL里可以直接调用String的方法返回值是int不等于-1表示包含。这个在做模糊查询条件拆分时很实用。where 1 1这种写法虽然能跑但不够优雅。MyBatis提供了更聪明的where标签它会自动去掉SQL开头的AND/OR还能自动处理条件为空的情况select idgetUsers resultTypeUser SELECT * FROM user where if testname ! null and name ! AND name LIKE CONCAT(%, #{name}, %) /if if teststatus ! null AND status #{status} /if /where /select6.2 缓存机制一级缓存、二级缓存的行为MyBatis缓存是高频面试点也是实战容易意识模糊的地方。一级缓存是SqlSession级别的默认开启。同一个SqlSession中如果两次查询使用相同的SQL语句和参数第二次不会查数据库直接返回缓存结果。但注意两个前提前提一是必须是同一个SqlSession前提二是中间没有发生增删改操作否则缓存会被清空。在Spring整合环境下SqlSessionTemplate的机制比较特殊每个Mapper方法调用默认会创建并使用同一个线程绑定的SqlSessionSqlSessionTemplate实现了线程安全它内部用的是动态代理每次操作会从SqlSessionHolder里拿SqlSession同一事务内复用同一个SqlSession。所以在同一个事务里一级缓存是生效的不在同一事务里每次Mapper调用可能拿到不同的SqlSession一级缓存就没啥存在感。二级缓存是Mapper级别的跨SqlSession共享。默认不开启要开启需要在Mapper XML里加cache/标签实体类实现Serializable接口否则序列化报错但实战中我很少开二级缓存。原因很简单在高并发场景下二级缓存会造成脏读问题——如果多个系统或应用同时操作同一张表缓存中的数据就可能过期。而且事务边界复杂时查询结果缓存的失效逻辑很微妙。MyBatis的缓存和业务系统的需求不像Redis那样可精细控制出了问题排查成本很高。先用好数据库索引和SQL优化缓存的事交给Redis等更可控的组件。6.3 批量插入的正确姿势热词里提到“mybatis plus 批量插入”这也是日常开发里经常出现的问题。在MyBatis里做批量插入主要有两种方式第一种是foreach拼接SQLinsert idbatchInsert INSERT INTO user (name, age, email) VALUES foreach collectionlist itemitem separator, (#{item.name}, #{item.age}, #{item.email}) /foreach /insert这种方式是一条SQL插入多条数据性能相对较好。但要注意MySQL的max_allowed_packet参数限制了单条SQL的最大长度如果一次拼接太多数据会报PacketTooBigException。所以一次批量插入建议控制在500~1000条。还要注意环境允许的话JDBC URL可以加上rewriteBatchedStatementstrue对JDBC的addBatch方式有更好的优化。第二种是JDBC Batch方式MyBatis也支持try (SqlSession sqlSession sqlSessionFactory.openSession(ExecutorType.BATCH)) { UserMapper mapper sqlSession.getMapper(UserMapper.class); for (User user : userList) { mapper.insert(user); } sqlSession.commit(); }这种方式是多次执行SQL但通过JDBC的Batch机制减少网络往返性能也很好。关键点在于要用ExecutorType.BATCH打开SqlSession否则默认还是普通执行。在Spring整合环境下直接用SqlSessionTemplate的batch执行时有讲究它要求整个批量操作在一个事务里否则每次openSession都是新的会话Batch效果就废了。所以推荐在Service方法上加Transactional然后通过SqlSessionTemplate获取Mapper用Batch模式批量执行。6.4 逻辑删除数据如何查询出来这个热词问的是MyBatis-Plus的逻辑删除问题。MyBatis-Plus默认开启逻辑删除后普通查询会自动追加deleted 0条件那用户想“把逻辑删除的数据也查出来”怎么办两个常规办法第一用TableLogic配合自定义SQL直接忽略逻辑删除条件。你可以自定义Mapper方法写SELECT * FROM user这样的原生SQLMyBatis-Plus不会给自定义SQL自动追加逻辑删除条件除非你用了它提供的QueryWrapper等封装。第二用QueryWrapper时通过方法级参数控制QueryWrapperUser wrapper new QueryWrapper(); wrapper.last(AND deleted 1); // 查已删除的数据或者指定SqlParser(filter true)但不建议轻易用全局忽略逻辑删除容易把业务搞乱。我的建议是业务需求确实需要查看被删除数据时单独写查询方法明确表达“我要查已删除数据”而不是改动全局逻辑删除行为。清晰的业务语义比少写一行代码重要得多。6.5 SQL日志打印与插件的选择“mybatis配置打印”、“mybatis 打印可执行sql插件”这类热词出现频率很高。确实MyBatis默认只打印预编译语句和参数不能直接看到完整的可执行SQL排查问题不方便。几个常用方案方案说明适用场景mybatis.configuration.log-impl配置StdOutImpl或Slf4jImpl打印Preparing和Parameters快速简单MyBatis Log PluginIDEA插件将MyBatis日志格式化为完整SQL点击直接执行本地开发调试p6spyJDBC驱动代理拦截打印真实完整SQL及耗时生产环境SQL问题排查MyBatis Log Easy Plus热词里出现的插件集成到项目里输出可读SQL不想装IDEA插件时最省事的方式是在配置里加mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl但更推荐用Slf4jImpl配合logging.level.com.example.demo.mapperdebug使用这样XML里的SQL打印以日志方式输出可以按等级控制而不是全部打到控制台。6.6 MyBatis兼容多数据库的问题热词里还有“mybatis兼容多数据库”这个场景主要出现在产品需要支持MySQL、PostgreSQL、Oracle等不同数据库时。核心思路是让SQL针对不同数据库走不同逻辑通常有两种做法第一种是databaseIdProvider。在MyBatis配置中定义Properties vendorProperties new Properties(); vendorProperties.setProperty(MySQL, mysql); vendorProperties.setProperty(Oracle, oracle); databaseIdProvider.setProperties(vendorProperties); factoryBean.setDatabaseIdProvider(databaseIdProvider);然后XML里可以写insert idinsertUser databaseIdmysql !-- mysql 写法 -- /insert insert idinsertUser databaseIdoracle !-- oracle 写法 -- /insertdatabaseIdProvider会根据当前数据库连接自动选择对应的SQL。第二种是在动态SQL里通过_databaseId参数判断if test_databaseId mysql !-- mysql 方言 -- /if if test_databaseId oracle !-- oracle 方言 -- /if_databaseId是MyBatis内置变量。这种方式适合差异点比较少的情况。如果每一条SQL都需要差异化建议考虑引入数据库方言层或者直接换通用持久层框架。7. 常见整合报错与排查实录这部分是干货中的干货都是在实际项目里踩过的坑整理成了速查表。报错信息可能原因解决办法Invalid bound statement (not found): xxxMapper.xxxXML没有加载到namespace写错方法名不一致检查mapperLocations路径检查namespace和方法idMapper method xxx has an unsupported return type返回类型配置错误resultType和实际返回类型要匹配Cause: org.apache.ibatis.binding.BindingException接口没有注册为Spring Bean加MapperScan或MapperProperty sqlSessionFactory or sqlSessionTemplate are requiredMapperScannerConfigurer没找到SqlSessionFactory检查Bean定义顺序或sqlSessionFactoryBeanNamewrite operations are not allowed in read-only mode连接处于只读状态检查事务注解是否加了readOnlytrue检查数据源配置Table doesnt exist表名或库名大小写问题检查MySQL表名大小写敏感配置A query was run and no Result Maps were found缺少resultMap或resultType必须指定结果映射Using a target of a method is not supportedJDK动态代理传了class对象检查参数传递是否传了实例挑几个重点展开说。第一个Invalid bound statement。这个错误99%的MyBatis新手都遇到过。原因就三种要么XML没被加载到mapperLocations配置错要么Mapper接口的包路径和XML的namespace不匹配要么接口方法和XML的id对不上。排查思路先确认target/classes目录下有没有生成对应的XML再用MapperScan的包路径和XML里的namespace逐一对比最后看方法名是否一致。第二个write operations are not allowed in read-only mode。这个不是MyBatis的经典报错而是Spring事务配置导致的。当你给Service方法加了Transactional(readOnly true)同时又执行了CUD操作驱动就会拒绝写入。排查思路方法只做查询就保持readOnlytrue有修改操作就删掉readOnly或者改成false。还有一个常见场景MYSQL连接串的readOnlytrue参数也会导致类似问题。第三个接口注入失败。Autowired注入Mapper时报NoSuchBeanDefinitionException原因通常是MapperScan扫的包不对或者根本没扫。要注意的是如果MapperScan扫描了接口但接口所在的包和Spring Boot启动类不在同一个包层级下有时会因扫描范围问题失败。我的习惯是MapperScan明确指定到mapper包不依赖启动类默认扫描。8. 整合完成后的自检清单与项目落地心得配置都做完项目也能跑起来这时候别急着就完事了。我在项目上会再过一遍自检清单确认整合质量。第一层功能正确性。增删改查都能跑通事务回滚符合预期。怎么验证事务写一个Service方法第一句insert成功第二句制造异常比如除零最后查数据库确认两条都没写进去回滚生效。第二层配置健壮性。编译环境换到测试库/生产库时只改配置不碰代码。检查是否有硬编码参数散落在代码里数据库密码是否还在代码仓库里。第三层执行效率。SQL是否走了索引N1查询是否出现。MyBatis的延迟加载如果配置不当很容易在循环查关联数据时变成N1。排查方法很简单开启SQL日志看执行一次列表查询时后台跑了多少条SQL。第四层可维护性。XML里SQL是否有清晰的注释动态SQL块是否拆成可复用的sql片段。比如查询列和where条件经常复用定义成sql idBase_Column_List然后通过include refidBase_Column_List/引入改一处全局生效。我在实际维护过程中还有几条个人经验第一同名冲突能避开就避开。Controller层的DTO别和Entity混用避免名相近导致resultType误配。代码生成器生成的XML和实体类在改动前最好先统一格式否则多人协作时会频繁冲突。第二XML里的SQL尽量手写核心部分生成器产物只做基础CRUD。因为生成器生成的查询方法太机械字段一多条件就臃肿不如自己写where动态条件灵活。第三分页功能优先用MyBatis的分页插件PageHelper或MyBatis-Plus的分页插件不要自己用SQL拼接limit。分页插件底层做过count查询优化自己拼很容易写出查全表count的烂SQL。第四日志级别在生产环境要调到WARN以上特别是StdOutImpl这类直接往控制台打的实现生产环境开着会非常影响性能。最后再补充一个很多人容易忽略的点MyBatis的XML文件里SQL结尾分号问题。在SQL Server等数据库上XML里SQL语句结尾不能带分号带了可能会报错。这个细节说是小事但真的能卡一下午。这套整合流程虽然从Spring 5说起但核心结论对Spring 6、Spring Boot 3同样适用——无非是依赖坐标和包名换了换。把SqlSessionFactoryBean、MapperScannerConfigurer和事务管理器这三个点吃透任何版本下你都能快速定位配置问题。希望这篇整理能帮到还在整合路上摸索的朋友。
📌 标签:
工业官网
设计趋势
AI 建站
SEO
获取完整报告 →
RELATED ARTICLES
推荐阅读
2026/9/10 19:21:36
从开源BI到ChatBI:自然语言交互的技术演进
2026/9/10 19:21:36
WSL 容器 API(WSLC)C 回调类型完全指南:从崩溃转储到镜像拉取的回调体系详解
2026/9/10 19:16:36
2026年大专生必备:数据分析技能与就业指南
2026/9/10 20:11:39
SpringBoot测试进阶指南:从MockMvc到Testcontainers的完整实践
2026/9/10 20:11:39
坐标系统基础与应用:从原理到工程实践
2026/9/10 20:11:39
用Python从零实现一个区块链:哈希引用、PoW与链校验详解
2026/9/10 20:11:39
交换机品牌怎么选?十大品牌深度对比与选型实战指南
2026/9/10 20:11:39
@Autowired注入失败导致空指针?一文讲透排查链路与根治方案
2026/9/10 20:06:39
Android APK加固性能优化:指令级混淆与零拷贝资源加密
2026/9/10 0:04:20
AI搜索的信任缺口:企业内容如何在答案时代自证可信
2026/9/10 0:04:20
Spring Boot+Vue+Node.js售后服务系统开发实战
2026/9/10 0:04:20
SpringBoot+Vue民宿预订管理系统开发实践:从架构设计到部署上线
2026/9/10 2:30:52
超人会飞不算本事:系统稳定依赖清晰规则与边界设计
2026/9/10 5:51:31
超人VS蜘蛛侠:拆解超级IP的影响力与传播方法论
2026/9/10 8:32:02
基于CNN的调制信号识别:MATLAB实现时频图分类实战