多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

Spring整合MyBatis全解析:配置、事务与踩坑实战

Spring整合MyBatis全解析:配置、事务与踩坑实战 先说个很多人都经历过的场景你辛辛苦苦把一个项目从 JDBC 改成 MyBatis写好了各种 Mapper 接口和 XML代码跑起来也正常。可一旦把它塞进 Spring 容器里统一管理就冒出一堆“神奇”问题——SqlSession 关不干净、事务不生效、Mapper 接口报找不到实现类、连接池老是超时。多数人此时的第一反应是把锅甩给 MyBatis 或 Spring其实根子往往出在两者的“协作方式”上。这篇博文就从 Spring 整合 MyBatis 这件事讲起。你不光能学会完整配置步骤还能明白每一步背后的逻辑——为什么这样配置、事务怎么接管、Mapper 接口为什么能被注入、遇到绑定异常该怎么排查。走一遍之后再回首你会发现这不仅是配几个 bean 的事而是理解 Spring 和持久层框架之间分工的一次机会。适合刚从 MyBatis 单机测试转向 Spring 整合项目的人也适合项目里已经在用但踩过不少坑的开发者。1. 为什么非整合不可原生 MyBatis 的麻烦事1.1 每次 CRUD 都要手动管理 SqlSession 的痛先别急着上配置得先搞清楚我们到底在解决什么问题。原生 MyBatis 的常用套路是这样的从 SqlSessionFactory 里开一个 SqlSession执行完 SQL 后手动 commit最后老老实实 close。你要是这么写过心里一定有数——这流程基本是复制粘贴稍微漏一步连接就泄漏跑到后面整个应用卡死。走到 Service 层更难受。一个业务方法里往往要操作两张表意味着要先开一个 SqlSession查完 A 表再查 B 表然后把业务判断逻辑夹在中间最后统一提交。这只是事务需求没那么复杂的情况。真要遇上嵌套调用、异常回滚代码里全是 try-catch-finally你还没开始写业务逻辑先被资源管理折磨到怀疑人生。原生方式还有一个问题SqlSession 本身是线程不安全的。写 Demo 的时侯无所谓但放到 Web 应用里一个请求对应一个线程你不能让所有请求共享同一个 SqlSession更不能每次请求都手动管理开关。这种重复且容易出错的劳动根本不该由人来执行。1.2 整合前后的核心变化Spring 整合 MyBatis 之后最大的变化就是 MyBatis 的生命周期完全交给 Spring 管理。你会发现项目里不再有人手动去开 SqlSession 和 close而是注入了一个叫 SqlSessionTemplate 的东西。它是线程安全的内部会自动管理 SqlSession 的创建、绑定到当前线程、提交或回滚、以及最终关闭。你写 Mapper 接口的时候也不需要写实现类——Spring 通过动态代理机制扫描到接口后自动生成代理对象注入到 Service 层。这也就是为什么 Service 里Autowired一个 Mapper 接口它居然能直接用的原因。事务方面更省心。原生状态下事务边界要自己圈Spring 接手后你只管在 Service 方法上加Transactional框架会自动开启事务、执行 SQL、提交或回滚。底层原理不复杂整合包里的 SqlSessionTemplate 会感知 Spring 当前的事务状态如果事务是 Spring 开启的它就直接加入这个事务不会自己另开一个从而保证多条 SQL 在同一个物理连接里执行真正做到原子性。2. 环境准备依赖、版本与项目结构2.1 Maven 依赖清单与版本选择整合需要两个核心库MyBatis 本身和mybatis-spring适配包。前者提供 SQL 映射能力后者负责把 MyBatis 桥接到 Spring 容器里。下面是一套比较标准且稳定的依赖组合dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.16/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.1.2/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version5.3.31/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version5.3.31/version /dependency dependency groupIdcom.zaxxer/groupId artifactIdHikariCP/artifactId version4.0.3/version /dependency dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.0.33/version /dependency版本选择要特别留意mybatis-spring2.x 对应 MyBatis 3.5对应 Spring 5.x如果你的项目处于 Spring Boot 环境直接用mybatis-spring-boot-starter就行它会帮你把版本对齐。但纯 Spring 项目必须手动匹配我看过不少人拿mybatis-spring1.x 配 Spring 5启动就报 NoSuchMethodError基本是版本不兼容的锅。2.2 连接池和驱动的补充说明连接池选型上HikariCP 是当前综合表现不错的选择。配置很简单核心参数就几个jdbcUrl、driverClassName、用户名密码再按需调整最大连接数和超时时间。使用 MySQL 8 及以上版本驱动类要写com.mysql.cj.jdbc.DriverjdbcUrl里建议显式带上serverTimezoneAsia/Shanghai和useSSLfalse不然后续时间格式化或 SSL 握手问题很可能让你排查半天。参数示例jdbcUrljdbc:mysql://localhost:3306/demo_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8 driverClassNamecom.mysql.cj.jdbc.Driver usernameroot password你的密码 maximumPoolSize10 connectionTimeout30000拿数据库连接池类比一下它相当于一个“电话总机”应用每次需要数据库连接时直接从池里取一条“空闲电话线”用完再放回去避免了频繁拨号建立连接的开销。HikariCP 内部做得很极致启动快、吞吐高对中小型项目完全够用。3. 配置落地XML 配置与 Java 配置两种姿势这一节是核心。两种方式原理相同选择哪一种看你项目的风格。早期项目偏爱 XML因为团队成员不一定会写 Java 配置类新项目大多走 Java 配置类型安全、重构方便。3.1 XML 配置方案创建一个applicationContext.xml核心内容如下?xml version1.0 encodingUTF-8? beans xmlnshttp://www.springframework.org/schema/beans xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xmlns:contexthttp://www.springframework.org/schema/context xmlns:txhttp://www.springframework.org/schema/tx xsi:schemaLocationhttp://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd context:component-scan base-packagecom.example.demo/ bean iddataSource classcom.zaxxer.hikari.HikariDataSource property namedriverClassName valuecom.mysql.cj.jdbc.Driver/ property namejdbcUrl valuejdbc:mysql://localhost:3306/demo_db?useSSLfalseamp;serverTimezoneAsia/Shanghai/ property nameusername valueroot/ property namepassword value123456/ property namemaximumPoolSize value10/ /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property namemapperLocations valueclasspath:mapper/*.xml/ property nametypeAliasesPackage valuecom.example.demo.entity/ property nameconfiguration bean classorg.apache.ibatis.session.Configuration property namemapUnderscoreToCamelCase valuetrue/ property namelogImpl valueorg.apache.ibatis.logging.stdout.StdOutImpl/ /bean /property /bean bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.example.demo.mapper/ property namesqlSessionFactoryBeanName valuesqlSessionFactory/ /bean bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/ /beans这段配置里每一条都有讲究。mapperLocations指定了 XML 映射文件的位置MyBatis 启动时会把你写的 SQL 解析进内存和 Mapper 接口绑定。typeAliasesPackage用于给实体类起短别名这样 XML 里写resultTypecom.example.demo.entity.User可以简化为resultTypeUser。mapUnderscoreToCamelCase更是必需品没有它数据库里的created_at永远映射不到 Java 属性createdAt上。MapperScannerConfigurer的作用是扫描指定包下的所有 Mapper 接口把它们注册成 Spring Bean。注意这里我用了sqlSessionFactoryBeanName而不是sqlSessionFactory这样做的好处是不会强制提前初始化 SqlSessionFactory能避开一些初始化顺序的潜在坑。3.2 Java 配置方案同等作用的 Java 配置类长这样Configuration MapperScan(com.example.demo.mapper) public class MyBatisConfig { Bean public DataSource dataSource() { HikariDataSource dataSource new HikariDataSource(); dataSource.setDriverClassName(com.mysql.cj.jdbc.Driver); dataSource.setJdbcUrl(jdbc:mysql://localhost:3306/demo_db?useSSLfalseserverTimezoneAsia/Shanghai); dataSource.setUsername(root); dataSource.setPassword(123456); dataSource.setMaximumPoolSize(10); return dataSource; } Bean public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) { SqlSessionFactoryBean factoryBean new SqlSessionFactoryBean(); factoryBean.setDataSource(dataSource); factoryBean.setTypeAliasesPackage(com.example.demo.entity); PathMatchingResourcePatternResolver resolver new PathMatchingResourcePatternResolver(); factoryBean.setMapperLocations(resolver.getResources(classpath:mapper/*.xml)); org.apache.ibatis.session.Configuration configuration new org.apache.ibatis.session.Configuration(); configuration.setMapUnderscoreToCamelCase(true); configuration.setLogImpl(org.apache.ibatis.logging.stdout.StdOutImpl.class); factoryBean.setConfiguration(configuration); return factoryBean; } Bean public DataSourceTransactionManager transactionManager(DataSource dataSource) { return new DataSourceTransactionManager(dataSource); } }MapperScan注解替代了 XML 里的 MapperScannerConfigurer放在配置类上即可它会扫描指定包以及子包下所有接口自动注册为 Mapper Bean。这里sqlSessionFactory方法接收一个 DataSource 参数Spring 会自动把上面定义的 dataSource Bean 注入进来省去了手工ref的繁琐。3.3 两种配置的取舍XML 配置在复杂的条件判断、多环境切换场景下更灵活——改配置不用重新编译运维直接替换配置文件就行。Java 配置更直观是类型安全的写错了编译期就会发现IDE 跳转也方便。我个人新建项目时倾向 Java 配置但保持 XML 映射文件存在——Mapper 接口的 SQL 写在 XML 里动态 SQL 写起来远比注解舒服这一点后面展开聊。4. Mapper 层开发接口与 XML 的配合4.1 命名空间绑定机制整合完成不等于项目能跑Mapper 接口还得和 XML 正确绑定。这里的绑定全靠 XML 根节点里的 namespace 属性。一个标准的映射文件长这样?xml version1.0 encodingUTF-8 ? !DOCTYPE mapper PUBLIC -//mybatis.org//DTD Mapper 3.0//EN http://mybatis.org/dtd/mybatis-3-mapper.dtd mapper namespacecom.example.demo.mapper.UserMapper resultMap iduserMap typeUser id propertyid columnid/ result propertyusername columnusername/ result propertyemail columnemail/ result propertycreatedAt columncreated_at/ /resultMap select idselectById resultMapuserMap SELECT id, username, email, created_at FROM user WHERE id #{id} /select /mappernamespace 必须与 Mapper 接口的全限定名完全一致select标签的 id 必须与接口里的方法名一致。MyBatis 启动解析时会自动从 namespace 定位到对应接口然后为每个 id 生成代理方法。写错任何一个字符运行到那一行就会抛出Invalid bound statement (not found)。4.2 参数映射与结果映射单个参数可以直接传。多个参数时最推荐的做法是用Param注解显式命名public interface UserMapper { ListUser selectByCondition(Param(username) String username, Param(status) Integer status); }XML 里这样引用select idselectByCondition resultTypeUser SELECT * FROM user WHERE username #{username} AND status #{status} /select不写Param的情况下MyBatis 会用arg0、arg1或param1、param2作为参数名极其容易写错。与其依赖这种隐式规则不如多写一行注解代码可读性立刻上一个台阶。结果映射有两条路一是靠自动映射 mapUnderscoreToCamelCase配置适合列名和属性名规规矩矩的表二是写 resultMap适合复杂表连表查询、字段改名、一对一或一对多。后者的典型场景是字段整理后命名完全对不上或者需要嵌套结果集的时候——这种场景不是自动映射能解决的老老实实写 resultMap 才是正解。4.3 动态 SQL 的写法这是 MyBatis 相比 JDBC 最有竞争力的地方。按条件查询用户SQL 的 WHERE 子句在不同入参下可能完全不同。用if和where组合比在 Java 代码里手动拼接 SQL 优雅得多select idselectByCondition resultTypeUser SELECT * FROM user where if testusername ! null and username ! AND username LIKE CONCAT(%, #{username}, %) /if if teststatus ! null AND status #{status} /if if testemail ! null AND email #{email} /if /where ORDER BY id DESC /selectwhere会自动处理第一项前面多余的 AND——如果子句以 AND 开头它会聪明地去掉。不需要你在 Java 里写boolean hasCondition之类的状态位框架替你干完了。批量插入也很常用。单条 insert 循环 N 次数据库连接往返 N 次效率感人。用一个foreach标签一条语句搞定insert idbatchInsert INSERT INTO user(username, email, status) VALUES foreach collectionlist itemuser separator, (#{user.username}, #{user.email}, #{user.status}) /foreach /insert这里的 collection 固定写list表示入参是一个 List。item 是你给集合里每个元素起的名字后续用user.username获取元素属性。5. 事务管理从手动 commit 到 Transactional5.1 原生 MyBatis 的事务方式原生 MyBatis 下手动控制事务你大概率写过类似代码拿到 SqlSession 之后先session.commit()要么回滚session.rollback()finally 里还要session.close()。想象一个业务方法里查询、插入、更新三个操作任何一个出问题都要把前面所有操作撤销——代码会变成什么样每个分支都来一遍回滚判断业务没写几行控制代码堆成山。5.2 Spring 事务的整合原理Spring 整合后你只需要在 Service 方法上加一个注解Service public class UserService { Autowired private UserMapper userMapper; Transactional(rollbackFor Exception.class) public void createUserWithProfile(User user, Profile profile) { userMapper.insert(user); // 假设这里抛出了 RuntimeException profileMapper.insert(profile); } }方法执行期间所有 Mapper 的操作都在同一个事务里。一旦中间出现异常Spring 检测到后整个事务回滚前面执行成功的 insert 也会被撤销不会留下半截数据。原理不复杂DataSourceTransactionManager是事务的核心它管理着数据源连接。Transactional标注的方法被 Spring AOP 代理——方法进入前从连接池取一个连接开启事务把连接绑定到当前线程执行过程中SqlSessionTemplate 检测到当前线程已有数据库连接就沿用这个连接而不是取新的方法正常结束则提交异常则回滚最后释放连接并解绑线程。这一整套流程让我直观理解了一个道理事务的本质不是“在方法上画个圈”而是牢牢锁定一个数据库连接让所有 SQL 都在这条连接上串行执行。5.3 事务不生效的常见原因事务不生效是比较气人的问题因为代码不报错、数据也会写入只是该回滚的时候没回滚。常见原因有四个第一方法被同类内部调用。Transactional走的是代理机制同类里一个方法调用另一个方法调用发生在代理对象内部相当于绕过了代理——Spring 无法拦到这次调用。要解决把事务方法挪到另外一个 Service 里或者自己注入代理对象再调用。第二异常被吞掉。事务注解默认只对 RuntimeException 和 Error 回滚对受检异常如 Exception不回滚。你必须在方法里抛异常并且确保它传播出来而不是 catch 住。有一个更稳的写法Transactional(rollbackFor Exception.class)这样不管什么异常都会触发回滚值得养成习惯。第三数据库表用了不支持事务的引擎。比如 MySQL 的 MyISAM本身就没有事务支持你注解加得再多也白搭。表引擎请用 InnoDB。第四事务管理器配置漏了。XML 方案里如果没配置 DataSourceTransactionManagerTransactional就是个摆设项目还不一定报错。配置完成后记得确认tx:annotation-driven是否存在。6. 踩坑实录第一次整合最容易遇见的五个问题6.1 绑定异常报 Invalid bound statement (not found)这是出现频率最高的报错没有之一。报错信息直接告诉你哪条 SQL 找不到但真正的原因往往有几类namespace 没有对应到 Mapper 接口全限定名XML 文件没被mapperLocations扫到。比如你把 XML 放在src/main/resources/mapper/下面配置却写classpath:mybatis/*.xml路径错了文件根本没加载接口方法名和 XML 里的 id 对不上少个字母都不行XML 文件后缀写成了.xml.txt或者编译后没有被打进 classpath排查顺序建议是先直接看编译产物target/classes/mapper/里有没有对应 XML再看 namespace 和 id。前者经常被忽略其实很坑。我用过一个项目IDE 资源目录没配好XML 在源码目录里躺着运行时完全找不到排查了半天才发现是构建配置问题。6.2 字段映射问题下划线与驼峰数据表列名用user_name这种下划线风格实体类属性用userName这种驼峰风格这是很常规的操作。MyBatis 默认可不会自动把两者对应起来需要开启全局配置。configuration.setMapUnderscoreToCamelCase(true);开启后自动映射会把user_name转换到userName。但注意resultMap 里显式定义的字段不受这个开关影响你写什么映射就是什么所以如果你用了 resultMap还是得手动把每个列名和属性名对齐。还有个小坑create_time这种字段映射到LocalDateTime createTime没问题但数据库时间字段类型如果是TIMESTAMPJava 端要用LocalDateTime或java.sql.Timestamp接收别用java.util.Date硬顶容易出现时区偏差。6.3 为什么 SESSION 事务模式下看不到新数据MyBatis 的一级缓存是 SqlSession 级别的。没整合之前你每次手动关闭 SqlSession缓存随之清空所以对这个问题不敏感。整合后SqlSessionTemplate 里的 SqlSession 生命周期和 Spring 事务绑定如果在一个长事务里反复查询同一行数据第二次查询只要没有更新操作就会直接命中缓存返回旧值。很多人在一个方法里先查一遍、再更新、再查一遍第二次查出来的还是旧数据就是这个原因。处理方法有三种第一种在 Mapper 方法上加flushCachetrue强制每次查询刷新缓存第二种把大事务拆小查询和更新分开第三种干脆关掉一级缓存localCacheScopeSTATEMENT性能损失不算大换取心智简单。在整合环境下一级缓存是个容易制造“幻觉”的东西建议项目早期就定好统一策略不要放任自流。6.4 SqlSession 为什么线程不安全原生 MyBatis 的 SqlSession 默认非线程安全整合后你就不该再手动使用它。写代码时记住SqlSessionTemplate 才是安全的它内部对每次操作都会从底层工厂取一个新的 SqlSession用完立即关闭。这也是为什么整合后你写代码不需要关心 SqlSession 的生命周期——这些都封装在 Template 里了。如果某天你图省事把 SqlSessionFactory 注进来在 Service 里手动开 SqlSession 操作数据库那就相当于绕开了 Spring 的管理。事务、连接回收全部失效高点并发下连接泄漏是迟早的事。整合项目的红线之一是数据库访问只通过 Mapper 接口不要直接碰 SqlSession。6.5 连接池配置导致的启动失败HikariCP 启动时会尝试获取一个连接做连通性检测如果数据库地址写错、服务没起或者连接数设置不合理比如一个测试库只允许 5 个连接你设置 100启动阶段就会报异常。这里最容易忽略的是connectionTimeout。默认 30 秒在极端情况下数据库负载高、网络抖动你以为卡死了其实是在等待连接。诊断方式很简单看异常堆栈里是否有 HikariPool 相关字样再用数据库客户端手动连一下立刻就能定位是配置问题还是网络问题。7. 实战中的经验补充日志、分页与代码规范7.1 开启 SQL 日志让问题直接显形排查 SQL 相关问题时最有效的辅助工具不是 debugger而是把 MyBatis 的 SQL 日志打开。在配置类里设置configuration.setLogImpl(org.apache.ibatis.logging.stdout.StdOutImpl.class);这会把每条 SQL、参数、查询结果行数直接打印到控制台。开发阶段我强烈建议开启。你会清楚看到每次执行的 SQL 长什么样、参数装配进 #{...} 后是什么值、最终结果映射成几个对象。一旦怀疑缓存或参数问题看一眼日志就真相大白。到生产环境建议换成 Log4j2 或 Slf4j 实现按 Logger 级别控制 MyBatis 输出的日志量避免敏感 SQL 参数全部打进日志文件。比如在 logback 里设置com.example.demo.mapper包为 DEBUG 级别这样既能看到该包下 SQL又不会把 Spring 内部的 INFO 日志刷得天昏地暗。7.2 分页查询的方案与实践MyBatis 自身没有内置分页插件。最常用的方案是开源分页插件 PageHelper用法简单到一行代码PageHelper.startPage(pageNum, pageSize); ListUser list userMapper.selectPage(); PageInfoUser pageInfo new PageInfo(list);原理是拦截器在执行 SQL 前自动改写原 SQL包一层 count 查询 limit 条件返回总条数和当前页数据。看打印日志时你会看到它发出两条 SQL一条SELECT count(0)一条带LIMIT的查询。不过我得提醒一点分页插件只对紧跟其后的第一条查询生效。你在 startPage 和查询之间如果执行了其他 SQL 或逻辑分页就会错位甚至完全不生效。另外复杂连表查询、SQL 里有 UNION、GROUP BY 时count 语句可能会被插件改写得不准确。这类场景我一般不用插件而是手动写两个方法一个 count 总数一个查分页数据至少心里有数。7.3 代码分区与命名习惯整合做完之后维持代码整洁更重要不然过两个月自己都看不下去。我从实际项目里总结下来下面几条习惯值得早期就固定实体类与数据库表一一对应放在 entity 包。DTO 和 VO 单独放别把接口入参和数据库实体混在一起尤其是字段增减频繁的接口项目。Mapper 接口只做单表或简单多表操作复杂业务逻辑放在 Service 层编排。我在真实项目里见过 Mapper 里写一千行 SQL 的那不是项目需要而是把 SQL 当业务逻辑用了。XML 的 id 建议和接口方法名保持一致不要额外起别名比如方法名selectByUsernameXML id 却写selectUserByName。搜索引擎和 IDE 跳转会非常痛苦全局搜索都救不了你。参数传递用 Param 显式命名团队里任何一个人都能一下子看懂#{username}对应的入参含义而不是猜 arg0 是谁。举个例子一个简单的用户列表分页接口规范的做法是 controller 接收查询条件对象Query 对象service 层调用PageHelper.startPage或手工分页查询然后组装成页面需要的 VO 返回。Mpper 接口只关注查询本身不感知分页状态这样以后替换分页方案也不会牵连业务层。再聊一个容易被忽略的点MyBatis 的 XML 路径编写。方法名和 XML id 保持一致真的重要——IDEA 配合 MyBatisX 插件时接口方法旁边能看到一个绿色跳转箭头直接跳到对应 XML 标签。没有这种命名一致性插件的便利也会大打折扣你连“看 SQL 实现”都得靠手搜。7.4 为什么建议把动态 SQL 留在 XML 里很多人刚开始接触 MyBatis 时觉得注解开发快、代码少一个Select就把 SQL 写到接口上了清爽无比。我当时也这么干过。用着用着就发现注解方式的动态 SQL 简直是折磨——script标签里塞if像把 XML 硬塞到 Java 字符串里转义、缩进、格式全乱代码审查时读起来非常费劲。XML 方式则没有这个问题。哪怕只有一条复杂的动态查询XML 的层次感也远远好过注解。注解适合固定的单行 SQL比如Select(SELECT * FROM user WHERE id #{id})这种雷打不动的操作。一旦查询条件有if、foreach、choose毫不犹豫用 XML。这是我在项目里亲身对比后的结论注解负责简单查询XML 负责动态逻辑两者不冲突但思想要清晰。还有一点SQL 写在哪里最好由团队约定统一。我在接手过的一个项目里见到过一半注解一半 XML 的混乱场面一个接口的查询分布在两处维护成本成倍增加。统一策略之后团队每个人看代码都省心不少。如果是新建项目我的建议是一律 XML没有例外。为什么因为哪怕现在这个 SQL 很简单三个月后难保不会加条件、加关联。直接上 XML后续演进不需要改代码结构只需要加标签。8. 整合完成后的一点心得以上都是配置和代码层面的内容最后说我个人的一些实际感受。第一次完整整合 Spring 和 MyBatis 时会觉得步骤有点多一个数据源、一个 SqlSessionFactory、一个扫描器、一个事务管理器各自分工明确。一旦理清“谁负责什么”整套配置其实很顺——数据源管连接SqlSessionFactory 管 SQL 解析和执行扫描器负责把接口变成 Bean事务管理器负责把多个操作拧成一股绳。配置不难难的是遇到报错时能猜出是哪一环出了问题。排查问题的通用路径我一直遵循一条原则先看日志再猜原因。MyBatis 报错信息其实相当精确无非是绑定失败、映射错误、连接异常这几类。把日志打开把 SQL 打印出来问题基本就缩小到很小的范围了。反而是连接池、日志、缓存这些“加分项”刚开始可以缺但整合一旦跑通就要尽早补上它们决定了你能不能在生产环境安心睡觉。遇到拿不准的配置时找一个最小的完整项目从头跑一遍比在存量项目里瞎试高效得多。把依赖、配置、接口、XML、Service、Controller 从 0 到 1 搭起来远比读十篇文档印象深刻。你亲手敲过一遍之后报错在哪个环节、解决方案是什么心里基本有数了以后再碰到同类的整合问题都会从容很多。
返回列表