简介:这是一套面向Java初学者与SSM框架入门开发者的完整人事管理系统实战源码,适用于课程设计、毕业设计及企业级管理类系统学习。系统基于Spring+SpringMVC+MyBatis主流整合架构,覆盖用户、部门、职位、员工、公告、下载中心等六大核心模块,完整实现CRUD、文件上传下载、权限拦截(AuthorizedInterceptor)、前端动态交互(JQuery+Layui)等典型业务场景。压缩包共284个文件,含29个核心Java类(如EmployeeController、NoticeController)、18个JSP页面、47个Jar依赖、20个JS脚本及1个SQL建库脚本,结构清晰、分层规范,便于理解MVC职责划分与SSM整合逻辑。资源包大小30.77MB,已附详细导入运行文档(含MySQL数据库初始化与IDEA/Eclipse配置说明)。目前已有1475人学习下载,配套代码经实测可直接运行,是掌握Java Web企业开发全流程的高实用性参考项目。 先说个实在的,SSM这套组合(Spring + SpringMVC + MyBatis)在Java后端里属于“老牌黄金搭档”,虽然现在Spring Boot大行其道,但只要你翻招聘要求或翻毕设题目,就会发现SSM依然是绕不开的坎。尤其是一个基于SSM框架开发的人事管理系统,它几乎把JavaWeb开发的核心知识点全串起来了:三层架构、依赖注入、AOP事务、ORM映射、MVC分发、JSP渲染、MySQL表设计……一个项目全跑一遍,比你看十遍教程都管用。
这个项目源码我前前后后跑了很多遍,也帮人排查过各种启动问题。今天不整虚的,直接把这套人事管理系统的设计思路、数据库表结构、SSM整合细节、源码跑通步骤、高频踩坑记录全部摊开讲。不管你是准备毕设、做课程设计,还是想靠这个项目补一补JavaWeb的底子,这篇都能让你少走不少弯路。
1. 项目整体设计与技术选型思路
1.1 为什么这个项目首选SSM而非Spring Boot
聊技术选型得结合场景。人事管理系统这种典型的业务型CRUD项目,核心是员工信息管理、部门组织架构、考勤薪资记录、系统用户登录权限,它不涉及高并发、不涉及分布式,就是标准的B/S架构数据管理系统。这种场景下用SSM反而比Spring Boot更合适——因为练习价值更高。
Spring Boot虽然“零配置”很爽,但它把大量底层细节封装好了,新手反而学不到东西。SSM需要手动整合Spring容器、手动配置SpringMVC的分发器、手动写MyBatis的Mapper映射,每一步配置都能逼你搞清楚“框架到底在干什么”。等你把SSM这套手动挡玩明白了,再去看Spring Boot的自动配置,完全是降维打击。
另外从实用角度看:目前大量老系统的维护、高校课程设计、毕业设计题目库,仍然以SSM为主。你拿着一个SSM人事管理系统,无论用来应付毕设答辩,还是写进简历作为项目经验,认知度都非常高。面试官听到“SSM项目”通常会往下问“Spring IOC原理”“MyBatis动态SQL”“SpringMVC流程”,这些都是你亲手搭过的,答起来心里有底。
1.2 人事管理系统的功能模块与权限边界划分
人事管理系统不是一个单纯“增删改查”demo,真正要落地,至少要把下面这几个模块拆清楚:
- 员工管理模块:员工信息的增删改查、按姓名/工号/部门筛选、员工离职状态变更。这是系统的核心主数据,所有其他模块都以员工表为基准。
- 部门管理模块:部门树形结构维护,部门新增、合并、撤销,以及部门下的员工人数统计。
- 考勤管理模块:每日打卡记录导入或录入,按月份汇总员工出勤天数、迟到早退次数。
- 薪资管理模块:基础工资、岗位工资、奖金、五险一金扣除项,按月生成薪资报表。
- 系统管理模块:用户登录、修改密码、权限拦截。一般分为管理员和普通HR两种角色,管理员能看全部数据,普通HR只能操作自己负责的部分。
很多入门源码只做了前两个模块,考勤和薪资是“硬凑”的假模块,数据不做关联。但你拿来做项目的话,我建议至少把员工、部门、登录权限这三个模块做成完整闭环,考勤和薪资做基础版本即可。这样既控制了开发量,又能展示你“对业务有理解”,毕竟权限和主数据才是HR系统的灵魂。
1.3 技术栈分层与数据库交互路径
SSM项目的代码分层非常固定,也是这套框架“规范”的来源。一个用户请求从浏览器发出来,最终落库再返回,走的是这条路:
浏览器 → JSP页面发请求 → SpringMVC前端控制器DispatcherServlet → HandlerMapping找到对应Controller → Controller调用Service接口 → Service实现类调用Mapper接口 → MyBatis执行SQL → MySQL返回结果 → 逐层封装回填 → ModelAndView渲染JSP → 响应回浏览器
这套链路里每一层都有明确的职责,也是面试高频题:
- Controller层:只做参数接收、参数校验、调用Service、把结果放Model、指定跳转视图。
- Service层:业务逻辑,比如发放薪资时要先校验员工状态、计算个税、记录日志。Service接口和Impl分离是一种好习惯。
- Mapper层(DAO层):只负责数据库操作,一个方法对应一条SQL。MyBatis的Mapper接口配合XML文件,可以写动态SQL,这是它比JDBC省事的地方。
- 实体层(entity/pojo):对应数据库表的Java对象,字段名与表字段一一映射。
这里有个关键点:MyBatis的ORM和Hibernate不同,它不是完全自动的,需要你写SQL。这既是缺点也是优点——执行效率可控、SQL灵活度极高,尤其擅长处理多表联查和复杂统计。人事管理系统的报表查询基本都是多表关联,用MyBatis写动态SQL比Hibernate的HQL舒服太多。
2. 数据库设计:人事管理系统的数据基石
2.1 员工主表设计思路与字段规范
数据库表设计直接决定系统能撑多远。人事系统最重要的是员工主表(employee),它的字段设计要兼顾可扩展性和查询效率。我常用的表结构是这样:
CREATE TABLE `employee` ( `id` INT NOT NULL AUTO_INCREMENT COMMENT '主键ID', `emp_no` VARCHAR(20) NOT NULL UNIQUE COMMENT '工号', `name` VARCHAR(50) NOT NULL COMMENT '姓名', `gender` TINYINT DEFAULT 1 COMMENT '性别:1男 2女', `birthday` DATE DEFAULT NULL COMMENT '出生日期', `id_card` VARCHAR(18) DEFAULT NULL COMMENT '身份证号', `phone` VARCHAR(20) DEFAULT NULL COMMENT '手机号', `email` VARCHAR(100) DEFAULT NULL COMMENT '邮箱', `education` VARCHAR(20) DEFAULT '本科' COMMENT '学历', `dept_id` INT DEFAULT NULL COMMENT '所属部门ID', `position_id` INT DEFAULT NULL COMMENT '职位ID', `hire_date` DATE DEFAULT NULL COMMENT '入职日期', `status` TINYINT DEFAULT 1 COMMENT '在职状态:1在职 2离职', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', `update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`id`), KEY `idx_dept_id` (`dept_id`), KEY `idx_name` (`name`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工信息表';几个设计要点说一下:
- 工号(emp_no)设唯一索引。这符合真实企业场景,工号是员工的业务标识,不要用自增id当工号。万一员工离职后工号重复使用,历史考勤和薪资记录会串数据。
- status字段做逻辑删除。人事系统里员工数据不能物理删除,否则历史薪资、社保记录全断了。状态改为离职(2)即可,员工列表默认只查status=1,后台可以查全部。
- 外键不要物理建,用普通索引。很多教程在建表时喜欢加FOREIGN KEY,实际项目里外键会锁表、影响性能、而且MyBatis多表查询根本用不上外键约束。只需要在关联字段建索引就行。
- 必填字段设置NOT NULL,避免程序里各种空指针判断。
2.2 部门、职位、考勤、薪资关联表设计
除了员工主表,系统还至少需要这几张核心表:
部门表(department):
CREATE TABLE `department` ( `id` INT NOT NULL AUTO_INCREMENT, `dept_name` VARCHAR(50) NOT NULL COMMENT '部门名称', `parent_id` INT DEFAULT 0 COMMENT '上级部门ID,0表示顶级', `manager` VARCHAR(50) DEFAULT NULL COMMENT '部门负责人', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;parent_id字段支持树形部门结构,比如总公司 → 技术部 → 后端组。查询子部门时用递归或MyBatis的 拼接。
考勤表(attendance):
CREATE TABLE `attendance` ( `id` INT NOT NULL AUTO_INCREMENT, `emp_no` VARCHAR(20) NOT NULL COMMENT '工号', `att_date` DATE NOT NULL COMMENT '考勤日期', `status` TINYINT DEFAULT 1 COMMENT '1正常 2迟到 3早退 4缺勤', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_emp_date` (`emp_no`, `att_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;这里有个特别容易踩的坑:同一个员工同一天只能有一条考勤记录,所以必须建联合唯一索引uk_emp_date(emp_no, att_date)。没有这个约束,批量导入考勤数据时插两条重复记录,月底统计就会翻车。
薪资表(salary):
CREATE TABLE `salary` ( `id` INT NOT NULL AUTO_INCREMENT, `emp_no` VARCHAR(20) NOT NULL COMMENT '工号', `salary_month` VARCHAR(7) NOT NULL COMMENT '薪资月份,例如2025-06', `base_salary` DECIMAL(10,2) DEFAULT 0 COMMENT '基础工资', `performance` DECIMAL(10,2) DEFAULT 0 COMMENT '绩效工资', `bonus` DECIMAL(10,2) DEFAULT 0 COMMENT '奖金', `insurance` DECIMAL(10,2) DEFAULT 0 COMMENT '五险一金扣除', `should_pay` DECIMAL(10,2) DEFAULT 0 COMMENT '应发工资', `actual_pay` DECIMAL(10,2) DEFAULT 0 COMMENT '实发工资', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_emp_month` (`emp_no`, `salary_month`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='薪资表';薪资表同样要建联合唯一索引,避免重复发放记录。DECIMAL(10,2) 而不是 FLOAT,因为金额必须精确运算,FLOAT这种浮点类型在钱上会有精度问题,这是需要用教训换来的经验。
2.3 用户表与权限管理的简化方案
人事系统登录用户表(sys_user)和员工表建议分开。因为有的系统维护人员可能不是企业员工,而员工也不一定都有系统登录权限。用户表设计相对简单:
CREATE TABLE `sys_user` ( `id` INT NOT NULL AUTO_INCREMENT, `username` VARCHAR(50) NOT NULL UNIQUE COMMENT '登录名', `password` VARCHAR(100) NOT NULL COMMENT '密码(MD5加密存储)', `real_name` VARCHAR(50) DEFAULT NULL COMMENT '真实姓名', `role` TINYINT DEFAULT 1 COMMENT '角色:1管理员 2普通HR', `status` TINYINT DEFAULT 1 COMMENT '账号状态:1启用 0禁用', `create_time` DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;密码存储在实际项目里至少要MD5加盐,更安全的做法是BCrypt。但很多课程设计源码直接明文存,这属于演示代码的偷懒。你作为学习者,哪怕项目是参考别人的,也建议改成加密存储,答辩时是加分项。
权限这块我没用Spring Security或者Shiro。人事系统的需求相对简单,用拦截器 + session角色标记就够了。管理员和普通HR的区别,通过Controller层或JSP页面的角色判断来控制菜单显示和操作按钮即可。杀鸡用牛刀只会增加学习负担。
3. SSM整合与核心功能实现
3.1 环境准备清单与版本匹配建议
跑SSM项目之前,先把环境装对,版本不匹配会让你怀疑人生。我实测稳定的组合:
- JDK:1.8(不要用11或17,老项目在JDK18上经常遇到模块访问限制问题)
- Maven:3.6.3
- Tomcat:8.5(兼容JDK1.8,支持Servlet 3.1)
- MySQL:5.7或8.0.33(8.0需要注意驱动名不同)
- IDEA:2020.3及以上都行
Maven依赖版本也不是随便写的,用我验证过的一组:
<properties> <spring.version>5.2.3.RELEASE</spring.version> <mybatis.version>3.5.4</mybatis.version> <mysql.version>8.0.33</mysql.version> </properties>如果你是MySQL 5.7,mysql-connector-java用5.1.49;如果是8.0,驱动类名改为com.mysql.cj.jdbc.Driver,并且URL后面要拼?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai,否则要么报SSL警告,要么报时区错误,要么乱码。这三个参数少写一个都会出问题。
3.2 Spring与MyBatis整合的配置要点
SSM的整合核心就是让Spring容器管理MyBatis的SqlSessionFactory和Mapper接口代理,这样你的Service里才能直接@Autowired注入Mapper。这部分是源码里最关键的配置,applicationContext.xml大致长这样:
<!-- 读取数据库配置 --> <context:property-placeholder location="classpath:jdbc.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> <!-- 配置SqlSessionFactory --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> <property name="typeAliasesPackage" value="com.hr.entity"/> </bean> <!-- 扫描Mapper接口 --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.hr.mapper"/> </bean> <!-- 开启Spring事务管理 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>这里我特别想强调一下事务。很多入门源码把事务配置删了,因为CRUD不涉及多表写入,看不出问题。但薪资发放这种业务,要同时改salary表、更新员工状态、记录操作日志,任何一个环节出错整个数据就脏了。建议你在Service实现类上加上@Transactional(rollbackFor = Exception.class),并确保事务管理器配置正确,这是“看起来小但很专业”的加分设计。
3.3 SpringMVC配置与web.xml的拦截路由
SpringMVC的配置核心是spring-mvc.xml,负责扫描Controller、开启注解驱动、配置视图解析器。具体配置:
<!-- 扫描Controller层 --> <context:component-scan base-package="com.hr.controller"/> <!-- 注解驱动 --> <mvc:annotation-driven/> <!-- 静态资源放行 --> <mvc:default-servlet-handler/> <!-- JSP视图解析器 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> </bean>注意prefix和suffix这两个配置:Controller里return "employee/list",实际会找/WEB-INF/views/employee/list.jsp。把JSP放在WEB-INF下面是个好习惯,因为用户没法直接通过URL访问WEB-INF目录下的文件,只能通过Controller转发过来,这能防止跳过登录直接打开页面。
web.xml里要做三件事:配置Spring容器监听器、配置DispatcherServlet、配置字符编码过滤器。很多项目中文乱码,就是漏了编码过滤器:
<filter> <filter-name>encodingFilter</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> </filter> <filter-mapping> <filter-name>encodingFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>这个过滤器必须配置在DispatcherServlet之前,并用/*匹配,才能保证POST请求和响应都是UTF-8编码。
3.4 登录鉴权与拦截器实现细节
登录功能看着简单,但它是人事系统最容易出安全问题的点。简单的做法是登录成功把用户对象放进session,然后写一个拦截器统一校验:
public class LoginInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 放行登录请求 if (request.getRequestURI().contains("/login")) { return true; } Object user = request.getSession().getAttribute("loginUser"); if (user == null) { // 未登录直接重定向到登录页 response.sendRedirect(request.getContextPath() + "/toLogin"); return false; } return true; } }然后在spring-mvc.xml里注册拦截器:
<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/login"/> <mvc:exclude-mapping path="/toLogin"/> <mvc:exclude-mapping path="/css/**"/> <mvc:exclude-mapping path="/js/**"/> <mvc:exclude-mapping path="/images/**"/> <bean class="com.hr.interceptor.LoginInterceptor"/> </mvc:interceptor> </mvc:interceptors>小心别把静态资源也拦截了,否则登录页的CSS样式全部加载不出来,页面跟裸奔一样。排除的路径要按你的项目实际资源路径调整。
登录密码的校验我也多说一句:存库的密码加盐MD5加密,登录时把用户输入的密码同样加盐加密再比对,而不是直接用明文SQL去查。这样即使数据库泄露,也不会直接暴露所有账号密码。
3.5 员工管理模块的CRUD与分页查询逻辑
员工列表页是整个系统最常用的功能,这里用一个“带关键字的条件分页查询”举例,这是最标准的写法:
Controller层:
@Controller @RequestMapping("/employee") public class EmployeeController { @Autowired private EmployeeService employeeService; @RequestMapping("/list") public String list(@RequestParam(defaultValue = "1") Integer pageNum, @RequestParam(defaultValue = "10") Integer pageSize, String keyword, Model model) { PageInfo<Employee> pageInfo = employeeService.findEmployeePage(pageNum, pageSize, keyword); model.addAttribute("pageInfo", pageInfo); model.addAttribute("keyword", keyword); return "employee/list"; } }Service层:
@Override public PageInfo<Employee> findEmployeePage(int pageNum, int pageSize, String keyword) { PageHelper.startPage(pageNum, pageSize); List<Employee> list = employeeMapper.selectByCondition(keyword); return new PageInfo<>(list); }Mapper的XML:
<select id="selectByCondition" resultType="com.hr.entity.Employee"> SELECT e.*, d.dept_name, p.position_name FROM employee e LEFT JOIN department d ON e.dept_id = d.id LEFT JOIN position p ON e.position_id = p.id <where> e.status = 1 <if test="keyword != null and keyword != ''"> AND (e.name LIKE CONCAT('%', #{keyword}, '%') OR e.emp_no LIKE CONCAT('%', #{keyword}, '%')) </if> </where> ORDER BY e.create_time DESC </select>分页用PageHelper插件,它的原理是拦截器在MyBatis执行前改写SQL,自动拼接LIMIT。要用它就在pom.xml里加依赖,然后在MyBatis配置里加<plugin interceptor="com.github.pagehelper.PageInterceptor"/>。这个插件不用配置太多东西,但请注意版本要和MyBatis匹配,pagehelper 5.x对应mybatis 3.x。
之所以用LEFT JOIN而不是INNER JOIN,是因为某些员工可能还没有分配部门(dept_id为空),LEFT JOIN能保证这些员工也查出来,部门字段显示为空。这个细节在实际使用中非常容易出现,很多人上线后才发现部门调整中的员工从列表里消失了。
4. 实操过程:从源码下载到项目跑通
4.1 数据库初始化与账号准备
我拿到一个SSM人事系统的源码,第一步永远是导入数据库,而不是先看代码。因为数据库跑通了,代码基本就能跑通一半。
先在MySQL里创建数据库:
CREATE DATABASE hr_system DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;建好库之后,导入源码里自带的schema.sql或hr_system.sql:
mysql -u root -p hr_system < hr_system.sql或者直接用Navicat/DataGrip等工具运行SQL脚本。导入之后重点检查几件事:
- employee、department等核心表是否有数据。有些源码故意不初始化员工数据,你要手动插入几条测试数据。
- 看看sys_user表里的账号密码是不是加密的。如果SQL里就直接是123456,说明源码本身只是演示,你在登录时要输入这个明文。
- 确认下有没有金额字段,如果有,检查数据类型是不是DECIMAL。
数据库这步一定别跳过。很多人在IDEA里折腾半天发现项目启动报错,最后查出来是根本没导入数据库或者连接密码不对。
4.2 IDEA导入SSM项目的工作流程
在IDEA中导入SSM项目有几个关键步骤,做错一个项目就起不来:
- File → New → Project from Existing Sources,选择源码目录,选Maven导入,等待依赖下载完成。如果网络慢或者镜像拉不下来,记得在settings.xml里配置阿里云镜像。
- 确认JDK版本。IDEA里File → Project Structure → Project,设置Project SDK为1.8。
- 配置Tomcat。Run → Edit Configurations → 点 + 号 → Tomcat Server → Local,选择你本地Tomcat目录。Deployment里点 + 号选择Artifact,Artifact必须选“xxx:war exploded”,Application context可以改为
/hr_system。 - 修改
jdbc.properties里的数据库账号密码,确认和本地MySQL一致。 - 启动Tomcat,控制台显示
Server startup in xxx ms,访问http://localhost:8080/hr_system。
这里有个IDE的细节:如果对war exploded打不起包,要检查Project Structure → Artifacts里是否已经生成了这个模块的war exploded。没有就手动加:+ → Web Application: Exploded → From Modules,选你这个模块。Output directory保持默认的tomcat/webapps下的路径就行。
4.3 核心代码包结构与支撑类讲解
跑通项目后,我建议按这个顺序去读代码,别一上来就抠业务:
com.hr ├── controller # 控制层:接收请求、参数校验、返回视图 ├── service # 业务层接口:定义业务方法 │ └── impl # 业务实现类:容器扫描、事务控制 ├── mapper # MyBatis的Mapper接口:数据访问方法声明 ├── entity # 实体类,对应数据库表 ├── interceptor # 拦截器,比如登录校验 └── common # 公共类:统一返回值、常量、MD5工具类resources目录下:
resources ├── mapper/ # MyBatis映射文件:和mapper接口对应 ├── applicationContext.xml # Spring核心配置 ├── spring-mvc.xml # SpringMVC配置 ├── jdbc.properties # 数据库连接参数 ├── mybatis-config.xml # MyBatis全局配置(可选) └── log4j.properties # 日志配置(可选)读源码时有个小技巧:从Controller开始往下看。比如想看员工列表逻辑,就先看EmployeeController的list方法,然后跳到EmployeeService接口和Impl,再看EmployeeMapper接口,最后打开resources/mapper/EmployeeMapper.xml看SQL。按这个链路走,你能完整理解一条请求的生命周期,比拿着源码从头一页一页翻效率高得多。
4.4 JSP页面渲染与数据回显的实现方式
SSM项目的JSP页面通常放在WEB-INF/views下,配合JSTL标签库做数据遍历和格式化。以一个员工列表页面片段为例:
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %> <table class="table table-bordered"> <thead> <tr> <th>工号</th> <th>姓名</th> <th>部门</th> <th>职位</th> <th>入职日期</th> <th>状态</th> <th>操作</th> </tr> </thead> <tbody> <c:forEach items="${pageInfo.list}" var="emp"> <tr> <td>${emp.empNo}</td> <td>${emp.name}</td> <td>${emp.deptName}</td> <td>${emp.positionName}</td> <td><fmt:formatDate value="${emp.hireDate}" pattern="yyyy-MM-dd"/></td> <td> <c:if test="${emp.status == 1}">在职</c:if> <c:if test="${emp.status == 2}">离职</c:if> </td> <td> <a href="${pageContext.request.contextPath}/employee/edit?id=${emp.id}">编辑</a> <a href="${pageContext.request.contextPath}/employee/delete?id=${emp.id}" onclick="return confirm('确定删除该员工吗?')">删除</a> </td> </tr> </c:forEach> </tbody> </table>这里有个容易忽略的坑:JSP页面上获取上下文的路径,一定要用${pageContext.request.contextPath},而不是写死/hr_system。因为你的Application context可能改了,写死了转发和跳转全部404。
JSP渲染数据时还有一点,实体类字段必须是驼峰命名并且提供getter/setter,否则EL表达式${emp.empNo}取不到值。MyBatis在开启驼峰映射的前提下,数据库字段emp_no能自动映射到empNo属性,在mybatis-config.xml里加上:
<settings> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings>别小看这个配置,不开启的话你查询结果里所有下划线字段都映射不上,页面一片空白。
5. 常见问题与排查技巧实录
5.1 数据库连接失败与驱动版本冲突
这个算是SSM新手报警率第一名。典型的报错:
Cannot create PoolableConnectionFactory (Access denied for user 'root'@'localhost' (using password: YES))排查思路照顺序来:
- 检查
jdbc.properties里面的账号密码有没有写错。注意properties文件里不需要给密码加引号,password=123456就对了,有引号反而会把引号当作密码的一部分。 - 检查URL里的IP和端口。本机用
localhost:3306没问题,如果是远程数据库,要确认防火墙开放了3306端口。 - MySQL 8.0务必把驱动类改成
com.mysql.cj.jdbc.Driver、URL加serverTimezone=Asia/Shanghai。 - 用数据库客户端单独连一次,排除数据库服务本身没起来。
这里很多人会忽略一个点:pom.xml里如果同时引入了5.1.x和8.0.x两个版本的mysql驱动,Maven会默认选最近的版本,可能导致驱动类和URL不匹配。建议pom里明确只保留一个版本。
5.2 启动报错:严重: 找不到类 org.springframework.web.servlet.DispatcherServlet
这个报错一看就知道是IDEA部署Tomcat时,“将依赖打进Artifact”这一步没做。Tomcat启动时不会自动去Maven仓库找jar,war包或war exploded必须把所有依赖的jar包放到WEB-INF/lib下。
解决方法:File → Project Structure → Artifacts → 选中你的war exploded → 右侧Available Elements里面找到你的module依赖,右键 → Put into /WEB-INF/lib。然后重新Build → Rebuild,再启动就好了。
如果不做这一步,Spring和MyBatis的jar包永远不在运行时里,无论你怎么修pom都没用。我见过有人在网上搜各种答案折腾了几个小时,其实就是这个设置。
5.3 页面报404:SpringMVC请求路径对不上
页面访问http://localhost:8080/hr_system/employee/list返回404,分三种情况:
- Controller没扫描到:spring-mvc.xml里的
<context:component-scan base-package="com.hr.controller"/>包路径写错了,或者你手动写了@Controller注解但被Spring容器和SpringMVC容器重复扫描。 - 视图解析器路径不对:Controller返回
"employee/list",但在WEB-INF/views下面没有employee目录或者没有list.jsp文件。检查一下文件真实位置。 - Tomcat的Application context路径不对:如果你部署后App context是
/hr_system_war_exploded,而你访问的是/hr_system,自然404。IDEA里Deployment页面可以修改这个值,保持和你的访问路径一致即可。
排查404最有效的办法是看Tomcat控制台日志,如果Controller层有方法映射到,日志里会打印RequestMappingHandlerMapping注册信息。如果压根没打印,说明Controller没被扫到;如果打印了,那就是视图路径或者部署路径的问题。
5.4 中文乱码问题的三级排查
中文乱码在JSP + SSM项目里几乎人人都会遇到,而且乱码的源头可能不止一处。我的排查顺序是:
- JSP页面本身:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>这行有没有。没有的话页面显示的中文直接乱码。 - 数据库连接:jdbc.properties里URL有没有
characterEncoding=utf8,没有就乱码。另外建库时建议用utf8mb4,比utf8能存更多字符。 - Tomcat接收参数:如果是GET请求中文乱码,在Tomcat的server.xml里给Connector加上
URIEncoding="UTF-8"。如果是POST请求乱码,用web.xml里的CharacterEncodingFilter解决。
排查时在页面显示处打印一句固定的中文测试,比如“测试中文”,逐层确认是文件乱码、传输乱码还是存储乱码。这个问题看似小,但在实际项目里最容易让人抓狂,建议把三处配置一次性全部配好。
5.5 IDEA中JSP函数引用无法跳转的问题
很多用IDEA的小伙伴遇到过,JSP里写的自定义函数或方法引用,按住Ctrl点不进去,甚至整个jsp文件都没有语法高亮。这不是代码问题,是IDEA的Mark Directory没配好。
解决办法:在IDEA左侧项目树里,找到你的webapp目录(或src/main/webapp),右键 → Mark Directory as → Web Resources Directory。再把WEB-INF下的jsp目录也标记好。如果还是没有高亮或跳转,检查IDEA有没有装Java EE插件,新版IDEA(如2021+)默认不带Java EE支持,要自己安装。
另外,太老的IDEA版本对JSP的动态EL表达式提示很弱,建议升级到2022.2以上,或者干脆用2024/2025版本,体验会好很多。
5.6 常见问题速查表
为了方便你快速定位,我整理了一份精简速查表:
| 现象 | 根本原因 | 快速解决 |
|---|---|---|
| 启动报错Access denied | 数据库密码错误 | 核对jdbc.properties |
| 启动报错Unknown database | 数据库名不存在 | 先CREATE DATABASE |
| 端口8080被占用 | 其他程序占用 | 改Tomcat端口或杀掉占用进程 |
| 页面全部没有样式 | 静态资源被拦截 | 在spring-mvc.xml放行静态资源 |
| 登录成功后跳转404 | 视图路径不对 | 检查WEB-INF/views下是否有对应jsp |
| SQL语句报错无效列名 | 数据库字段名或表别名错误 | 核对Mapper.xml里的SQL |
| 时间显示为null | 实体类没有date类型转换 | 用@DateTimeFormat或手动格式化 |
| 批量插入慢 | 没有开启批处理 | MyBatis配置allowMultiQueries |
| 内存溢出OutOfMemory | Tomcat默认内存不够 | 在catalina.sh里调大-Xmx |
6. 这套源码的深层价值与扩展方向
6.1 读源码的路线与学习方法建议
把项目跑通只是第一步。很多同学跑通了就以为完事了,等面试官问一句“你项目里权限怎么设计的”就傻眼。我建议跑通后做三件事:
第一,关闭浏览器,凭记忆在白纸上画出你理解的架构图:从浏览器发请求到页面渲染返回,经过了哪些类、哪些配置文件、哪些方法。画不出来就回去看,直到能画出来。这张图就是你对SSM的完整认知。
第二,挑一个完整的功能模块读代码,比如员工管理,用刚才说的Controller → Service → Mapper → XML链路,逐行读源码。读的时候问自己:如果我要加一个字段“籍贯”,要从数据库改到页面,所有要动的文件有哪些?这个功能改造练完,基本就掌握这套源码了。
第三,用断点调试看一次请求的完整过程。在Controller入口打一个断点,然后F7跟着代码一步步走,观察request、model、MyBatis参数处理。比你看十遍文字教程都管用。
6.2 从毕设/课程项目到“可写进简历”的优化路线
如果这个项目你是要拿来写简历或面试聊的,至少要把下面几个“平庸点”去掉:
- 把SQL查询做索引优化。在employee表的name、dept_id、status字段建联合索引,用EXPLAIN看查询计划,面试时能讲出“我的SQL是覆盖索引”这种话,优势明显。
- 加入简单的操作日志功能。写一个AOP切面,统一记录哪个用户、什么时间、操作的哪个功能。人资系统的敏感操作日志是合规刚需,这一点很实战。
- 把密码加密从MD5升级到BCrypt。spring-security-crypto里自带BCryptPasswordEncoder,登录时校验、注册时加密。面试时提“安全设计”很有说服力。
- 增加数据导出功能。用POI/Aliyun EasyExcel在员工列表页加一个“导出Excel”按钮。人事系统导出报表是刚需,做完你会觉得业务场景丰富很多。
优化不是让你推翻重写,而是基于现有源码做增量。每做完一个优化点就记一句笔记,面试聊项目的时候,你就能说出“我基于一个SSM人事管理系统,做了日志切面、Excel导出、安全加固这三件事”,瞬间和只会背增删改查的求职者拉开差距。
我在实际带人和做项目的过程中,最深的一个体会是:代码跑通根本不值得兴奋,把每一行配置、每一条SQL背后的为什么搞明白,才是源码对你真正的价值。这套人事管理系统的源码我建议你至少完整敲一遍按钮、跟一遍调试、改一个功能、加一个模块。四步走完,SSM这套东西就算真正长在你身上了,以后无论看Spring Boot项目还是自己写JavaWeb,都就是从“用过”变成了“懂”的状态。
本文还有配套的精品资源,点击获取