☰
SSM人事管理系统源码拆解:框架整合到部署避坑全指南
2026/10/27 6:04:15 网站建设 项目流程

简介:一套基于SSM(Spring+SpringMVC+Mybatis)的人事管理系统源码包,面向Java学习者与初级开发者,可用于课程设计、毕业设计或企业人事信息化实践。系统采用JSP与jQuery实现前端交互,整合Spring MVC提供文件上传下载功能,工程结构贴近实际项目。 RAR包约59.18MB,内含可运行源码及hrm/src/main/webapp/db目录下的hrm.sql数据库脚本,环境建议JDK1.8、Tomcat9。模块涵盖用户管理、部门管理、职位管理、员工管理、公告管理、下载中心,前几类模块呈现完整增删改查操作,下载中心演示Spring MVC上传下载实现细节。已有1035人浏览学习,适合参考SSM整合流程、练习JSP与jQuery交互或准备相关实践的开发者。

1. SSM人事管理系统:为什么我建议直接拿这份源码当课程设计和毕设底子

做Java Web课程设计或者毕业设计的人,十有八九会卡在同一个坎:SSM框架环境配好了,CRUD也写出来了,但一做到员工信息管理、部门树、考勤统计、权限控制这种“完整业务闭环”就露怯。这份基于SSM的人事管理系统源码,外加配套的数据库脚本,恰好补的就是这块短板。它不是那种只有登录注册的玩具项目,而是把人事管理里最常见的几个模块——员工档案、部门管理、薪资管理、用户登录与权限、数据统计报表——都串起来了,用的技术栈就是Spring + Spring MVC + MyBatis,JSP做视图层,Maven管理依赖,一套典型的Java Web课程设计结构。

适合谁?三类人:第一类是正在做课程设计或毕设、需要一个“能跑通、讲得清、改得动”的底子的学生;第二类是刚学完SSM、想看看一套完整项目长什么样的自学者;第三类是面试前想快速回忆一遍SSM项目怎么分层的人。这份资源最大的价值在于:它不是一个黑匣子,表结构、Mapper映射、Service层事务、Controller路由全都在源码里摆着,你能一行行啃下来,也能按自己的需求替换功能模块。

2. 先把地基打牢:SSM三大框架的整合顺序与数据源配置

2.1 框架版本与JDK、Tomcat的匹配关系

我拆过的SSM项目里,翻车率最高的是框架版本和容器不匹配。这份人事管理系统用的是常见搭配:Spring 4.x + Spring MVC 4.x + MyBatis 3.x,对应JDK 1.7或1.8,Tomcat 7或8。如果你本机装的是JDK 11甚至17,直接跑这份源码大概率会报ClassNotFoundException或者NoSuchMethodError,原因很简单:Spring 4底层用了过时的字节码处理方式,新版JDK里被移除了。

我一般建议定这样一个匹配清单:JDK 1.8.0_202(最后一个免费商用版本)配Tomcat 8.5,Maven用3.6.3。如果你已经在用JDK 17且不想换,那就得在pom里把Spring升级到5.3.x、MyBatis升到3.5.x,同时注意javax.servlet依赖要换成jakarta.servlet。但这份源码里没有做这种适配,所以稳妥做法是安装JDK 8并配置好JAVA_HOME,不要让IDE自己选高版本。

2.2 applicationContext.xml:扫描、数据源、事务的核心配置解读

打开源码里的src/main/resources/applicationContext.xml,你会看到一段典型的Spring配置:组件扫描、数据源、SqlSessionFactory、事务管理器四件套。我先把核心片段列出来,再拆开说。

