简介:SSM整合即Spring、Spring MVC与MyBatis三大Java开源框架的集成,广泛应用于企业级Web开发。本示例通过完整的配置与代码实现,展示如何将三者协同工作,完成请求处理、业务逻辑控制和数据库操作。项目基于Maven构建,涵盖环境配置、三层架构设计、依赖注入、数据源管理及Mapper接口集成等关键环节,帮助开发者掌握SSM整合的核心流程与实践技巧,为Java Web应用开发提供坚实基础。
SSM框架整合全流程深度解析与实战
在现代Java企业级开发的江湖里,SSM(Spring + Spring MVC + MyBatis)就像那套经典拳法——看似朴实无华,实则内力深厚。它不靠花哨的概念堆砌,而是凭借 高内聚、低耦合 的设计哲学,在无数生产系统中稳扎稳打。😎 你有没有发现,哪怕微服务和Spring Boot已经风靡多年,很多老项目依然跑着这套组合?因为它足够稳定、足够灵活,更重要的是——够“接地气”。
可别小看这仨字母:
- Spring 是大脑🧠,管全局Bean调度与依赖注入;
- Spring MVC 是门面担当,负责接客(HTTP请求)和送客(响应渲染);
- MyBatis 则是数据库里的“特工”,精准执行SQL任务,还不留痕迹。
今天咱们就来一场沉浸式演练,从零搭建一个SSM项目,不只是贴代码、走流程,更要搞清楚每个环节背后的 设计意图 和 避坑指南 。准备好了吗?🚀
构建基石:Maven工程结构与依赖管理的艺术
先问一句:为什么我们非得用Maven?直接下载jar包不香吗?当然不香!那样你会陷入“依赖地狱”——版本冲突、重复引入、打包臃肿……简直是开发者的噩梦。😱
而Maven呢?它就像一位严谨的建筑师,帮你规划好每一块砖的位置,还自带“自动采购系统”(中央仓库),让你专注写业务逻辑。
标准目录结构:约定优于配置的力量
打开任何一个标准Maven项目,你会看到这样的布局:
my-ssm-project/
├── pom.xml
├── src/
│ ├── main/
│ │ ├── java/ # Java源码
│ │ ├── resources/ # 配置文件
│ │ └── webapp/ # Web资源(JSP、静态文件)
│ └── test/
│ ├── java/ # 单元测试
│ └── resources/ # 测试配置
└── target/ # 编译输出(自动生成)
这个结构不是随便定的,它是 Maven默认约定 的一部分。比如 src/main/java 下的所有类会被自动编译到 classpath, src/main/resources 中的内容也会被打包进最终的WAR/JAR文件。
💡 小贴士: webapp 目录下放的是Web应用专属资源,如 WEB-INF/web.xml 、JSP页面、CSS/JS等。当你运行 mvn package 时,Maven会调用 maven-war-plugin 把整个 webapp 打成WAR包,丢给Tomcat部署。
至于测试部分, src/test/java 和 src/test/resources 是独立存在的——它们只在测试阶段生效,不会污染主程序环境。这种隔离机制对单元测试太友好了!
pom.xml :项目的灵魂所在
如果说Maven是建筑师,那 pom.xml 就是它的设计蓝图。来看一段典型的配置:
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example.ssm</groupId>
<artifactId>ssm-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<spring.version>5.3.21</spring.version>
<mybatis.version>3.5.11</mybatis.version>
<jdk.version>11</jdk.version>
</properties>
<!-- 更多内容见下文 -->
</project>
几个关键字段解释一下:
- groupId :组织标识,一般反向域名,比如 com.company.project
- artifactId :项目名,代表具体模块,如 user-service
- version :遵循语义化版本规范(MAJOR.MINOR.PATCH), SNAPSHOT 表示开发中版本
- packaging :打包类型, jar (普通库)、 war (Web应用)、 pom (父模块)
注意到 <properties> 了吗?这里集中管理了版本号,好处显而易见:你想升级Spring版本?改一处就行,不用满屏找 5.3.21 😅
引入SSM三大件:别漏掉关键依赖
要让SSM跑起来,这几个核心依赖缺一不可:
| 框架组件 | Maven坐标 | 作用说明 |
|---|---|---|
| Spring Context | org.springframework:spring-context:5.3.21 | IoC容器核心 |
| Spring Web MVC | org.springframework:spring-webmvc:5.3.21 | MVC请求处理 |
| MyBatis | org.mybatis:mybatis:3.5.11 | ORM持久层 |
| MySQL驱动 | mysql:mysql-connector-java:8.0.33 | 数据库连接 |
| MyBatis-Spring | org.mybatis:mybatis-spring:2.0.7 | 整合桥梁 |
完整配置如下:
<dependencies>
<!-- Spring 核心 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.version}</version>
</dependency>
<!-- MyBatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
<!-- Servlet API -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
</dependencies>
重点来了👇:
- 所有Spring相关模块统一使用 ${spring.version} 变量,防止版本错配导致 NoSuchMethodError 。
- servlet-api 设置为 <scope>provided</scope> ,意思是运行时由Tomcat提供,不打进WAR包,避免类加载冲突。
- mybatis-spring 是关键粘合剂!没有它,Spring根本不知道怎么托管 SqlSessionFactory 。
依赖冲突怎么办?Maven也有“仲裁规则”
现实总是残酷的。当你引入多个第三方库时,很可能出现同一个类库的不同版本共存问题。比如A依赖Log4j 1.x,B依赖SLF4J+Logback,这时候咋办?
Maven有一套内置的 依赖调解机制 :“最短路径优先 + 第一声明优先”。你可以通过命令查看依赖树:
mvn dependency:tree
输出可能像这样:
[INFO] com.example.ssm:ssm-demo:war:1.0.0-SNAPSHOT
[INFO] +- org.springframework:spring-context:jar:5.3.21:compile
[INFO] | \- org.springframework:spring-expression:jar:5.3.21:compile
[INFO] +- org.springframework:spring-webmvc:jar:5.3.21:compile
[INFO] | \- org.springframework:spring-web:jar:5.3.21:compile
[INFO] \- mysql:mysql-connector-java:jar:8.0.33:runtime
如果发现冲突,最佳实践是使用 BOM(Bill of Materials) 来统一管理:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>5.3.21</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
导入Spring官方的BOM后,所有子项目版本都会自动协调一致,再也不用手动维护一堆版本号啦!👏
graph TD
A[Maven Project] --> B[pom.xml]
B --> C{Dependency Resolution}
C --> D[Direct Dependencies]
C --> E[Transitive Dependencies]
D --> F[Conflict Detected?]
E --> F
F -->|Yes| G[Apply Nearest-Win Strategy]
F -->|No| H[Build Success]
G --> I[Manual Version Override]
I --> J[Use <dependencyManagement> or <exclusions>]
J --> K[Final Classpath]
这张图清晰展示了Maven如何一步步构建最终的类路径。记住:自动化虽好,但关键时刻还得人工干预!
工欲善其事,必先利其器:IDE与服务器集成
再厉害的武功也得有趁手兵器。接下来我们把项目导入IDE,并配置Tomcat,让代码真正“活”起来。
IntelliJ IDEA导入Maven项目:三步搞定
- 打开IDEA →
File→Open→ 选择你的项目根目录下的pom.xml - IDE会提示“Import as Maven project”,勾选“Auto-import”
- 等待索引完成,结构就出来了 ✅
⚠️ 如果没识别成功?右侧面板点Maven刷新图标,或者手动执行:
mvn clean compile
建议开启“自动导入”,这样每次改完 pom.xml ,IDE就会自动下载新依赖,效率飞起!
Tomcat集成:让Web应用跑起来
以IntelliJ为例:
- 进入
Run/Debug Configurations - 添加新的
Tomcat Server→ Local - 指定Tomcat安装路径
- 在
Deployment中添加当前WAR项目 - 设置Application context为
/ssm-demo
启动后访问 http://localhost:8080/ssm-demo ,看到欢迎页就说明成功啦!
JDK版本别搞错:编译插件必须配
很多人遇到这个问题:明明写了Java 11的新特性(比如var),却报错说不支持?原因就是Maven默认可能用了低版本JDK编译。
解决方法:配置 maven-compiler-plugin
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>11</source>
<target>11</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
-
source:源码兼容级别 -
target:生成字节码的目标版本 -
encoding:推荐设为UTF-8,避免中文乱码
多环境配置?用profiles轻松切换
不同环境下数据库地址不一样,难道每次都要手动改配置文件?当然不行!Maven提供了 profile 功能:
<profiles>
<profile>
<id>dev</id>
<properties>
<env>development</env>
<db.url>jdbc:mysql://localhost:3306/dev_db</db.url>
</properties>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
</profile>
<profile>
<id>prod</id>
<properties>
<env>production</env>
<db.url>jdbc:mysql://prod-server:3306/prod_db</db.url>
</properties>
</profile>
</profiles>
打包时指定环境即可:
mvn clean package -Pprod
配合资源过滤还能动态替换占位符:
<build>
<resources>
<resource>
<directory>src/main/resources</directory>
<filtering>true</filtering>
<includes>
<include>**/application.properties</include>
</includes>
</resource>
</resources>
<filters>
<filter>src/main/filters/filter-${env}.properties</filter>
</filters>
</build>
真正做到“一次构建,处处部署”!🎯
Spring与MyBatis深度融合:数据访问层的灵魂塑造
现在进入重头戏——Spring和MyBatis的整合。这两个框架一个管对象生命周期,一个管SQL执行,怎么才能无缝协作?
Spring上下文初始化:applicationContext.xml 的使命
applicationContext.xml 是Spring的主配置文件,通常由 ContextLoaderListener 加载,形成“根上下文”(Root ApplicationContext)。这里面放的是Service、DAO、DataSource这些非Web组件。
注解 vs XML:哪种方式更好?
以前全是XML配置:
<bean id="userService" class="com.example.service.UserServiceImpl">
<property name="userDao" ref="userDao"/>
</bean>
虽然直观,但项目一大就变得冗长难维护。于是Spring推出了注解驱动:
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserDao userDao;
}
哪个更优?其实各有千秋:
| 配置方式 | 优点 | 缺点 | 推荐场景 |
|---|---|---|---|
| XML配置 | 集中管理、易于调试 | 冗长、维护成本高 | DataSource、TxManager等核心Bean |
| 注解驱动 | 简洁高效、贴近代码 | 分散难控 | 普通业务组件(Service、DAO) |
✅ 最佳实践:基础设施用XML,业务组件用注解。
组件扫描:别忘了排除Controller!
启用注解扫描很简单:
<context:component-scan base-package="com.example.service, com.example.dao">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:exclude-filter>
</context:component-scan>
⚠️ 注意!一定要排除 @Controller ,否则它会被加载进根上下文,造成双重注册问题,事务也可能失效!
graph TD
A[启动Spring容器] --> B{加载 applicationContext.xml}
B --> C[解析 <context:component-scan>]
C --> D[扫描 base-package 路径下的所有类]
D --> E[检查是否含@Component/@Service/@Repository]
E --> F[创建BeanDefinition并注册]
F --> G[根据@Autowired注入依赖]
G --> H[完成Bean初始化]
这就是IoC容器的工作流,是不是很清晰?
数据源与事务管理:Druid + TransactionManager
数据源是连接数据库的桥梁。国内首选当然是阿里开源的 Druid ,性能强、监控全、还能防SQL注入!
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
<property name="initialSize" value="5"/>
<property name="maxActive" value="20"/>
<property name="filters" value="stat,wall"/>
</bean>
对应的 jdbc.properties :
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/ssm_demo?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai
jdbc.username=root
jdbc.password=123456
然后配上事务管理器:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
从此就可以愉快地使用 @Transactional 了:
@Service
@Transactional(rollbackFor = Exception.class)
public class UserServiceImpl {
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
// 两个操作要么全成功,要么全回滚
}
}
📌 提醒:自调用会导致事务失效!比如 this.transferMoney() 不会触发代理,务必注意。
MyBatis整合:SqlSessionFactoryBean与Mapper扫描
MyBatis需要被纳入Spring容器管理,关键角色是 SqlSessionFactoryBean :
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
其中 mybatis-config.xml 可以做一些全局设置:
<configuration>
<typeAliases>
<package name="com.example.entity"/>
</typeAliases>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
</configuration>
最后一步:自动扫描Mapper接口!
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.example.dao"/>
</bean>
或者用Java配置更简洁:
@Configuration
@MapperScan("com.example.dao")
public class MyBatisConfig {}
Spring会为每个Mapper接口生成代理对象,拦截方法调用并执行对应SQL。
graph LR
A[Spring容器启动] --> B[加载 applicationContext.xml]
B --> C[创建 DataSource]
C --> D[构建 SqlSessionFactoryBean]
D --> E[读取 mybatis-config.xml]
E --> F[加载 Mapper XML 文件]
F --> G[扫描 Mapper 接口 @MapperScan]
G --> H[生成 Mapper 代理对象]
H --> I[注入 Service 层 @Autowired]
I --> J[完成整合]
这条链路走通了,你的数据访问层才算真正立住了!
Spring MVC:请求是如何从浏览器抵达数据库的?
前端发个请求,后端返回数据,中间到底经历了什么?让我们揭开 DispatcherServlet 的神秘面纱。
DispatcherServlet:前端控制器的诞生
一切始于 web.xml :
<servlet>
<servlet-name>dispatcher</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcher</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
-
contextConfigLocation指定了Spring MVC专属配置文件; -
load-on-startup=1让它随容器启动立即加载; -
<url-pattern>/</url-pattern>表示拦截所有请求(除.jsp外);
⚠️ 但这会导致静态资源也被拦截!解决方案有两个:
<!-- 方案1:允许默认Servlet处理静态资源 -->
<mvc:default-servlet-handler />
<!-- 方案2:显式映射静态资源路径 -->
<mvc:resources mapping="/static/**" location="/WEB-INF/static/" />
推荐两者都加上,稳妥!
上下文分层:父子容器的秘密
SSM项目通常有两个Spring容器:
- 根上下文 :由 ContextLoaderListener 创建,包含Service、DAO;
- Web上下文 :由 DispatcherServlet 创建,包含Controller、HandlerMapping;
配置如下:
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
DispatcherServlet 初始化时会以根上下文为父级,形成父子关系。这意味着Controller可以引用Service,但反之不行,层次分明!
sequenceDiagram
participant Container as Servlet Container
participant Listener as ContextLoaderListener
participant RootCtx as Root ApplicationContext
participant Dispatcher as DispatcherServlet
participant WebCtx as Web ApplicationContext
Container ->> Listener: 启动时触发 contextInitialized()
Listener ->> RootCtx: 创建并加载 applicationContext.xml
Listener ->> Container: 绑定 RootCtx 到 ServletContext
Container ->> Dispatcher: 初始化 load-on-startup=1
Dispatcher ->> WebCtx: 创建子上下文,设置 RootCtx 为父级
WebCtx ->> Dispatcher: 注册 HandlerMapping, Controller 等 Bean
Note right of WebCtx: 子上下文可引用父上下文中<br/>的 Service/DAO Bean
这种设计特别适合多前端共用同一套服务层的场景。
控制器与请求映射:RESTful风格实践
写Controller现在都是注解驱动了:
@Controller
@RequestMapping("/user")
public class UserController {
@Autowired
private UserService userService;
@GetMapping("/list")
public String listUsers(Model model) {
model.addAttribute("users", userService.findAll());
return "user/list"; // 解析为 /WEB-INF/views/user/list.jsp
}
@GetMapping("/{id}")
@ResponseBody
public User getUserById(@PathVariable Long id) {
return userService.findById(id);
}
}
-
@GetMapping等价于@RequestMapping(method=GET) -
@PathVariable提取路径变量 -
@RequestParam接收查询参数 -
@RequestBody接收JSON体(需Jackson依赖)
RESTful设计原则也要遵守:
- 资源用名词复数: /users
- 操作用HTTP方法:GET查、POST创、PUT更、DELETE删
- 返回标准状态码:200 OK、201 Created、404 Not Found
视图解析与JSP渲染:传统但有效
虽然前后端分离是趋势,但某些后台管理系统仍用JSP。这时需要配置视图解析器:
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/views/"/>
<property name="suffix" value=".jsp"/>
</bean>
然后Controller返回逻辑视图名,比如 "user/list" ,就会被拼成 /WEB-INF/views/user/list.jsp 。
JSP中可以用EL表达式和JSTL:
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<c:forEach items="${users}" var="user">
${user.name}
</c:forEach>
graph TD
A[客户端请求] --> B{DispatcherServlet}
B --> C[HandlerMapping查找Controller]
C --> D[调用Controller方法]
D --> E[返回ModelAndView]
E --> F[ViewResolver解析视图名]
F --> G[JSP引擎编译执行]
G --> H[输出HTML响应]
H --> I[客户端浏览器渲染]
整个流程一目了然。
实战演练:DAO→Service→Controller全链路打通
最后我们来跑一遍真实场景。
DAO层:Mapper XML编写规范
<mapper namespace="com.example.dao.UserMapper">
<select id="findById" resultType="User">
SELECT * FROM users WHERE id = #{id}
</select>
<insert id="insertUser" useGeneratedKeys="true" keyProperty="id">
INSERT INTO users(name, email) VALUES(#{name}, #{email})
</insert>
</mapper>
注意:
- namespace 必须等于接口全限定名;
- useGeneratedKeys 获取自增ID;
- #{} 是预编译占位符,防SQL注入。
动态SQL:条件查询利器
<select id="findUsers" parameterType="map">
SELECT * FROM users
<where>
<if test="name != null">AND name LIKE #{name}</if>
<if test="age != null">AND age >= #{age}</if>
</where>
</where>
</select>
<where> 会智能处理AND/OR前缀, <if> 实现条件拼接。
Service层:事务边界控制
@Service
@Transactional
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public void createUser(User user) {
userMapper.insertUser(user);
// 其他业务逻辑...
}
}
确保事务注解加在public方法上,且不要内部调用。
联调测试:Postman + 断点调试
启动Tomcat后,用Postman发请求:
POST /user/create HTTP/1.1
Content-Type: application/json
{
"name": "李四",
"email": "lisi@example.com"
}
预期返回200,并在数据库验证插入成功。
IDE里设断点跟踪调用栈:
1. UserController.create()
2. UserService.createUser()
3. UserMapper.insertUser()
4. SQL执行 ← MyBatis绑定参数 ← Spring事务管理
层层深入,逻辑闭环。
常见问题诊断手册 🛠️
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 404错误 | url-pattern 配置错误 | 改为 / 或 /api/* |
| No qualifying bean | 组件未加注解或包扫描路径错 | 检查 @ComponentScan 和注解 |
| 事务未生效 | 方法非public或this调用 | 改为public,避免自调用 |
| 中文乱码 | 未设置字符编码过滤器 | 添加 CharacterEncodingFilter |
| 参数绑定失败 | @RequestBody 缺失或JSON格式错 | 检查Content-Type和字段匹配 |
开启MyBatis日志查看SQL:
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
整套流程走下来,你会发现SSM并不是过时的技术,而是一套经过时间检验的成熟方案。只要你理解了它的设计理念和协作机制,就能驾驭自如,甚至在Spring Boot时代也能触类旁通。💪
所以,别再说“我都用Boot了还学啥XML”,真正的高手,永远懂得从底层原理出发,看清每一行代码背后的故事。🔥
简介:SSM整合即Spring、Spring MVC与MyBatis三大Java开源框架的集成,广泛应用于企业级Web开发。本示例通过完整的配置与代码实现,展示如何将三者协同工作,完成请求处理、业务逻辑控制和数据库操作。项目基于Maven构建,涵盖环境配置、三层架构设计、依赖注入、数据源管理及Mapper接口集成等关键环节,帮助开发者掌握SSM整合的核心流程与实践技巧,为Java Web应用开发提供坚实基础。
943

被折叠的 条评论
为什么被折叠?



