SSM框架人力资源管理系统:从数据库设计到Spring MVC联调的完整落地指南
简介基于SSM框架构建的人力资源管理系统是一套面向计算机专业毕业设计学生与Java实战学习者的完整项目资料包。资源共含3个文件包括项目说明txt、源码压缩包zip及数据库脚本sql打包后大小约25MB结构简明可直接运行部署。系统采用Spring、SpringMVC、MyBatis与MySQL开发分为管理员与员工两种角色包含部门管理、职位管理、员工管理、考勤管理、薪资管理、意见管理及个人管理七大模块其中部门与职位支持增删改查考勤与薪资帮助HR快速掌握全员状态意见管理兼顾员工反馈与权限控制个人管理则方便员工维护自身信息功能齐全、界面简洁覆盖企业人力管理核心场景。目前已有2579人学习/下载经过严格调试确保可运行适合作为毕设项目直接使用也为Java学习者提供了一个理解SSM整合与业务模块设计的良好素材。1. 用 SSM 做人力资源管理系统到底是在做一件什么事人力资源管理系统是这个行业里最“标准”的选题业务明确、表结构稳定、页面能直观展示增删改查又不像电商那样要处理复杂的订单状态机。SSMSpring Spring MVC MyBatis在这里不是性能最优解却是毕设阶段容错率最高的组合Java 生态资料多、出问题能在五分钟内搜到答案。但真正让这套系统“像毕设”而不是“课程作业”的不是页面多华丽而是三样东西表之间能不能自圆其说、数据库脚本能否在另一台电脑上一键跑通、以及源码里有没有体现事务和关联查询。这篇文章就把这三条主线拆开讲按“先设计表、再写映射层、最后联调接口”的顺序落地末尾给出一份可以直接当验收依据的源码与数据库脚本检查清单。2. 先把表设计定死人力资源管理系统数据库脚本的核心字段与建表顺序2.1 为什么要先画 ER 图再写 SQL五张核心表的关联关系大多数拿到“基于SSM的人力资源管理系统”源码的人第一件事是去跑 .sql 文件跑通了就以为完事。这正好把顺序搞反了。数据库脚本是这套系统的地基脚本里如果出现三张表互相外键引用、或者员工表里存了部门名称而不是部门 ID后面所有 Mapper 的关联查询都会写得非常别扭。我先按常见做法梳理出五张核心表员工表employee、部门表dept、岗位表position、考勤表attendance、薪资表salary。很多版本还会加一张用户表sys_user用来做登录鉴权。这六张表的关系跨度刚好覆盖了人力资源管理系统里最典型的三种查询场景一对一员工对岗位、一对多部门对员工、多对多员工与考勤记录按月汇总。设计时有一个原则表名用单数还是复数不重要但字段名不能混用驼峰和下划线MyBatis 的 resultMap 映射时统一用下划线转驼峰的策略否则后面每个查询都要写别名。部门与岗位之间建议用逻辑上的间接关联employee 表里同时存 dept_id 和 position_id而 position 表再通过 dept_id 指向部门。这样设计的原因是多数实际业务里“岗位”是挂在部门之下的HR 在录入员工时要先选部门再选岗位前端联动表单的数据源也需要这张中间关系。2.1.1 字段命名与主键策略主键不要用数据库自增之外的花活。SSM 项目里最常见的坑是使用 UUID 做字符串主键联合查询时每个 Mapper 里都要多传一个参数分页插件也会受主键类型影响。这里统一采用BIGINT AUTO_INCREMENT在 MySQL 8.x 下没有任何兼容问题。字段名我建议强制遵循以下约定外键字段统一叫xxx_id时间字段统一叫xxx_time如 hire_time、create_time状态字段统一叫status用 TINYINT 表示且必须加默认值。这个约定能让你在建表脚本阶段就规避掉一半的 MyBatis 映射问题。2.2 用一段 SQL 脚本把员工、部门、考勤一次建齐这里给出一份可以直接用于人力资源管理系统的最小化数据库脚本片段。它的特点是把外键约束放在所有表创建之后再统一追加避免因建表顺序导致的外键失败。-- 初始化人力资源管理系统数据库字符集统一使用 utf8mb4 CREATE DATABASE IF NOT EXISTS hrms DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE hrms; -- 部门表 CREATE TABLE dept ( id BIGINT AUTO_INCREMENT COMMENT 部门ID, dept_name VARCHAR(50) NOT NULL COMMENT 部门名称, manager_id BIGINT DEFAULT NULL COMMENT 部门负责人指向employee.id, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (id) ) ENGINEInnoDB; -- 岗位表通过dept_id与部门关联 CREATE TABLE position ( id BIGINT AUTO_INCREMENT COMMENT 岗位ID, dept_id BIGINT NOT NULL COMMENT 所属部门ID, position_name VARCHAR(50) NOT NULL COMMENT 岗位名称, base_salary DECIMAL(10,2) DEFAULT 0.00 COMMENT 岗位基本工资, PRIMARY KEY (id) ) ENGINEInnoDB; -- 员工表 CREATE TABLE employee ( id BIGINT AUTO_INCREMENT COMMENT 员工ID, emp_no VARCHAR(20) NOT NULL UNIQUE COMMENT 工号, name VARCHAR(30) NOT NULL, gender TINYINT DEFAULT 1 COMMENT 1男 2女, phone VARCHAR(20), dept_id BIGINT NOT NULL, position_id BIGINT NOT NULL, hire_time DATE COMMENT 入职日期, status TINYINT DEFAULT 1 COMMENT 1在职 2离职, PRIMARY KEY (id) ) ENGINEInnoDB; -- 考勤表按月记录同一员工同月只有一条记录 CREATE TABLE attendance ( id BIGINT AUTO_INCREMENT, employee_id BIGINT NOT NULL, month VARCHAR(6) NOT NULL COMMENT 格式202501, work_days INT DEFAULT 0, leave_days INT DEFAULT 0, late_count INT DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY uk_emp_month (employee_id, month) ) ENGINEInnoDB; -- 薪资表 CREATE TABLE salary ( id BIGINT AUTO_INCREMENT, employee_id BIGINT NOT NULL, month VARCHAR(6) NOT NULL, base_salary DECIMAL(10,2) NOT NULL, bonus DECIMAL(10,2) DEFAULT 0.00, deduction DECIMAL(10,2) DEFAULT 0.00, actual_salary DECIMAL(10,2) GENERATED ALWAYS AS (base_salary bonus - deduction) STORED, PRIMARY KEY (id) ) ENGINEInnoDB;这段脚本有几个值得注意的参数。employee.emp_no加了 UNIQUE 约束这是人力资源管理系统里的硬性校验——工资和考勤都依赖工号定位员工。attendance表的唯一索引uk_emp_month (employee_id, month)是最容易漏的没有它同一个人同一个月可能插入多条考勤后续月度汇总报表直接错翻。年龄、入职年限这类字段反而是冗余的可以通过 hire_time 实时计算不需要单独存一个age字段否则每年还要定时任务去刷新它。2.3 数据字典与字段约束长度、默认值、索引怎么定选几个高频场景说明参数策略。手机号字段用 VARCHAR(20) 而不是 VARCHAR(11)因为 HR 系统里员工可能填座机号码甚至包含分机号性别字段用 TINYINT 注释 1/2而不是直接存“男/女”中文字符串中文会导致索引空间变大且易出错。金额统一用 DECIMAL(10,2)禁止 FLOAT/DOUBLE数据脚本是否严谨看金额字段类型就能判断。索引方面外键字段dept_id、position_id、employee_id一定要建普通索引。MySQL 8.x 的 InnoDB 引擎会自动为外键建索引但如果你只写字段不写外键约束很多人为了省事不建物理外键就必须手动KEY idx_dept_id (dept_id)。提示建物理外键在毕设项目里是一把双刃剑。它能展示你懂参照完整性但也会让后续测试脚本的删除顺序变得繁琐。我一般建议保留外键定义然后所有涉及删除的测试数据都走 SQL 脚本里显式声明的顺序执行。3. Maven 工程与 MyBatis 映射层的落地写法3.1 在 IntelliJ IDEA 里创建 SSM 工程的最小依赖清单SSM 整合的第一步不是写代码而是先把 pom.xml 定住。很多“跑不起来”的毕设源码根因是依赖版本互相打架最典型的组合是 Spring 5.x 搭配 MyBatis 3.4.x 时缺了mybatis-spring的适配版本。最小可用依赖清单如下坐标都是 Maven 中央仓库的官方版本properties spring.version5.3.30/spring.version /properties dependencies !-- Spring 核心容器 -- dependency groupIdorg.springframework/groupId artifactIdspring-context/artifactId version${spring.version}/version /dependency !-- Spring MVC -- dependency groupIdorg.springframework/groupId artifactIdspring-webmvc/artifactId version${spring.version}/version /dependency !-- Spring JDBC提供事务管理器 -- dependency groupIdorg.springframework/groupId artifactIdspring-jdbc/artifactId version${spring.version}/version /dependency !-- MyBatis 主库 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis/artifactId version3.5.13/version /dependency !-- mybatis-spring 适配层 -- dependency groupIdorg.mybatis/groupId artifactIdmybatis-spring/artifactId version2.0.7/version /dependency !-- MySQL 驱动 -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId version8.0.33/version scoperuntime/scope /dependency !-- 数据库连接池直接用默认配置 -- dependency groupIdcom.alibaba/groupId artifactIddruid/artifactId version1.2.20/version /dependency /dependencies这里的核心是mybatis-spring的版本不能乱选。2.0.x 系列适配 MyBatis 3.5.x如果你是拿的旧源码里还写着org.mybatis:mybatis-spring:1.3.2配合 Spring 5.3 会出现启动时BeanCreationException报错信息指向 sqlSessionFactory。处理方式很简单要么把 Spring 降级到 5.0.x要么把 mybatis-spring 升到 2.0.7我建议后者。3.2 手写 Mapper 接口和 XML避免 generator 生成后的重复改毕设项目规模不大表不超过十张不值得引入 MyBatis Generator。自动生成代码虽然省事但生成出来的 XML 里Base_Column_List和Example类会让代码量翻倍答辩时被问“这行干什么的”答不上来就尴尬了。手写 Mapper 反而能把这个环节变成加分项。以部门员工联查为例写一个组合查询的 Mapperpublic interface EmployeeMapper { // 分页条件查询条件动态拼接 ListEmployeeVO selectEmployeePage(Param(deptId) Long deptId, Param(status) Integer status, Param(keyword) String keyword); }select idselectEmployeePage resultMapEmployeeVOMap SELECT e.id, e.emp_no, e.name, e.gender, e.phone, e.hire_time, e.status, d.dept_name, p.position_name FROM employee e LEFT JOIN dept d ON e.dept_id d.id LEFT JOIN position p ON e.position_id p.id where if testdeptId ! null AND e.dept_id #{deptId} /if if teststatus ! null AND e.status #{status} /if if testkeyword ! null and keyword ! AND (e.name LIKE CONCAT(%, #{keyword}, %) OR e.emp_no LIKE CONCAT(%, #{keyword}, %)) /if /where ORDER BY e.id DESC /select这段代码在人力资源管理系统里是复用率最高的一个查询。三个参数都是可选条件where标签会自动去掉第一个多余的 AND这是 MyBatis 动态 SQL 的标准用法。LIKE CONCAT(%, #{keyword}, %)必须写 CONCAT不能写%${keyword}%后者会引入 SQL 注入风险且无法走预处理。resultMap 里如果用resultTypemap会丢失下划线转驼峰这里建议显式声明 resultMap。3.3 Service 层事务Transactional 要加在接口实现类还是实现上一个合格的毕设源码事务至少要体现在两个地方入职登记创建员工 初始化当月考勤记录和薪资发放批量更新员工薪资 写发放流水。如果整个项目只在配置里开了事务管理器Service 层一个Transactional都找不到答辩老师会追问“事务控制在哪里”。位置问题记住一条结论就够了写在实现类的 public 方法上不要写在接口的方法签名上。Spring 的默认事务代理基于 JDK 动态代理或 CGLIB标注在接口上只有在特定切面配置下才会生效。另一个常见误用是方法内部this.selfMethod()调用另一个事务方法会导致第二个方法的事务失效——因为自调用不经过代理对象。Service public class EmployeeServiceImpl implements EmployeeService { Autowired private EmployeeMapper employeeMapper; Autowired private AttendanceMapper attendanceMapper; Override Transactional(rollbackFor Exception.class) public void hireEmployee(Employee employee, String month) { // 1. 插入员工主记录 employeeMapper.insert(employee); // 2. 初始化当月考勤默认全勤 Attendance att new Attendance(); att.setEmployeeId(employee.getId()); att.setMonth(month); att.setWorkDays(0); att.setLeaveDays(0); att.setLateCount(0); attendanceMapper.insert(att); // 如果第2步失败第1步的insert自动回滚 } }rollbackFor Exception.class是必填参数。Spring 默认只对 RuntimeException 回滚如果代码里抛出的是受检异常比如SQLException不加这个参数事务不会回滚。很多源码跑起来数据不一致问题就出在这。4. Spring MVC MyBatis 联调从 404 到接口可用的完整链路4.1 三个配置文件职责拆清spring-context.xml、spring-mvc.xml、mybatis-config.xmlSSM 的配置是新手最容易卡住的地方。难的不是写入 XML 本身而是搞不清三个配置文件的边界。用一句话拆解spring-context.xml 管业务和数据库Service、Mapper、DataSource、事务spring-mvc.xml 只管 Controller 和视图解析mybatis-config.xml 只放 MyBatis 自身的全局设置。!-- spring-context.xml 中的核心片段 -- context:component-scan base-packagecom.hrms.service / bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName valuecom.mysql.cj.jdbc.Driver / property nameurl valuejdbc:mysql://localhost:3306/hrms?useSSLfalseamp;serverTimezoneAsia/Shanghai / property nameusername valueroot / property namepassword value123456 / /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource / property namemapperLocations valueclasspath:mapper/*.xml / property nameconfigLocation valueclasspath:mybatis-config.xml / /bean mybatis:scan base-packagecom.hrms.mapper / bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource / /bean tx:annotation-driven transaction-managertransactionManager /!-- spring-mvc.xml 核心配置 -- context:component-scan base-packagecom.hrms.controller / mvc:annotation-driven mvc:message-converters bean classorg.springframework.http.converter.StringHttpMessageConverter property namedefaultCharset valueUTF-8 / /bean /mvc:message-converters /mvc:annotation-drivenurl里有两个容易踩的坑。第一是serverTimezoneAsia/Shanghai必须配MySQL 8.x 驱动不加会按时区报错第二是property标签里必须转写成amp;很多源码报cvc-elt.4.2错误就是没转义。mybatis:scan的 base-package 必须与 Mapper 接口包路径一致Maven 多模块时这里还容易漏掉mapperLocations指向 XML 的路径。4.2 用 Postman 跑通“新增员工”接口的请求体与响应体联调阶段先把最简单的新增员工接口跑通确认链路完整后再碰分页查询。假设 Controller 已经写了PostMapping(/employee)接收 JSON 请求体配合RequestBody注解那么 Postman 里的请求是这样构造的POST http://localhost:8080/hrms/employee Content-Type: application/json { empNo: EMP2025001, name: 赵一鸣, gender: 1, phone: 13800138000, deptId: 1, positionId: 2, hireTime: 2025-03-01, status: 1 }正常响应体应当是一个统一的结果封装{ code: 200, message: success, data: { id: 1 } }如果返回的是 400 Bad Request先检查 JSON 里的键名与实体字段是否完全一致比如deptId在实体里是deptId而不是dept_id如果返回 500 且日志里有Invalid bound statement (not found)是 Mapper XML 的 namespace 与方法名不一致去 target/classes/mapper 目录下确认 XML 是否被编译进产物目录。4.3 常见异常的根因对照SSM 联调阶段频率最高的三类问题异常现象直接原因检查顺序启动报ClassNotFoundException: org.springframework.web.filter.CharacterEncodingFilter依赖缺 spring-web看 pom.xml 是否有 spring-webmvc页面 404控制台无任何错误DispatcherServlet 映射路径没配置检查 web.xml 中url-pattern//url-pattern查询结果中文乱码数据库连接、JSP 页面、响应三处编码不一致先查 Druid 连接 URL 是否带characterEncodingutf8Cause: org.xml.sax.SAXParseExceptionXML 里出现了未转义全局搜索 mapper.xml 里的并替换为amp;分页查询不生效缺少 PageHelper 依赖或版本冲突确认 PageHelper 版本 5.x 且启动了拦截器乱码问题在人力资源管理系统里出现的概率极高因为员工姓名、部门名称都是中文。解码动作每次都从 URL 参数进入建议在 web.xml 里显式配置 Spring 的 CharacterEncodingFilter并设置forceEncodingtrue否则 request 和 response 的动作会不一致。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-mapping5. 收尾毕设答辩前要把这套人力资源管理系统源码验证到什么程度5.1 用初始化脚本一键重建数据库源码包交付时常见的问题是开发机上数据库里攒了一大堆测试数据导出 SQL 时把开发数据一起带了出来。正确做法是维护一个init.sql只包含建库、建表和必要的初始数据一个管理员账号、两个测试部门。验收时不能靠本机 Navicat要换一台干净的电脑或在本机删库再跑一遍mysql -u root -p init.sql执行后检查三件事SHOW TABLES;能否看到全部表SELECT * FROM dept;是否有初始数据有没有报ERROR 1007数据库已存在——如果存在说明脚本里少了DROP DATABASE IF EXISTS或CREATE DATABASE IF NOT EXISTS。这一步是源码包能不能换算成成绩的关键。5.2 用接口清单反查功能覆盖度答辩前打开 Postman 建一个集合按角色功能分组列出接口清单每组选中一个代表接口做验证并截图。人力资源管理系统至少要覆盖六类接口登录鉴权、员工管理增删改查、部门管理支持变更员工部门、考勤登记按月录入、薪资核算查询与导出、统计分析按部门统计人数与薪资总额。每类接口的返回值统一用ResultT包装里面字段固定为code、message、data。答辩时展示这个集合比现场敲代码更有说服力。另外建议在源码根目录放一个README.md内容只有四段JDK 与 MySQL 版本要求、数据库初始化步骤、Maven 打包的clean package命令、部署时的 Tomcat 版本。不要写大段的系统介绍评审人更关心能不能快速跑起来。做到上面这些源码、数据库脚本、答辩演示三者就是同一套证据链这套 SSM 人力资源管理系统才算真正立得住。本文还有配套的精品资源点击获取