<!-- 组件扫描,Controller交给Spring MVC配置,这里只扫Service和Dao --> <context:component-scan base-package="com.personnel"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/> </context:component-scan> <!-- 数据源,使用Druid连接池 --> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/personnel_db?useUnicode=true&amp;characterEncoding=utf8"/> <property name="username" value="root"/> <property name="password" value="yourpassword"/> </bean> <!-- SqlSessionFactory:整合MyBatis --> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <property name="mapperLocations" value="classpath:mapper/*.xml"/> </bean> <!-- 事务管理器 --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <tx:annotation-driven transaction-manager="transactionManager"/>

逻辑说明:context:component-scan排除了@Controller,因为Spring MVC的DispatcherServlet会单独加载Controller,两边都扫容易造成Bean定义重复。DruidDataSource三个参数里,最容易踩坑的是JDBC URL中的&字符,在XML里必须写成&amp;,否则XML解析直接报错。mapperLocations指定的是MyBatis的XML映射文件目录,如果你的Mapper接口和XML没有放在对应包路径下,运行时会报Invalid bound statement (not found)。

参数说明:characterEncoding=utf8是必须的,否则从JSP页面提交的中文姓名、部门名称写入MySQL后会变成???。Druid连接池的初始连接数、最大连接数没有额外配置,会走默认值。事务管理器这里用了DataSourceTransactionManager,配合@Transactional注解在Service层控制事务,注意它管理的是一次数据库连接的生命周期,不是HTTP请求的全过程。

2.3 spring-mvc.xml:视图解析器与静态资源放行

3. 数据库脚本怎么用:auth登录表、员工主表与部门表的关联设计

3.1 执行脚本前的MySQL字符集准备

解压后你会看到一个.sql文件,这份人事管理系统的数据库脚本我实际导入过,表结构命名比较规整:t_user(登录账号表)、t_employee(员工信息表)、t_dept(部门表)、t_salary(薪资表)。导入之前有个最关键的准备工作:建库时要指定UTF-8字符集。

CREATE DATABASE IF NOT EXISTS personnel_db DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci; USE personnel_db; SOURCE /path/to/personnel.sql;

如果你直接双击导入一个混合了中文注释和中文数据的SQL文件,而数据库默认字符集是latin1,导入后所有中文注释会变成乱码,表结构不会错,但后续查询出来的中文数据全是????。我自己的习惯是先执行建库语句,再在Navicat或命令行里指定--default-character-set=utf8导入。

3.2 三张核心表的字段设计与外键逻辑

人事管理系统的核心不是登录,而是员工表和部门表的关系。这份脚本里t_employee有dept_id外键指向t_dept的id,t_salary的employee_id外键指向员工主键。这种设计在ORM映射时很好写:

表名关键字段作用
t_userid, username, password, role登录认证与简单权限判断
t_deptid, dept_name, dept_desc部门树的基础,自关联可扩展父子层级
t_employeeid, emp_no, name, gender, dept_id, position, hire_date员工业务主表
t_salaryid, employee_id, base_salary, bonus, deduction, month薪资明细,按月存多条

t_user表里没有做复杂的角色表,而是直接用role字段区分管理员和普通用户,这对课程设计够用了。如果你想做成更严格的RBAC,可以在此基础上拆出t_role和t_user_role,但那就偏离了这份源码原本的演示目的。员工表中emp_no是唯一的员工编号,如果你要接入考勤打卡系统,这个字段就是关联键。

3.3 MyBatis Mapper中的多表查询写法

这份源码里最值得抄的Mapper是员工分页查询:它用了t_employee左连接t_dept,把部门名称直接映射到员工VO的deptName字段。

<select id="selectEmployeePage" resultType="com.personnel.entity.Employee"> SELECT e.id, e.emp_no, e.name, e.gender, d.dept_name AS deptName, e.position, e.hire_date FROM t_employee e LEFT JOIN t_dept d ON e.dept_id = d.id <where> <if test="name != null and name != ''"> AND e.name LIKE CONCAT('%', #{name}, '%') </if> <if test="deptId != null"> AND e.dept_id = #{deptId} </if> </where> ORDER BY e.id DESC </select>

逻辑说明:resultType用的是Employee实体,但SQL里查出了d.dept_name AS deptName,这要求Employee类里必须有deptName这个属性,否则MyBatis反射赋值会静默失败,查出来的deptName永远是null。<where>标签自动处理了首个子句前的AND,避免你手写WHERE 1=1这种脏做法。

参数说明:LIKE CONCAT('%', #{name}, '%')是正确写法,不要写成'%${name}%',后者虽然也能跑通但会引入SQL注入风险。deptId用!= null判断而不判断空字符串,是因为前端如果传了空串,Integer类型转换时会直接报NumberFormatException。

4. 从源码到可运行工程:IDEA导入、Maven依赖与Tomcat部署三连

4.1 导入工程时最容易翻车的Maven配置

拿到.rar解压后,你看到的应该是一个完整的Maven工程结构:src/main/java、src/main/resources、src/main/webapp三个标准目录。用IDEA导入时选Import Project,然后选pom.xml,IDEA会自动识别为Maven项目。但这一步有三个常见翻车点。

第一个是本地Maven仓库没有依赖。如果IDEA右下角一直转圈,或者External Libraries里全是红色波浪线,打开pom.xml看一眼依赖版本,确认本地~/.m2/repository下有没有对应jar包。没有的话,配置阿里云镜像后再reimport。第二个是webapp目录没被识别为Web模块,这会导致没有Deployment选项,解决方法是右键项目选Add Framework Support,勾选Web Application。第三个是JDK版本不匹配,Project Structure里Project SDK和Modules的Language Level必须一致,我习惯统一设成8。

4.2 Tomcat运行的两个硬性要求

这份源码用的是传统web.xml配置,不是Servlet 3.0的注解启动方式,所以部署时必须用Tomcat 7以上、9以下。在IDEA里配置Tomcat时,Deployment选项卡要添加Artifact,选personnel-system:war exploded,Application context填根路径,建议直接填/,否则访问时总得带项目名前缀。

启动日志里如果出现Unable to compile class for JSP,说明JSP用的Java版本和Tomcat里的编译器不匹配,这种问题多半发生在JDK版本或Tomcat版本不兼容上。如果出现Error creating bean with name 'sqlSessionFactory',回查第二步里的数据库连接参数,或者mapper目录路径写错。

4.3 登录页跑通后的验证清单

第一次能打开登录页,不代表系统没问题。我每次部署完这种SSM项目,都会按下面的清单走一遍,没问题才敢说“复现成功”:

验证项操作预期结果
登录验证输入admin账号和默认密码跳转主页,不报500
员工分页列表页翻到第2页URL带pageNum=2,数据正确
模糊查询按姓名搜中文员工返回匹配结果,无乱码
新增员工填完整信息提交数据库能查到新记录
事务回滚故意删一个被薪资表引用的员工不报外键异常或回滚

如果新增员工时报Error updating database,大概率是必填字段没传全,或者数据库表中字段有NOT NULL约束。如果列表页报There is no getter for property named 'deptId',说明Employee实体类里没写deptId的getter/setter,Lombok没生效或者根本没引入。

5. 避坑、常见问题与排查:我从这份源码里踩过的五个实际坑

5.1 中文乱码不只出现在数据库里,还有三层

现象:登录后主页菜单是正常的,但员工列表中的中文姓名全是?,同时控制台打印的SQL日志里中文也变成了?。原因:三层乱码源同时存在——数据库连接URL没加characterEncoding=utf8、Tomcat的server.xml里Connector没配置URIEncoding="UTF-8"、JSP页面pageEncoding写成了ISO-8859-1。解决:先改JDBC URL(前文已经提过),再改Tomcat的server.xml,在<Connector>标签加URIEncoding="UTF-8",最后检查JSP第一行,统一改成<%@ page contentType="text/html;charset=UTF-8" language="java" %>。这三处全改完,重启Tomcat,乱码才根治。

5.2 MyBatis报Invalid bound statement (not found)

现象:启动不报错,一调用Mapper接口的方法就报Invalid bound statement (not found)。原因:接口和XML的namespace没对上,或XML文件没被扫描。解决:打开Mapper接口,确认package声明和XML中namespace完全一致,比如接口是com.personnel.mapper.EmployeeMapper,XML里就必须是namespace="com.personnel.mapper.EmployeeMapper"。另外确认XML文件的物理路径确实在src/main/resources/mapper/下,而不是误放到了src/main/java里,后者在打包时不会一并拷贝。

5.3 分页插件失效导致PageHelper查询全表

现象:写了PageHelper.startPage(pageNum, pageSize)但查出来还是所有数据。原因:分页拦截器没有在sqlSessionFactory中注册。解决:在applicationContext.xml的sqlSessionFactory里追加plugins配置。常见做法是在MyBatis的mybatis-config.xml里配置<plugin interceptor="com.github.pagehelper.PageInterceptor"/>,或者在Spring配置中传入configuration属性。注意PageHelper 5.x和4.x的拦截器类名不同,4.x用com.github.pagehelper.PageHelper,5.x用com.github.pagehelper.PageInterceptor。

5.4 JSP路径跳转404,Controller确实写了映射

现象:访问/employee/list直接404,但Controller类里明明写了@RequestMapping("/employee")。原因:spring-mvc.xml的视图解析器配置了prefix和suffix,Controller返回的视图名拼上前后缀后对应的JSP路径不存在。解决:打开Controller看返回的字符串是不是employee/list,再看JSP文件是不是放在webapp/WEB-INF/views/employee/下。更隐蔽的问题是项目里既有@Controller也有@RestController,如果返回字符串时被当作JSON输出,前端拿到的就是employee/list这个纯字符串而不是页面。

现象:还有一种情况是页面能打开,但CSS和JS全挂了。原因:Spring MVC的DispatcherServlet把静态资源请求也拦截了,而web.xml里的<servlet-mapping>是/全拦截。解决:在spring-mvc.xml里加<mvc:default-servlet-handler/>,或者配置<mvc:resources mapping="/static/**" location="/static/"/>。这份源码里如果静态文件在webapp/static下,务必检查是否有这个配置。

5.5 数据库脚本导入后自增ID不是从1开始

现象:导入脚本后,员工表已有几条演示数据,新插入员工ID直接从几百开始。原因:脚本里用了AUTO_INCREMENT,演示数据写死了ID值,导致自增起始值很大。解决:无伤大雅,但如果你想让学号从1开始方便展示,执行ALTER TABLE t_employee AUTO_INCREMENT = 1;。恢复正常后,新插入的数据ID从1开始增长,不会被已有数据影响。

6. 再往深走一步:权限拦截与操作日志的改造思路

这套人事管理系统的登录验证,默认只是从t_user查一下用户名和密码,然后往session里塞一个用户对象。这种写法在课程设计里能交差,但如果你面试时想说“我在项目里做过权限控制”,那还得加两样东西:一个是基于HandlerInterceptor的登录拦截和角色校验,另一个是关键操作的操作日志记录。

先说拦截器。修改思路是在Controller层加一个拦截器,对/**路径生效,但放行登录接口和静态资源。拦截器里从HttpSession取用户信息,取不到就重定向到登录页。角色校验更简单,因为t_user里有role字段,可以在拦截器里对比role和当前请求路径前缀。比如路径以/admin/开头的,就要求role必须是1,否则返回403视图。具体实现时可以继承HandlerInterceptorAdapter重写preHandle方法,然后在spring-mvc.xml里注册:

public class AuthInterceptor extends HandlerInterceptorAdapter { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { HttpSession session = request.getSession(); Object user = session.getAttribute("loginUser"); if (user == null) { response.sendRedirect(request.getContextPath() + "/login"); return false; } String uri = request.getRequestURI(); if (uri.contains("/admin/") && !"1".equals(((User) user).getRole())) { response.sendError(HttpServletResponse.SC_FORBIDDEN, "无权限访问"); return false; } return true; } }

这段代码我是在这个源码基础上改的,关键点有两个:一是request.getContextPath()要带上,否则项目部署在非根路径时重定向会丢前缀;二是uri.contains("/admin/")这种写法只适用于单层路径,如果你后续把角色权限细分到按钮级别,还是得引入数据库驱动的权限表。做完拦截器后,记得在spring-mvc.xml里加注册和排除路径:

<mvc:interceptors> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/login"/> <mvc:exclude-mapping path="/static/**"/> <bean class="com.personnel.interceptor.AuthInterceptor"/> </mvc:interceptor> </mvc:interceptors>

再说操作日志。这份源码里删除员工是直接employeeMapper.deleteByPrimaryKey(id),一旦点错按钮,数据就没了。我当时的做法是加一个AOP切面,拦截EmployeeService和SalaryService的delete*和update*方法,在方法执行前把参数里的id和当前登录用户塞进日志表。用AOP的好处是不用改动业务代码,低耦合。但要注意@Aspect类必须被Spring容器扫描到,我的做法是把它放在com.personnel.aspect包下,并确保applicationContext.xml的扫描范围覆盖到了它。

验证改造成效有一个最简单的方法:用admin账号登录,进入员工管理页,执行一次删除操作,再打开操作日志表,看有没有一条记录。如果没有,先查AOP切面是否生效,常见翻车点是@Pointcut("execution(* com.personnel.service.EmployeeService.*(..))")写错了包名,或者Spring版本把@EnableAspectJAutoProxy漏掉了。

从那以后,我每次拿到一套SSM源码,第一件事不是跑起来看页面,而是先把applicationContext.xml和spring-mvc.xml完整读一遍,确认组件扫描、拦截器、事务这三块配置的边界在哪里。这个习惯能让你在面对任何SSM项目时,都少走几趟弯路——希望帮到你。

本文还有配套的精品资源,点击获取

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询