简介:这是一套面向Java Web初学者与需要二次开发者的SSM框架完整项目源码,基于Spring、Spring MVC与MyBatis三大框架搭建,采用MVC分层架构,适合用来理解企业级应用的依赖注入、请求分发与持久层映射机制。压缩包共226个文件,约56.11MB,其中32个java源文件与32个class文件构成核心代码,20个jsp页面与19个js、13个css负责前端展示,20个xml与4个properties承载Spring、Spring MVC、MyBatis及数据库连接配置,另有26个jar提供运行依赖。项目按DAO、Service、Controller三层组织,配合视图解析器完成请求到JSP的跳转,并包含AppInfo等初始化信息,目录结构清晰,便于按模块阅读与调试。目前已有2386人学习下载,读者可借此掌握SSM整合流程、配置要点与分层职责,并在此基础上扩展功能、积累Java Web实战经验。
1. 从“能跑就行”到“能交差”:一套 SSM 完整项目该有的样子
很多同学做 Java SSM 项目时,习惯先写代码再补文档,结果到了中期检查被导师问“你的分层依据是什么”“事务边界画在哪”,当场卡壳。这套 SSM 完整项目方案要解决的,就是“代码能跑但说不清设计”这个通病。它适合正在做毕设或课程设计的学生,也适合需要快速交付一个后台管理类项目的初级开发者。整套方案围绕 Spring + SpringMVC + MyBatis 三层结构展开,覆盖从需求拆解、数据库设计、模块编码到验收答辩的完整链路。下面按“需求立住 → 设计落地 → 避坑排查 → 验收交付”的顺序推演,每一章都给出可直接抄改的结构和关键实现。
2. 需求与选型:为什么 SSM 仍是教学项目的稳妥解
2.1 先锁定业务边界,再谈技术
SSM 项目最容易翻车的地方不是框架本身,而是需求没锁死就动手。我一般会先画一张业务边界图,把系统拆成“必须做”“可以做”“不做”三档。以典型的后台管理系统为例,必须做的通常是用户登录、角色权限、单表增删改查;可以做的包括文件上传、操作日志、数据导出;不做的比如分布式事务、消息队列、微服务拆分。这个划分直接决定了后面数据库表数量和接口数量,也决定了你答辩时能不能自圆其说。
需求锁定后,再倒推数据模型。用户表、角色表、权限表、业务主表这四类基本跑不掉。用户与角色是多对多,角色与权限也是多对多,所以中间表至少两张。业务主表根据选题不同,可能是订单、图书、设备、课程等。表设计阶段就要把外键关系、索引字段、逻辑删除标记想清楚,不然后面改表结构会连带改一堆 Mapper 和 Service。
提示:需求文档不用写几十页,但“功能清单 + 数据字典 + 接口列表”这三样必须齐。答辩时导师翻的就是这三样。
2.2 技术选型:版本组合与依赖关系
SSM 的版本组合是新手第一个坑。Spring 5.x 配 SpringMVC 5.x,MyBatis 3.5.x,JDK 用 8 或 11,Tomcat 用 8.5 或 9。不要混用 Spring 6 和 JDK 8,也不要把 MyBatis-Plus 和原生 MyBatis 的配置文件混在一起。下面是一个经过验证的 Maven 依赖片段,版本号可按需微调,但大版本要对齐。
<!-- pom.xml 关键依赖,版本对齐是前提 --> <properties> <spring.version>5.3.30</spring.version> <mybatis.version>3.5.14</mybatis.version> </properties> <dependencies> <!-- Spring 核心:IoC 容器和事务管理 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>${spring.version}</version> </dependency> <!-- SpringMVC:处理 Web 层请求映射 --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>${spring.version}</version> </dependency> <!-- MyBatis:持久层,与 Spring 整合需要 mybatis-spring --> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>${mybatis.version}</version> </dependency> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.1.2</version> </dependency> <!-- 数据库驱动与连接池 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.20</version> </dependency> </dependencies>这段依赖里,mybatis-spring是整合的关键,它负责把 MyBatis 的 SqlSession 交给 Spring 管理。Druid 连接池不是必须,但它的监控页面在调试阶段很好用。版本号不要盲目追新,Spring 5.3.x 是长期维护版,和 JDK 8/11 兼容性最稳。
2.3 分层职责:别把 Service 写成大杂烩
SSM 的标准分层是 Controller → Service → Mapper。Controller 只做参数接收和结果封装,不写业务判断;Service 承担事务和业务逻辑;Mapper 只负责 SQL 映射。常见错误是把权限校验、数据组装、异常处理全塞进 Controller,导致 Service 变成空壳。我的习惯是:Controller 里不超过 15 行,Service 方法按业务动作命名,Mapper 接口一个方法对应一条 SQL。
事务边界通常放在 Service 层,用@Transactional注解。注意默认只对 RuntimeException 回滚,如果捕获了异常又没重新抛出,事务不会生效。这个点在后面避坑章节会展开。
3. 总体架构与数据库设计:四层结构与核心表落地
3.1 架构分层与请求流转
整套系统按四层组织:表现层(JSP/HTML + Controller)、业务层(Service)、持久层(Mapper + XML)、数据层(MySQL)。请求从浏览器发出,经过 web.xml 中的 DispatcherServlet 拦截,根据 spring-mvc.xml 里的视图解析器和注解映射找到对应 Controller 方法,再调用 Service,Service 通过 Mapper 操作数据库,结果逐层返回。
这个流转过程听起来简单,但配置文件一多就容易乱。我一般把配置拆成三个文件:applicationContext.xml管 Spring 核心和事务,spring-mvc.xml管 Web 层扫描和视图解析,mybatis-config.xml管 MyBatis 全局设置。web.xml 里用 ContextLoaderListener 加载 Spring 容器,用 DispatcherServlet 加载 SpringMVC 容器。两个容器扫描的包要分开,否则事务注解可能不生效。
<!-- applicationContext.xml 关键片段:数据源与事务 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/ssm_demo?useSSL=false&serverTimezone=Asia/Shanghai"/> <property name="username" value="root"/> <property name="password" value="你的密码"/> </bean> <!-- 事务管理器绑定数据源 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/> <!-- Mapper 接口扫描,注意与 SpringMVC 扫描包错开 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.demo.mapper"/> </bean>basePackage指向 Mapper 接口所在包,Spring 会自动生成代理实现。tx:annotation-driven开启注解事务,Service 上的@Transactional才会被识别。数据源 URL 里的serverTimezone不写可能报时区错误,这是 MySQL 8 的常见问题。
3.2 核心表结构与字段说明
数据库设计直接决定 Mapper 的复杂度。下面给出四张核心表的字段定义,业务主表以“设备信息”为例,可替换成你的选题。
| 表名 | 字段 | 类型 | 说明 |
|---|---|---|---|
| sys_user | id | bigint | 主键,自增 |
| username | varchar(50) | 登录名,唯一索引 | |
| password | varchar(100) | 存储加密后的密码 | |
| status | tinyint | 1 启用 0 禁用 | |
| sys_role | id | bigint | 主键 |
| role_name | varchar(50) | 角色名 | |
| sys_user_role | user_id | bigint | 联合主键 |
| role_id | bigint | 联合主键 | |
| biz_device | id | bigint | 主键 |
| device_name | varchar(100) | 设备名称 | |
| device_code | varchar(50) | 编号,唯一 | |
| create_time | datetime | 创建时间 | |
| is_deleted | tinyint | 逻辑删除标记 |
密码字段不要存明文,通常用 MD5 加盐或 BCrypt。逻辑删除标记is_deleted在查询时统一加条件,避免物理删除导致关联数据丢失。联合主键在 MyBatis 里需要用@Param或 Map 传参,后面 Mapper 章节会演示。
3.3 接口清单与返回格式
接口设计要统一返回结构,否则前端处理起来很痛苦。我一般定义一个Result类,包含code、msg、data三个字段。code 用 200 表示成功,500 表示业务异常,401 表示未登录。Controller 方法统一返回Result,由 SpringMVC 自动转 JSON。
// 统一返回体,放在 common 包下 public class Result<T> { private int code; private String msg; private T data; public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.code = 200; r.msg = "操作成功"; r.data = data; return r; } public static <T> Result<T> fail(String msg) { Result<T> r = new Result<>(); r.code = 500; r.msg = msg; return r; } // getter/setter 省略 }这个类看起来简单,但能省掉大量重复的 JSON 拼接。注意泛型T让 data 可以是任意类型,分页数据、单个对象、列表都能装。前端只需要判断 code 是否为 200,不用关心 HTTP 状态码。
4. 模块实现:登录鉴权、分页查询与文件上传
4.1 登录鉴权:从密码校验到拦截器
登录模块是 SSM 项目里最能体现分层思想的模块。Controller 接收用户名密码,Service 查库比对,成功后把用户信息写入 Session。后续请求通过拦截器判断 Session 是否存在,不存在则跳转登录页。
// LoginController 核心方法 @PostMapping("/login") @ResponseBody public Result<User> login(String username, String password, HttpSession session) { User user = userService.login(username, password); if (user == null) { return Result.fail("用户名或密码错误"); } session.setAttribute("loginUser", user); return Result.success(user); }Service 层的login方法先按用户名查用户,再用加密工具比对密码。如果用户状态是禁用,直接返回 null。这里不要抛异常,否则前端拿不到友好提示。拦截器实现HandlerInterceptor接口,在preHandle里检查 Session,放行登录接口和静态资源。
// 登录拦截器,注册在 spring-mvc.xml 中 public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); if (session.getAttribute("loginUser") == null) { response.sendRedirect(request.getContextPath() + "/login.jsp"); return false; } return true; } }拦截器注册时要排除/login、/css/**、/js/**等路径,否则登录页自己都被拦。这个细节在调试时经常被忽略,导致页面无限重定向。
4.2 分页查询:PageHelper 的接入与参数传递
分页是后台系统的刚需。手写 limit 不是不行,但 PageHelper 插件能省掉 count 查询和分页逻辑。接入方式是在 MyBatis 配置里加插件,然后在 Service 里调用PageHelper.startPage。
<!-- mybatis-config.xml 中配置分页插件 --> <plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <property name="helperDialect" value="mysql"/> <property name="reasonable" value="true"/> </plugin> </plugins>reasonable设为 true 时,页码小于 1 查第一页,大于总页数查最后一页,避免空结果。Service 里这样写:
public PageInfo<Device> queryByPage(int pageNum, int pageSize, String keyword) { PageHelper.startPage(pageNum, pageSize); List<Device> list = deviceMapper.selectByKeyword(keyword); return new PageInfo<>(list); }PageInfo封装了总记录数、总页数、当前页数据,前端直接取用。注意startPage必须紧挨着查询方法,中间不要插入其他数据库操作,否则分页会作用到错误的 SQL 上。
4.3 文件上传与下载:路径配置与大小限制
文件上传在 SSM 里需要配置MultipartResolver。在 spring-mvc.xml 中定义 bean,设置最大文件大小和编码。
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="10485760"/> <property name="defaultEncoding" value="UTF-8"/> </bean>maxUploadSize单位是字节,10485760 即 10MB。Controller 方法用MultipartFile接收,保存到服务器磁盘或项目外的上传目录。保存路径不要放在 webapp 下,否则重新部署会丢失。我一般配一个upload.path属性,指向服务器绝对路径。
@PostMapping("/upload") @ResponseBody public Result<String> upload(MultipartFile file) throws IOException { if (file.isEmpty()) { return Result.fail("文件为空"); } String originalName = file.getOriginalFilename(); String suffix = originalName.substring(originalName.lastIndexOf(".")); String newName = UUID.randomUUID().toString() + suffix; File dest = new File(uploadPath + newName); file.transferTo(dest); return Result.success(newName); }文件名用 UUID 重命名,避免中文名和重名问题。下载时通过ResponseEntity<byte[]>返回字节流,设置Content-Disposition头。这个模块的坑主要在路径权限和文件类型校验,后面避坑章节会提。
5. 避坑与排查:SSM 项目里最容易翻车的五个点
5.1 事务不生效:注解加了但回滚没发生
现象是 Service 方法抛异常后数据仍然写入。原因通常是三个:一是 Service 类没有被 Spring 扫描到,@Transactional自然无效;二是异常被 catch 后没有重新抛出;三是事务方法不是 public。解决方式是检查 spring 配置里的component-scan是否包含 Service 包,catch 块里用throw new RuntimeException(e)重新抛出,确保方法修饰符是 public。
5.2 Mapper 绑定失败:Invalid bound statement
启动时报Invalid bound statement (not found),说明 Mapper 接口和 XML 没对上。原因可能是 XML 的 namespace 写错、方法名不一致、XML 没放在 resources 的对应目录下。解决方式是核对 namespace 是否等于接口全限定名,方法名和 id 是否一致,并在 pom 的 resources 配置里把**/*.xml包含进去,否则 Maven 打包会漏掉 XML。
5.3 中文乱码:GET 和 POST 表现不同
POST 乱码通常是 web.xml 里没配CharacterEncodingFilter,或者配了但forceEncoding没设 true。GET 乱码在 Tomcat 8 以后默认 UTF-8,但 URL 参数仍可能乱。解决方式是统一在 web.xml 加编码过滤器,并在数据库连接 URL 上加characterEncoding=utf8。JSP 页面头部也要声明pageEncoding="UTF-8"。
5.4 静态资源被拦截:CSS 和 JS 加载 404
现象是登录页样式全丢,控制台报 404。原因是 DispatcherServlet 映射了/,把静态资源请求也当成 Controller 处理了。解决方式是在 spring-mvc.xml 里加<mvc:default-servlet-handler/>或<mvc:resources mapping="/static/**" location="/static/"/>。前者交给容器默认 Servlet 处理,后者显式映射目录。
5.5 连接池耗尽:Too many connections
压测或长时间运行后报连接数超限。原因是 Druid 配置的maxActive过大或连接没归还。检查 Service 里是否有手动获取 Connection 没关闭的情况,MyBatis 和 Spring 整合后不需要手动关连接。把maxActive设为 20 左右,加removeAbandoned="true"和removeAbandonedTimeout="180"让泄漏连接自动回收。
6. 验收与答辩:怎么把项目讲成一份合格方案
验收前一周,我一般会做三件事:跑通全流程、整理文档、模拟提问。全流程不是只点一遍页面,而是从登录开始,把每个模块的正常和异常路径都走一遍,记录报错和修复方式。文档按“需求 → 设计 → 实现 → 测试”四段整理,每段配一张图或一张表。模拟提问则把导师可能问的问题列出来,比如“为什么用 SSM 不用 SpringBoot”“事务边界怎么定的”“分页插件原理是什么”,每个问题准备三句话以内的回答。
答辩时有一个技巧:先讲业务价值,再讲技术实现。很多同学一上来就讲 Spring 的 IoC 和 AOP,导师听多了会烦。不如先说“这个系统解决了设备信息手工登记效率低的问题”,再展开“用 SSM 分层是为了让业务逻辑和 SQL 解耦,方便后续维护”。这样导师会觉得你是在做项目,不是在背框架。
下面这张表是我常用的验收自查清单,按模块逐项打勾,避免遗漏。
| 检查项 | 通过标准 | 常见问题 |
|---|---|---|
| 登录鉴权 | 未登录跳转,登录后放行 | 拦截器路径配置错误 |
| 增删改查 | 每张业务表都有对应页面 | 删除未做逻辑删除 |
| 分页 | 页码切换正常,总数正确 | startPage 位置错误 |
| 文件上传 | 大小限制生效,路径可配 | 上传目录在 webapp 下 |
| 事务 | 异常时数据回滚 | 异常被吞掉 |
| 日志 | 关键操作有记录 | 只打 System.out |
答辩前把这张表过一遍,基本不会出大问题。还有一点,代码里不要留TODO和注释掉的调试代码,导师翻到会扣印象分。
从那以后我每次带项目,都强制在验收前三天做一次“陌生人测试”——找一个没参与项目的同学,让他按文档从零部署一遍,卡在哪就补哪。这个习惯帮我省掉了无数次现场翻车。希望这套结构能帮你把 SSM 项目从“能跑”推到“能交差”,需要完整源码和文档的可以按项目名自取。
本文还有配套的精品资源,点击获取