1. SSM框架整合概述
SSM(Spring + Spring MVC + MyBatis)是目前Java企业级开发中最主流的框架组合之一。作为一名有多年Java开发经验的工程师,我见证了这个技术栈从兴起到成为行业标准的过程。相比早期的SSH(Struts2 + Spring + Hibernate)组合,SSM具有更轻量、更灵活、更适合现代Web开发的特点。
在实际项目开发中,我发现很多新手开发者虽然能单独使用这三个框架,但在整合时总会遇到各种问题。本文将分享我在多个生产项目中总结出的SSM整合方案,这个方案已经经过多个百万级用户项目的验证,具有极高的稳定性和可维护性。
2. 环境准备与项目搭建
2.1 开发环境要求
- JDK 1.8+
- Maven 3.6+
- Tomcat 8.5+
- MySQL 5.7+
- IntelliJ IDEA/Eclipse
提示:强烈建议使用与教程一致的版本,不同版本间可能存在兼容性问题。
2.2 Maven项目初始化
使用以下命令创建Maven项目骨架:
mvn archetype:generate -DgroupId=com.example -DartifactId=ssm-demo -DarchetypeArtifactId=maven-archetype-webapp -DinteractiveMode=false2.3 目录结构调整
标准的SSM项目目录结构如下:
src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── example/ │ │ ├── controller/ │ │ ├── service/ │ │ │ ├── impl/ │ │ ├── dao/ │ │ ├── entity/ │ │ └── config/ │ ├── resources/ │ │ ├── spring/ │ │ ├── mybatis/ │ │ └── db.properties │ └── webapp/ │ ├── WEB-INF/ │ │ └── views/ │ └── resources/ └── test/ └── java/3. 核心框架配置
3.1 Spring配置
3.1.1 applicationContext.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation="http://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"> <!-- 扫描Service和DAO组件 --> <context:component-scan base-package="com.example"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- 加载数据库配置文件 --> <context:property-placeholder location="classpath:db.properties"/> <!-- 配置数据源 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> <!-- 配置事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!-- 开启注解事务 --> <tx:annotation-driven transaction-manager="transactionManager"/> </beans>3.1.2 db.properties
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/ssm_demo?useSSL=false&useUnicode=true&characterEncoding=UTF-8 jdbc.username=root jdbc.password=1234563.2 Spring MVC配置
3.2.1 spring-mvc.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xmlns:mvc="http://www.springframework.org/schema/mvc" xsi:schemaLocation="http://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/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> <!-- 扫描Controller组件 --> <context:component-scan base-package="com.example.controller"/> <!-- 配置视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean> <!-- 开启MVC注解驱动 --> <mvc:annotation-driven/> <!-- 静态资源处理 --> <mvc:resources mapping="/static/**" location="/static/"/> </beans>3.2.2 web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- 配置Spring上下文监听器 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 配置DispatcherServlet --> <servlet> <servlet-name>dispatcherServlet</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring/spring-mvc.xml</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>dispatcherServlet</servlet-name> <url-pattern>/</url-pattern> </servlet-mapping> <!-- 字符编码过滤器 --> <filter> <filter-name>characterEncodingFilter</filter-name> <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class> <init-param> <param-name>encoding</param-name> <param-value>UTF-8</param-value> </init-param> <init-param> <param-name>forceEncoding</param-name> <param-value>true</param-value> </init-param> </filter> <filter-mapping> <filter-name>characterEncodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> </web-app>3.3 MyBatis配置
3.3.1 mybatis-config.xml
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <!-- 开启驼峰命名自动映射 --> <setting name="mapUnderscoreToCamelCase" value="true"/> <!-- 打印SQL语句 --> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <typeAliases> <package name="com.example.entity"/> </typeAliases> </configuration>3.3.2 Spring整合MyBatis配置
在applicationContext.xml中添加:
<!-- 配置SqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/> <property name="mapperLocations" value="classpath:mybatis/mapper/*.xml"/> </bean> <!-- 配置Mapper扫描器 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.example.dao"/> </bean>4. 业务代码实现
4.1 实体类示例
package com.example.entity; import java.io.Serializable; public class User implements Serializable { private Integer id; private String username; private String password; private String email; // getters and setters // toString() }4.2 DAO层实现
4.2.1 接口定义
package com.example.dao; import com.example.entity.User; import java.util.List; public interface UserDao { User selectById(Integer id); List<User> selectAll(); int insert(User user); int update(User user); int delete(Integer id); }4.2.2 Mapper XML
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.dao.UserDao"> <resultMap id="userMap" type="User"> <id property="id" column="id"/> <result property="username" column="username"/> <result property="password" column="password"/> <result property="email" column="email"/> </resultMap> <select id="selectById" resultMap="userMap"> SELECT * FROM user WHERE id = #{id} </select> <select id="selectAll" resultMap="userMap"> SELECT * FROM user </select> <insert id="insert" useGeneratedKeys="true" keyProperty="id"> INSERT INTO user(username, password, email) VALUES(#{username}, #{password}, #{email}) </insert> <update id="update"> UPDATE user SET username=#{username}, password=#{password}, email=#{email} WHERE id=#{id} </update> <delete id="delete"> DELETE FROM user WHERE id=#{id} </delete> </mapper>4.3 Service层实现
4.3.1 服务接口
package com.example.service; import com.example.entity.User; import java.util.List; public interface UserService { User getUserById(Integer id); List<User> getAllUsers(); boolean addUser(User user); boolean updateUser(User user); boolean deleteUser(Integer id); }4.3.2 服务实现
package com.example.service.impl; import com.example.dao.UserDao; import com.example.entity.User; import com.example.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service @Transactional public class UserServiceImpl implements UserService { @Autowired private UserDao userDao; @Override public User getUserById(Integer id) { return userDao.selectById(id); } @Override public List<User> getAllUsers() { return userDao.selectAll(); } @Override public boolean addUser(User user) { return userDao.insert(user) > 0; } @Override public boolean updateUser(User user) { return userDao.update(user) > 0; } @Override public boolean deleteUser(Integer id) { return userDao.delete(id) > 0; } }4.4 Controller层实现
package com.example.controller; import com.example.entity.User; import com.example.service.UserService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.*; import java.util.List; @Controller @RequestMapping("/user") public class UserController { @Autowired private UserService userService; @GetMapping("/{id}") public String getUser(@PathVariable Integer id, Model model) { User user = userService.getUserById(id); model.addAttribute("user", user); return "user/detail"; } @GetMapping("/list") public String listUsers(Model model) { List<User> users = userService.getAllUsers(); model.addAttribute("users", users); return "user/list"; } @GetMapping("/add") public String toAddPage() { return "user/add"; } @PostMapping("/add") public String addUser(User user) { userService.addUser(user); return "redirect:/user/list"; } @GetMapping("/edit/{id}") public String toEditPage(@PathVariable Integer id, Model model) { User user = userService.getUserById(id); model.addAttribute("user", user); return "user/edit"; } @PutMapping("/edit") public String editUser(User user) { userService.updateUser(user); return "redirect:/user/list"; } @DeleteMapping("/{id}") public String deleteUser(@PathVariable Integer id) { userService.deleteUser(id); return "redirect:/user/list"; } }5. 视图层实现
5.1 用户列表页面 (list.jsp)
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <html> <head> <title>用户列表</title> </head> <body> <h2>用户列表</h2> <a href="/user/add">新增用户</a> <table border="1"> <tr> <th>ID</th> <th>用户名</th> <th>邮箱</th> <th>操作</th> </tr> <c:forEach items="${users}" var="user"> <tr> <td>${user.id}</td> <td>${user.username}</td> <td>${user.email}</td> <td> <a href="/user/${user.id}">查看</a> <a href="/user/edit/${user.id}">编辑</a> <a href="/user/${user.id}" onclick="return confirm('确定删除吗?')">删除</a> </td> </tr> </c:forEach> </table> </body> </html>5.2 用户详情页面 (detail.jsp)
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>用户详情</title> </head> <body> <h2>用户详情</h2> <p>ID: ${user.id}</p> <p>用户名: ${user.username}</p> <p>邮箱: ${user.email}</p> <a href="/user/list">返回列表</a> </body> </html>6. 测试与验证
6.1 单元测试
package com.example.service; import com.example.entity.User; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.util.List; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:spring/applicationContext.xml") public class UserServiceTest { @Autowired private UserService userService; @Test public void testCRUD() { // 测试新增 User user = new User(); user.setUsername("testUser"); user.setPassword("123456"); user.setEmail("test@example.com"); userService.addUser(user); // 测试查询 User dbUser = userService.getUserById(user.getId()); System.out.println(dbUser); // 测试更新 dbUser.setEmail("new@example.com"); userService.updateUser(dbUser); // 测试列表查询 List<User> users = userService.getAllUsers(); users.forEach(System.out::println); // 测试删除 userService.deleteUser(user.getId()); } }6.2 集成测试
- 启动Tomcat服务器
- 访问
http://localhost:8080/user/list - 测试各个功能点:
- 新增用户
- 查看用户详情
- 编辑用户信息
- 删除用户
7. 常见问题与解决方案
7.1 事务不生效问题
问题现象:Service层方法执行异常时,数据库操作没有回滚。
解决方案:
- 确保在applicationContext.xml中配置了事务管理器
- 确保开启了注解驱动
<tx:annotation-driven/> - 在Service实现类上添加
@Transactional注解 - 检查异常类型,默认只对RuntimeException回滚,可通过
@Transactional(rollbackFor=Exception.class)修改
7.2 MyBatis映射文件找不到
问题现象:启动时报错,提示找不到Mapper.xml文件。
解决方案:
- 检查
sqlSessionFactory的mapperLocations配置路径是否正确 - 确保Mapper.xml文件放在了resources/mybatis/mapper目录下
- 检查Mapper.xml的文件名是否与接口名一致
- Maven项目中,确保resources目录被正确识别(可在pom.xml中添加build配置)
7.3 静态资源访问不到
问题现象:CSS/JS/图片等静态资源无法加载。
解决方案:
- 在spring-mvc.xml中配置
<mvc:resources> - 确保静态资源放在了webapp/static目录下
- 检查路径引用是否正确,如
<link href="/static/css/style.css" rel="stylesheet">
7.4 中文乱码问题
问题现象:页面显示或数据库存储出现乱码。
解决方案:
- 确保web.xml中配置了CharacterEncodingFilter
- 检查数据库连接URL是否包含
useUnicode=true&characterEncoding=UTF-8 - 确保JSP页面设置了
<%@ page contentType="text/html;charset=UTF-8" %> - 检查Tomcat的server.xml中Connector配置了
URIEncoding="UTF-8"
8. 性能优化建议
8.1 数据库连接池优化
推荐使用Druid连接池,配置示例:
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close"> <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="minIdle" value="5"/> <property name="maxActive" value="20"/> <property name="maxWait" value="60000"/> <property name="timeBetweenEvictionRunsMillis" value="60000"/> <property name="minEvictableIdleTimeMillis" value="300000"/> <property name="validationQuery" value="SELECT 1"/> <property name="testWhileIdle" value="true"/> <property name="testOnBorrow" value="false"/> <property name="testOnReturn" value="false"/> <property name="poolPreparedStatements" value="true"/> <property name="maxPoolPreparedStatementPerConnectionSize" value="20"/> <property name="filters" value="stat,wall"/> </bean>8.2 MyBatis二级缓存
启用MyBatis二级缓存可以显著提高查询性能:
- 在mybatis-config.xml中配置:
<settings> <setting name="cacheEnabled" value="true"/> </settings>- 在Mapper.xml中添加缓存配置:
<cache eviction="LRU" flushInterval="60000" size="512" readOnly="true"/>8.3 SQL性能优化
- 为常用查询字段添加索引
- 避免使用SELECT *,只查询需要的字段
- 复杂查询考虑使用MyBatis的关联查询或多次简单查询
- 大数据量查询考虑使用分页插件
9. 项目扩展方向
9.1 集成Redis缓存
- 添加Redis依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency>- 配置RedisTemplate:
@Configuration public class RedisConfig { @Bean public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) { RedisTemplate<String, Object> template = new RedisTemplate<>(); template.setConnectionFactory(factory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(new GenericJackson2JsonRedisSerializer()); return template; } }9.2 添加Swagger接口文档
- 添加Swagger依赖:
<dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency>- 配置Swagger:
@Configuration @EnableSwagger2 public class SwaggerConfig { @Bean public Docket api() { return new Docket(DocumentationType.SWAGGER_2) .select() .apis(RequestHandlerSelectors.basePackage("com.example.controller")) .paths(PathSelectors.any()) .build() .apiInfo(apiInfo()); } private ApiInfo apiInfo() { return new ApiInfoBuilder() .title("SSM项目API文档") .description("SSM框架整合示例项目") .version("1.0") .build(); } }9.3 日志系统集成
推荐使用SLF4J + Logback组合:
- 添加依赖:
<dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency>- 配置logback.xml:
<?xml version="1.0" encoding="UTF-8"?> <configuration> <property name="LOG_HOME" value="/logs"/> <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${LOG_HOME}/ssm-demo.log</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${LOG_HOME}/ssm-demo.%d{yyyy-MM-dd}.log</fileNamePattern> <maxHistory>30</maxHistory> </rollingPolicy> <encoder> <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern> </encoder> </appender> <root level="INFO"> <appender-ref ref="STDOUT"/> <appender-ref ref="FILE"/> </root> <logger name="com.example" level="DEBUG"/> </configuration>10. 生产环境部署建议
10.1 安全配置
- 数据库密码加密:使用Jasypt等工具加密配置文件中的敏感信息
- 防止SQL注入:使用MyBatis的#{}参数绑定
- XSS防护:在Controller层对用户输入进行过滤
- CSRF防护:Spring Security提供了CSRF防护支持
10.2 性能调优
Tomcat优化:
- 调整连接器配置
- 启用NIO模式
- 合理设置线程池参数
JVM优化:
- 设置合适的堆内存大小
- 选择合适的垃圾收集器
- 配置GC日志监控
10.3 监控方案
- 使用Spring Boot Actuator监控应用健康状态
- 集成Prometheus + Grafana实现可视化监控
- 使用ELK收集和分析日志
- 配置告警机制,对异常情况进行及时通知
11. 总结与经验分享
在实际项目开发中,SSM框架的整合只是基础工作,更重要的是理解每个框架的设计理念和最佳实践。以下是我在多个项目中总结的一些经验:
- 分层清晰:严格遵循Controller-Service-Dao的分层架构,每层只关注自己的职责
- 事务控制:事务应放在Service层,且粒度要合理,避免大事务
- 异常处理:使用统一的异常处理机制,避免直接向上抛出原生异常
- 代码规范:遵循团队约定的代码规范,保持风格一致
- 测试驱动:为关键业务逻辑编写单元测试和集成测试
对于初学者,建议从简单的CRUD功能开始,逐步深入理解框架的各个组件。遇到问题时,多查阅官方文档和源码,这是最好的学习资料。