
很多人在学SSM整合的时候卡住不是因为某个框架多难而是没搞明白这三个框架凑在一起时谁负责谁、谁的容器管哪些Bean、配置文件的加载顺序是什么。单独看Spring教程觉得懂了单独看MyBatis也觉得懂了结果一整合项目启动直接报错或者页面404又或者Service层注入为null排查一整晚死活找不到原因。这篇就把SSM整合从工程搭建到配置、从调用链到排错完整讲透适合刚学完Java Web、正准备做SSM项目练习的同学也适合那些被整合折磨过、想系统梳理一遍的人。1. SSM整合的本质不是堆框架而是理清容器边界1.1 三个框架各自的“地盘”是什么先把三个框架的分工说清楚。Spring是一个大管家负责对象的创建、依赖注入、事务管理但是它对Web请求处理和数据库操作不敏感你给它一个普通的Service类它能管得很好但HTTP请求进来该找谁处理、SQL语句该往哪里发它不管。SpringMVC是Spring家族里的Web层框架专门处理浏览器发来的请求。它做的事情是接收参数、调用Service、把结果封装好返回给前端。它和Spring本是同源所以在整合时天然亲近但要注意它有自己独立的子容器。MyBatis是持久层框架负责把Java对象和SQL语句之间做映射。它本身是可以脱离Spring独立运行的但真正的业务系统不会直接用MyBatis的原生API而是把它交给Spring管理让Spring来创建SqlSession、管理Mapper接口的代理对象、统一控制事务。这三者的关系可以这样理解Spring是心脏负责供给血液Bean依赖SpringMVC是嘴负责接客接收请求MyBatis是手脚负责干活操作数据库。整合的本质就是让这三个部分协同工作而不是各自为战。1.2 父子容器的概念90%的整合疑惑都出在这里SSM整合里最容易被忽略、又最常引发问题的就是Spring和SpringMVC双容器的问题。Spring的根容器也叫父容器由ContextLoaderListener加载负责管理Service、Dao、数据源、事务这些中后端组件。SpringMVC的子容器由DispatcherServlet加载负责管理Controller、视图解析器、处理器映射这些Web组件。子容器可以访问父容器的Bean但父容器不能反过来访问子容器的Bean。这句话是理解SSM整合的关键后面很多诡异问题都从这里冒出来。比如说你在SpringMVC的配置里扫描了Controller在根容器的配置里扫描了Service和Mapper这是标准做法。但如果你手一抖在根容器配置里把Controller也扫了或者反过来在SpringMVC配置里把Service也扫了就可能出现事务不生效、Bean重复创建、注入混乱的情况。原因是容器各管各的你在Web子容器里创建了一个Service但事务AOP是在父容器里配置的那个切面对子容器里的Service管不着。所以整合时要有清晰的边界意识根容器管Service、Dao、数据源、事务子容器只管Controller和Web相关的东西。2. 搭建工程的正确顺序依赖、目录和前期准备别在第一步就卡住2.1 依赖清单版本不是随便选的组合要稳我用Maven构建工程Java版本用1.8这个组合经过大量项目验证非常稳定。如果你用更高版本的Java或Spring 6配置方式会有变化但入门阶段不建议一上来就挑战最新版先把最经典的路走通后面再升级心里就有底了。pom.xml的依赖大致这样组织properties maven.compiler.source1.8/maven.compiler.source maven.compiler.target1.8/maven.compiler.target spring.version5.3.23/spring.version /properties dependencies !-- Spring核心模块 -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency !-- MyBatis和Spring整合包 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.10/version /dependency dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.7/version /dependency !-- MySQL驱动 -- dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.30/version /dependency !-- 数据库连接池 -- dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.15/version /dependency !-- Servlet API和JSP -- dependency groupIdjavax.servlet/groupId artifactIdjavax.servlet-api/artifactId version4.0.1/version scopeprovided/scope /dependency dependency groupIdjavax.servlet/groupId artifactIdjstl/artifactId version1.2/version /dependency !-- Jackson处理JSON -- dependency groupIdcom.fasterxml.jackson.core/groupId artifactIdjackson-databind/artifactId version2.13.4/version /dependency /dependenciesspring-jdbc这个依赖很多人会漏掉。没有它Spring管理数据源和事务时缺少JdbcTemplate等底层支持启动阶段容易报找不到类的错误。mybatis-spring这个包也需要注意版本它和MyBatis主版本要配合2.0.7对应MyBatis 3.5.x系列别随手拿一个老版本搭新MyBatis否则SqlSessionFactoryBean可能出兼容问题。如果不使用Maven手动往WEB-INF/lib里塞jar包也可以但依赖传递会让这件事变得异常痛苦。我建议现阶段一定要把Maven用熟SSM这种多依赖项目正是练习Maven管理能力的好场景。2.2 目录结构Mapper XML的位置决定你能不能找到它标准的Maven webapp目录结构是这样的src/main/java Java源码 ├── com/example/entity ├── com/example/mapper ├── com/example/service ├── com/example/service/impl ├── com/example/controller src/main/resources 配置文件 ├── jdbc.properties ├── spring-context.xml ├── spring-mvc.xml ├── mybatis-config.xml └── com/example/mapper/*.xml src/main/webapp Web应用根目录 ├── WEB-INF/web.xml ├── static └── WEB-INF/views这里有个新手常踩的坑把Mapper XML和Mapper接口放在同一个包路径下但放在src/main/java目录里。这样做的本意是方便管理但Maven默认不会把src/main/java下的XML文件打进classes目录编译后运行时找不到XML就会报“Invalid bound statement (not found)”。解决方案有两种。第一种把Mapper XML放到src/main/resources目录下保持包路径一致例如接口是com.example.mapper.UserMapperXML就放在resources/com/example/mapper/UserMapper.xml。第二种在pom.xml中配置resources插件让Maven把src/main/java下的XML文件也一并拷贝到classes目录。我推荐第一种最省心不用改pom配置不会出现意外。2.3 提前准备统一返回结果体和基础工具类整合完成后要测试接口如果每个Controller方法都手动拼接JSON代码会很难看。我建议在动手整合之前先准备一个简单的统一返回体比如Result类public class ResultT { private Integer code; private String message; private T data; public Result() {} public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(success); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } // getter/setter省略 }这个工具类会让后续Controller开发快很多也顺便熟悉一下泛型和静态工厂的用法属于顺手的事。3. 三份核心配置文件的“分工图”Spring管根SpringMVC管WebMyBatis被Spring托管3.1 jdbc.properties数据库连接参数单独拎出来写配置文件最忌讳把数据库账号密码硬编码在Spring XML里。一是改起来麻烦二是多个环境切换时要改动多处。单独建一个jdbc.properties用占位符引用这是标准做法。jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/ssm_demo?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse jdbc.usernameroot jdbc.password123456MySQL 8的驱动类是com.mysql.cj.jdbc.Driver不是老版本的com.mysql.jdbc.Driver。URL里必须有serverTimezone否则连接会报时区错误。这个细节如果你用的是MySQL 5.x驱动类才是org.gjt.mm.mysql.Driver或com.mysql.jdbc.Driver别混。3.2 spring-context.xml根容器的核心配方spring-context.xml是SSM整合的核心配置文件内容较多我按功能拆开来看。数据源配置。我选Druid因为它的监控能力好参数也直观context:property-placeholder locationclasspath:jdbc.properties/ bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName value${jdbc.driver}/ property nameurl value${jdbc.url}/ property nameusername value${jdbc.username}/ property namepassword value${jdbc.password}/ /beanSqlSessionFactoryBean配置。这里是MyBatis接入Spring的关键需要指定数据源、Mapper XML的位置、以及MyBatis全局配置bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource/ property nameconfigLocation valueclasspath:mybatis-config.xml/ property namemapperLocations valueclasspath:com/example/mapper/*.xml/ property nametypeAliasesPackage valuecom.example.entity/ /beantypeAliasesPackage指定实体类包名后Mapper XML里写resultType时就可以直接用类名不用写全限定类名。比如返回User直接写resultTypeUser就行。这个配置能省不少事我几乎每次都会用。Mapper扫描配置。这是让Spring自动为Mapper接口生成代理对象的开关bean classorg.mybatis.spring.mapper.MapperScannerConfigurer property namebasePackage valuecom.example.mapper/ property namesqlSessionFactoryBeanName valuesqlSessionFactory/ /bean注意这里用的是sqlSessionFactoryBeanName而不是sqlSessionFactory原因是MapperScannerConfigurer的初始化时机比较早如果直接用ref引用bean可能在数据源尚未完全初始化时就触发解析导致一连串诡异问题。用字符串名称方式可以延迟解析这是MyBatis-Spring官方推荐的做法不少老代码里用ref也能跑但新版下偶尔会出幺蛾子。组件扫描和事务配置context:component-scan base-packagecom.example.service/ bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource/ /bean tx:annotation-driven transaction-managertransactionManager/扫描范围只写com.example.service不写整个com.example。如果你把com.example全扫了会把Controller也扫进根容器这就是我之前说的重复创建问题一定注意。3.3 spring-mvc.xmlWeb子容器的配置spring-mvc.xml管的是Controller层和Web相关配置比根容器简洁很多。!-- 扫描Controller -- context:component-scan base-packagecom.example.controller/ !-- 开启注解驱动处理JSON、参数绑定等 -- mvc:annotation-driven/ !-- 静态资源放过 -- mvc:default-servlet-handler/ !-- 视图解析器 -- bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/views// property namesuffix value.jsp/ /beanmvc:annotation-driven这个配置不是摆设它注册了RequestMappingHandlerMapping、RequestMappingHandlerAdapter这些处理器映射适配器开启RequestBody、ResponseBody等注解支持。如果没有它你写的RestController可能直接不生效。早期SSM项目拿过来跑最常见的问题就在这里——漏了这一行所有Controller全部404而且Tomcat控制台不报错非常隐蔽。静态资源处理用mvc:default-servlet-handler让静态JS、CSS、图片文件放行给默认Servlet处理。如果不配置SpringMVC会拦截所有请求你访问static下的css文件也会被当成Controller映射去找结果当然404。如果你不用JSP而是做前后端分离把视图解析器这段改成使用ResponseBody返回JSON即可视图解析器可以保留也可以删掉不影响接口调用。3.4 web.xml让Tomcat认识Spring和SpringMVCweb.xml是SSM整合的入口也是很多人只关注Spring配置文件而忽略的部分。它要做三件事加载根容器、配置DispatcherServlet、设置编码过滤器。web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd version4.0 !-- 1. 加载Spring根容器 -- context-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-context.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener !-- 2. 配置DispatcherServlet -- servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping !-- 3. 字符编码过滤器 -- filter filter-nameencoding/filter-name filter-classorg.springframework.web.filter.CharacterEncodingFilter/filter-class init-param param-nameencoding/param-name param-valueUTF-8/param-value /init-param init-param param-nameforceEncoding/param-name param-valuetrue/param-value /init-param /filter filter-mapping filter-nameencoding/filter-name url-pattern/*/url-pattern /filter-mapping /web-appCharacterEncodingFilter里的forceEncoding设为true很关键。只设encoding的话如果请求头里已经带了自己的字符集过滤器不会强制覆盖。设置forceEncoding为true后请求和响应都会统一成UTF-8能有效解决POST提交中文乱码。DispatcherServlet的url-pattern选择“/”而不是“.do”也值得说一句。配成“/”会让静态资源也进入SpringMVC需要借助mvc:default-servlet-handler放行配成“.do”则后端接口必须带.do后缀现在主流做法已经不用这种风格了。我推荐用“/”配合静态资源放行配置比较符合当前开发的习惯。mybatis-config.xml在SSM整合里可以很精简只有一个全局配置?xml version1.0 encodingUTF-8? !DOCTYPE configuration PUBLIC -//mybatis.org//DTD Config 3.0//EN http://mybatis.org/dtd/mybatis-3-config.dtd configuration settings setting namemapUnderscoreToCamelCase valuetrue/ /settings /configuration这个配置开启了下划线转驼峰映射比如数据库字段user_name会自动映射到Java属性userName省去大量resultMap手工映射。当然如果你的查询返回的是Map类型或者是多表联查的复杂结果该写resultMap还是要写。4. 调用链落地从Mapper接口到Controller一层一层接线4.1 先写实体类和Mapper接口配置全部就绪后开始写代码。以一个用户模块为例。实体类对应数据库表命名和数据库字段保持一致风格public class User { private Integer id; private String userName; private String password; private String email; private Integer age; // getter/setter省略 }数据库表字段如果是user_name、password、email、age配合上面的mapUnderscoreToCamelCase配置userName就能自动对应user_name列。Mapper接口只需要定义方法不用写实现类这背后的原理是SqlSessionFactoryBean扫描到接口后用JDK动态代理生成Mapper代理对象public interface UserMapper { ListUser findAll(); User findById(Integer id); Integer insert(User user); Integer update(User user); Integer deleteById(Integer id); }对应的UserMapper.xml放在resources/com/example/mapper/目录下。namespace必须写接口的全限定类名否则MyBatis无法把XML里的语句和接口方法绑定起来。id必须和方法名一致parameterType和resultType要和方法的参数、返回类型匹配?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.mapper.UserMapper select idfindAll resultTypeUser SELECT id, user_name, password, email, age FROM user ORDER BY id /select select idfindById parameterTypeint resultTypeUser SELECT id, user_name, password, email, age FROM user WHERE id #{id} /select insert idinsert parameterTypeUser useGeneratedKeystrue keyPropertyid INSERT INTO user(user_name, password, email, age) VALUES(#{userName}, #{password}, #{email}, #{age}) /insert update idupdate parameterTypeUser UPDATE user SET user_name #{userName}, password #{password}, email #{email}, age #{age} WHERE id #{id} /update delete iddeleteById parameterTypeint DELETE FROM user WHERE id #{id} /delete /mapperinsert里的useGeneratedKeys和keyProperty是一对非常好用的配置能让自增主键回填到传入实体的id属性里插入后立刻拿到新数据的id省去一次查询。4.2 Service层的接口和实现类Service层做两件事衔接Controller和Mapper、承载业务逻辑。先定义接口public interface UserService { ListUser getAllUsers(); User getUserById(Integer id); Integer addUser(User user); Integer updateUser(User user); Integer deleteUser(Integer id); }实现类用Service注册到Spring容器再通过Autowired注入MapperService public class UserServiceImpl implements UserService { Autowired private UserMapper userMapper; Override Transactional public ListUser getAllUsers() { return userMapper.findAll(); } Override Transactional public User getUserById(Integer id) { return userMapper.findById(id); } Override Transactional public Integer addUser(User user) { return userMapper.insert(user); } Override Transactional public Integer updateUser(User user) { return userMapper.update(user); } Override Transactional public Integer deleteUser(Integer id) { return userMapper.deleteById(id); } }有人会问Service接口有必要定义吗在SSM整合阶段我建议保留。一方面是企业项目里经常用接口暴露服务、实现类隐藏细节另一方面在Root容器扫描时接口和实现类分离也更符合Spring代理的配置习惯。虽然现在Spring Boot流行以后简单场景下直接写实现类也没问题但SSM阶段的规范还是要练到位。Transactional注解加在实现类的方法上。这里有一种典型错误是只写Service忘了加Transactional注解然后质疑事务为什么不生效。另外事务注解要加在Service实现类上不是加在Mapper接口上因为事务管理的是业务方法不是单条SQL。4.3 Controller层的参数接收和返回Controller放在单独的包下用RestController或Controller加ResponseBody的方式返回JSON。现在主流做法是直接用RestController简化为REST风格接口RestController RequestMapping(/api/user) public class UserController { Autowired private UserService userService; GetMapping(/list) public ResultListUser list() { ListUser users userService.getAllUsers(); return Result.success(users); } GetMapping(/{id}) public ResultUser detail(PathVariable(id) Integer id) { User user userService.getUserById(id); return Result.success(user); } PostMapping(/add) public ResultVoid add(RequestBody User user) { userService.addUser(user); return Result.success(null); } PutMapping(/update) public ResultVoid update(RequestBody User user) { userService.updateUser(user); return Result.success(null); } DeleteMapping(/{id}) public ResultVoid delete(PathVariable(id) Integer id) { userService.deleteUser(id); return Result.success(null); } }这里的PostMapping加RequestBody接收前端传来的JSON字符串Jackson会自动把JSON反序列化成User对象。如果前端提交的是表单格式application/x-www-form-urlencoded就不要用RequestBody直接让SpringMVC做参数绑定把User作为方法参数不加注解即可。一个经验是接收JSON用RequestBody接收表单参数直接写对象参数两种方式千万别混用。混用的典型结果就是所有字段为null因为SpringMVC在解析JSON时不会同时再去解析表单参数。4.4 联调测试从启动到请求的完整路径工程配置完成后部署到Tomcat并启动。第一次运行如果一切顺利控制台会打印Spring容器初始化的日志包含Druid连接池的启动信息和Mapper的注册信息。用Postman或直接在浏览器地址栏输入GET http://localhost:8080/项目名/api/user/listPOST http://localhost:8080/项目名/api/user/addBody选择raw JSON内容为{userName:zhangsan,password:123456,email:zhangsantest.com,age:18}看到统一返回体里包含code: 200和data数据说明整条链路已经通了。如果你看到的不是200而是404检查访问路径中的项目名是否一致。IDEA中经常出现部署后项目名和访问路径对不上的情况可以在Tomcat的Deployment里直接把Application context设为“/”访问路径就变成http://localhost:8080/api/user/list看起来清爽不少。5. 启动即报错和运行期异常的典型场景从根因到排查链路5.1 空指针Service或Mapper注入为null这是SSM整合里最常见的错误。表现是请求进入Controller后调用Service时抛出NullPointerException提示userService为null。排查链路从三层依次推进第一确认Controller里是否写了Autowired或构造器注入。没写注解就不可能注入这个最基础的问题有时反而会忽略。第二确认Controller是否被SpringMVC容器扫描到。如果你只在spring-context.xml中写了component-scan base-packagecom.example但扫描范围没覆盖controller包就会导致Controller的Bean虽然存在但初始化时没有注入Service。建议把扫描范围拆成两处明确指定根容器扫service子容器扫controller不要图省事扫整个包。第三确认Service实现类是否加了Service注解。如果只实现了接口却忘了注册注解Spring容器里根本找不到这个Bean注入自然失败。在代码调试时可以先在ApplicationContext中查询ApplicationContext context new ClassPathXmlApplicationContext(spring-context.xml); System.out.println(context.getBean(userServiceImpl));能打印出对象说明Bean注册没问题问题一定出在Controller所在子容器的扫描或注入配置上。5.2 Invalid bound statement (not found)Mapper接口和XML没绑定这个错误的表现是请求处理时抛异常org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)。根因只有一个MyBatis根据Mapper接口的全限定名去找对应的Statement时没找到。为什么会找不到排查链路如下第一检查XML的namespace是否写成了接口的全限定类名。namespace少写一个字母、大小写不一致都会导致找不到绑定。第二检查XML文件是否被复制到了classes目录。打开项目的target/classes目录看看里面有没有对应的Mapper XML。没有的话就说明文件放在src/main/java下没被Maven编译打包按照前面说的方案把XML移到resources目录。第三检查spring-context.xml里的mapperLocations路径是否和实际目录匹配。比如你配置的是classpath:com/example/mapper/.xml但XML实际放在classpath:mapper/.xml那肯定匹配不上。用一个小命令验证XML是否加载成功启动时观察日志如果MyBatis成功加载了映射文件日志里会打印“Parsed X mapper configurations”这样一行字X就是加载到的Mapper XML数量。这个数字对不上说明路径或文件摆放有问题。5.3 请求404但控制台没有日志这个现象很恶心——Tomcat启动了页面也访问到了但就是404而且控制台里没有任何异常输出。常见的根因有两个。第一个是访问路径和Controller的RequestMapping对不上。比如RequestMapping写的是/api/user实际请求的是/api/User大小写不一样也找不到处理器。第二个是mvc:annotation-driven这行配置缺失导致SpringMVC没有注册处理映射器。这种情况下SpringMVC收到请求后找不到能处理它的Handler前端控制器会返回404但因为不是业务代码抛异常控制台完全无输出。排查这类问题有个通用思路先在DispatcherServlet上加一行日志配置把SpringMVC的请求映射日志级别调到DEBUG。具体是log4j.properties或logback.xml里设置org.springframework.web.servlet为DEBUG级别重启后就能看到SpringMVC尝试匹配的所有URL和处理器的映射情况。根据日志很快就能锁定是哪个环节出了问题。5.4 数据库中文乱码中文乱码问题在SSM整合里也高发而且可能发生在多个环节。排查时按数据流方向逐段验证浏览器请求到Controller - Controller参数传递 - Service传入Mapper - 数据库存储 - 查询结果返回浏览器。第一段是请求乱码在web.xml里配置CharacterEncodingFilterforceEncoding设置为true。如果你的项目没配这个过滤器POST表单或JSON里的中文传到后端直接就是乱码。第二段是数据库连接乱码jdbc.url里加useUnicodetruecharacterEncodingutf8。这一步很多人会忘记导致数据进入数据库就已经是问号。第三段是数据库本身字符集问题。建表时指定UTF-8CREATE TABLE user ( id INT AUTO_INCREMENT PRIMARY KEY, user_name VARCHAR(50), password VARCHAR(50), email VARCHAR(100), age INT ) DEFAULT CHARSETutf8mb4;MySQL 8默认可能已经是utf8mb4但老版本或自定义配置文件里不一定建议建表时显式声明。字符集用utf8mb4比utf8更好因为它能存emoji和更全面的Unicode字符。还有一处隐蔽的乱码来源是JSP页面本身的编码。在JSP顶部加% page contentTypetext/html;charsetUTF-8 languagejava %如果页面顶部没写这句JSP默认按ISO-8859-1处理页面上的中文显示成乱码不说提交给后端的中文也会受影响。乱码排查最笨也最有效的方法是打日志在Controller入口先打印一次参数在Mapper XML执行前打印一次看乱码从哪一步开始出现就能准确锁定问题环节。这个方法虽然土但比瞎猜高效得多。6. 再说几句SSM整合的进阶认知6.1 整合完成后再往哪个方向深入SSM整合跑通以后千万别觉得自己已经掌握了Java Web开发这只是基本功。往前再走一步你应该把注意力放到几个硬核方向上。事务的传播行为和隔离级别值得专门研究比如REQUIRED和REQUIRES_NEW有什么区别为什么在同一个类里调用另一个带Transactional的方法事务不生效。这块内容面试必问实际项目里也一定会用到。MyBatis的缓存机制一级缓存是SqlSession级别的二级缓存是Mapper级别的到底怎么配置、什么场景下可以用这里面坑很多特别是多表关联查询时缓存失效的边界条件。Spring的AOP原理为什么配置了tx:annotation-driven之后在UserServiceImpl上加一个Transactional就能起作用这背后是动态代理在支撑。理解了代理才能真正读懂Spring的很多设计。这些方向保证了你要真的有项目实战才能融会贯通。建议把一个SSM项目从头到尾自己敲一遍不是抄代码而是从建表、写实体、写Mapper、到调通接口每一步都完整走一遍遇到问题自己从头排查。6.2 SSM整合踩坑的通用排查心法总结一套实用的排查心法也是我自己调试了多少次以后才总结出来的。第一不要依赖看代码找错误。代码看十遍不如启动一次项目看日志。SSM整合报错时控制台的堆栈信息通常已经足够定位问题关键是不要被前半段冗长的Spring启动日志干扰直接看Caused by后面的内容。第二配置问题调试起来非常痛苦所以写配置时一定要小心谨慎。XML配置里少写一个结束标签、大小写不对、路径打错这些错误往往在运行时才暴露而且报错信息不直观。我的习惯是每写一个配置文件就检查一遍标签闭合和命名空间不要等全部写完再统一排查那样问题太多根本无从下手。第三善用IDEA和日志工具。IDEA中对Spring和MyBatis都有插件能看到Bean的依赖关系、Mapper的绑定情况。日志级别调低到DEBUG可以看到Spring初始化时读取了哪些配置文件、注册了哪些Bean、MyBatis加载了哪些Mapper XML这些信息对定位问题帮助巨大。第四如果项目实在跑不通不要死磕一个点。把配置全部注释掉一段一段恢复看看哪一步开始出现问题。比如先把Controller测试通、再把Service调通、最后接Mapper分步验证比整体排查快得多。SSM整合这件事说穿了就是让三个框架在同一个项目里各自安分守己地干活。Spring管好业务对象的生老病死SpringMVC管好请求的来龙去脉MyBatis管好SQL的映射执行中间用配置文件把它们串起来。搞清楚容器边界搞清楚配置文件谁加载谁搞清楚Mapper绑定原理你就能把SSM整合真正吃透。后面学Spring Boot时你会发现Spring Boot不过是用自动配置把这些东西藏了起来底层还是这一套逻辑。把SSM整合的每个细节弄明白学起Spring Boot的自动配置和starter机制来会轻松得多。