ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

基于SSM+JSP的企业人事管理系统:从配置到部署全攻略

基于SSM+JSP的企业人事管理系统:从配置到部署全攻略 简介一份基于SSM框架Spring、SpringMVC、MyBatis与JSP技术的人事管理系统设计与实现项目资源面向Java Web开发学习者、SSM框架实践者及需要完成毕业设计的学生覆盖了企业人事管理中员工信息维护、薪酬核算、考勤统计、绩效考核等实际业务需求。压缩包共987个文件大小11.48MB包含Java后端源码、46个JSP页面、HTML/CSS/JavaScript等前端资源、MyBatis映射文件、数据库初始化SQL脚本以及大量图片字体等UI素材项目按MVC模式分层组织代码结构清晰便于学习或二次开发。目前已有58人学习下载适合作为课程设计或项目实训的参考资料。配套说明文档和使用指导可帮助快速完成环境配置与系统部署读者在动手实践中可掌握SSM框架整合流程、JSP动态页面构建和数据库设计方法并理解权限管理、数据查询与备份恢复等常用功能的实现思路。1. 为什么选择 SSM 加 JSP 方案来交付企业人事管理系统在前后端分离已经成为主流的今天继续谈 SSM 加 JSP 好像有点“返祖”。但如果你真正接手过企业内部的 OA、人事、考勤这类系统会发现它们依然大量跑在 SSM 框架上交付形态就是一个 zip 包里面是 Spring、SpringMVC、MyBatis 的 XML 配置和一堆 JSP 页面。我最早接手这种项目时也犹豫过直接拆了重写成 Spring Boot 不是更好后来发现人事管理系统这类业务核心价值不在框架新不新而在“员工、部门、薪资、考勤”这些数据关系能否被稳定地维护。SSM 的请求链路非常直白JSP 发请求Controller 接Service 处理MyBatis 落库。任何一环出问题日志和调用栈都能直接对应到三层中的某一层这比微服务排查成本低得多。所以这篇文章就围绕“一个 SSM 企业人事管理系统 zip 包”的视角把框架配置、数据模型、JSP 视图和 Tomcat 部署的完整路径讲清楚。适合刚接手此类项目的开发也适合要把自己写的系统打包交出去的人。2. SSM 整合的企业人事系统配置骨架2.1 Spring、SpringMVC、MyBatis 在企业人事系统里各管哪一段企业人事管理系统的功能看似多但拆开来看无非是部门维护、员工信息增删改查、考勤登记、薪资调整。这些功能落到技术层面就是“请求怎么进”“业务逻辑放哪”“SQL 怎么写”三件事。SSM 的分工恰好对应这三件事。Spring 是容器层管理 Service 和 Mapper 的实例也管理数据库连接池和事务边界。SpringMVC 负责 HTTP 层把浏览器发来的 URL 映射到 Controller 方法再把 Controller 返回的逻辑视图交给 JSP 渲染。MyBatis 负责持久层把 Java 接口方法和 XML 里的 SQL 绑定在一起用动态 SQL 解决查询条件不固定的问题。一个问题既然 Spring Boot 也能做同样的事为什么还要学 SSM在企业人事系统这种环境里数据库往往是已经跑了好几年的老库JDK 版本可能是 1.7 或 1.8Tomcat 是老版本运维人员只认 war 包。SSM 的优势是把配置全部写成 XML哪个 Bean 被创建、哪个 mapper 被扫描都看得见。相比之下 Spring Boot 的自动化虽然快但出了问题需要懂自动装配原理才能排。反正我是这么看的在毕业设计、课程设计、中小型企业内部系统里SSM 依然是出镜率极高的组合也是那些“基于 ssm 的学生信息管理系统”之类项目能直接改成人事系统的基础——表和权限变了骨架没变。2.2 企业人事系统里最值得抄的 web.xml 配置SSM 项目部署到 Tomcat入口是web.xml。这里需要完成三件事加载 Spring 容器、挂载 SpringMVC 前端控制器、配置字符编码过滤器。最后一件在人事系统里尤其关键因为员工姓名、部门名称都是中文编码不对满屏乱码根本没法用。web-app xmlnshttp://xmlns.jcp.org/xml/ns/javaee xmlns:xsihttp://www.w3.org/2001/XMLSchema-instance xsi:schemaLocationhttp://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd version3.1 context-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/applicationContext.xml/param-value /context-param listener listener-classorg.springframework.web.context.ContextLoaderListener/listener-class /listener filter filter-nameencodingFilter/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-nameencodingFilter/filter-name url-pattern/*/url-pattern /filter-mapping servlet servlet-namedispatcher/servlet-name servlet-classorg.springframework.web.servlet.DispatcherServlet/servlet-class init-param param-namecontextConfigLocation/param-name param-valueclasspath:spring/spring-mvc.xml/param-value /init-param load-on-startup1/load-on-startup /servlet servlet-mapping servlet-namedispatcher/servlet-name url-pattern//url-pattern /servlet-mapping /web-app这段配置里有两个容易忽略的细节。CharacterEncodingFilter中的forceEncoding必须设为 true否则它只会设置请求编码不会设置响应编码。另一个是用/还是*.do的问题。很多从学生信息管理系统改过来的代码喜欢用*.do好处是 JSP 和静态资源不会被 DispatcherServlet 拦下来坏处是 URL 不美观而且如果要启用 REST 风格的路径*.do会直接挡路。我一般用/然后在 SpringMVC 配置里单独放行静态资源。2.3 applicationContext 与 spring-mvc 的分工及常见错配SSM 的配置最容易犯错的地方是扫描包重叠。applicationContext.xml 里只扫描 Service 和 Mapper 相关组件spring-mvc.xml 里只扫描 Controller。如果把两个扫描范围写成一样Service 会被实例化两次事务代理也会失效出现“方法上有Transactional却不回滚”的诡异情况。!-- applicationContext.xml -- context:component-scan base-packagecom.company.hr.service / context:property-placeholder locationclasspath:jdbc.properties / bean iddataSource classcom.alibaba.druid.pool.DruidDataSource property namedriverClassName value${jdbc.driver} / property nameurl value${jdbc.url} / property nameusername value${jdbc.username} / property namepassword value${jdbc.password} / /bean bean idsqlSessionFactory classorg.mybatis.spring.SqlSessionFactoryBean property namedataSource refdataSource / property namemapperLocations valueclasspath:mapper/*.xml / /bean bean idtransactionManager classorg.springframework.jdbc.datasource.DataSourceTransactionManager property namedataSource refdataSource / /bean tx:annotation-driven transaction-managertransactionManager /!-- spring-mvc.xml -- context:component-scan base-packagecom.company.hr.controller / mvc:annotation-driven / mvc:resources mapping/static/** location/static/ / bean classorg.springframework.web.servlet.view.InternalResourceViewResolver property nameprefix value/WEB-INF/jsp/ / property namesuffix value.jsp / /bean参数说明Druid 连接池适合内网人事系统它有监控页面和慢 SQL 统计。mapperLocations指向classpath:mapper/*.xml如果这个路径写错了Spring 启动时不会报错直到调用 Mapper 方法才抛Invalid bound statement排查时很容易走弯路。InternalResourceViewResolver的 prefix 写成/WEB-INF/jsp/是推荐做法JSP 放在 WEB-INF 下浏览器就不能直接访问物理路径所有页面都必须经过 Controller 转发权限控制才有效。常见配置错误对照表错误现象根源修复方式启动后 Service 被创建两次两个 xml 扫描了相同包分离 base-packageService 和 Controller 分开扫事务不回滚事务管理器没注入 DataSource检查 DataSourceTransactionManager 的 dataSource 引用Invalid bound statementmapperLocations 路径错误确认 mapper XML 在 resources 下的实际路径3. 企业人事系统的数据模型与 SSM 落库3.1 员工、部门、薪资、考勤如何设计成表人事管理系统的核心表通常有五张部门表、员工表、考勤表、薪资表、用户表。其中员工信息不能和登录用户混在一张表里因为员工可能离职但账号仍需要保留或者一个员工有多种角色。设计时最怕的是没有唯一约束导致同一个工号在系统里出现两条记录考勤汇总和薪资计算全部错乱。CREATE TABLE department ( id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL, manager_id INT DEFAULT NULL COMMENT 部门负责人关联 employee.id ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT 部门表; CREATE TABLE employee ( id INT PRIMARY KEY AUTO_INCREMENT, emp_no VARCHAR(20) NOT NULL UNIQUE COMMENT 工号, emp_name VARCHAR(50) NOT NULL, dept_id INT NOT NULL, position VARCHAR(50), hire_date DATE, salary_base DECIMAL(10,2), status TINYINT DEFAULT 1 COMMENT 1在职 0离职, FOREIGN KEY (dept_id) REFERENCES department(id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT 员工表; CREATE TABLE attendance ( id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL, work_date DATE NOT NULL, status CHAR(1) COMMENT A正常 L迟到 E早退 X缺勤, UNIQUE KEY uk_emp_date(emp_id, work_date) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT 考勤表; CREATE TABLE salary ( id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL, effective_month CHAR(7) NOT NULL COMMENT 薪资月份格式YYYY-MM, amount DECIMAL(10,2) NOT NULL, UNIQUE KEY uk_emp_month(emp_id, effective_month) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT 薪资表;这里的几个设计要点值得注意。薪资表用effective_month记录月份而不是用create_time因为薪资是按月结算的同一个员工同一个月份只能有一条记录所以建联合唯一索引。考勤表用(emp_id, work_date)做唯一索引避免同一天重复打卡产生两条数据。很多从学生信息管理系统改出来的代码员工表连emp_no唯一索引都不建上线后数据出了问题才补代价极大。3.2 MyBatis 多条件查询人事员工列表怎么写人事列表页通常有“姓名”“部门”“在职状态”三个查询条件用户可能只填一个也可能三个都填。如果在 Java 代码里拼接 SQL或者用if标签时不小心在 WHERE 后面多出一个 AND都会出问题。MyBatis 自带的where标签可以自动处理掉首个条件前的 AND。先写 Mapper 接口package com.company.hr.mapper; import com.company.hr.entity.Employee; import java.util.List; public interface EmployeeMapper { ListEmployee selectByCondition(EmployeeQuery query); }对应 XMLselect idselectByCondition resultTypecom.company.hr.entity.Employee SELECT id, emp_no, emp_name, dept_id, position, hire_date, salary_base, status FROM employee where if testempName ! null and empName ! AND emp_name LIKE CONCAT(%, #{empName}, %) /if if testdeptId ! null AND dept_id #{deptId} /if if teststatus ! null AND status #{status} /if /where ORDER BY emp_no /select逻辑说明where会在元素内部存在内容时自动生成 WHERE 关键字并把第一个多余的 AND 或 OR 去掉。if的判断条件里字符串要同时检查 null 和空串否则用户在前端清空输入框后传过来的是SQL 还是会拼接。很多人习惯在 Java 里拼%再传入也不是不行但直接写在 XML 里能让 SQL 的整体结构一目了然。3.3 事务在批量调薪操作里的设定人事系统里的“批量调薪”是最典型的事务场景。比如给某部门所有员工统一增加 300 元底薪执行的是多条 UPDATE。如果调第 10 个人时数据库连接断掉前面 9 个人已经更新了后面的人没更新薪资数据就出现了中间状态。这种问题必须用事务解决。Service public class SalaryService { private final EmployeeMapper employeeMapper; Autowired public SalaryService(EmployeeMapper employeeMapper) { this.employeeMapper employeeMapper; } Transactional(rollbackFor Exception.class) public void batchAdjust(ListInteger employeeIds, BigDecimal increment) { for (Integer id : employeeIds) { employeeMapper.increaseSalary(id, increment); } } }这里有个很小的细节Transactional默认只在抛出 RuntimeException 时回滚而 SQL 操作抛出的异常可能是 SQLException这种检查型异常不会触发回滚。所以一定要写rollbackFor Exception.class。另外事务最好放在 Service 层不要直接写在 Controller 方法上。我见过一个项目把Transactional加在 Controller 的请求处理方法上结果事务在 SpringMVC 的拦截器还没结束时就被迫提交连接池很快就满了。人事系统虽然并发不高但这种错误积累下来一到月底算考勤就卡死。4. JSP 在企业人事系统里的三个必填坑4.1 为什么人事系统 JSP 页面要重度使用 EL 和 JSTLJSP 页面在 WEB-INF 目录下浏览器无法直接访问只能通过 Controller 转发这是权限控制的基础。但很多 SSM 项目真正难维护的不是后端而是 JSP 里夹杂的 Java 代码。我以前接手过一个老系统一个员工列表页面里几乎每一行都有%和%甚至直接用% JSONArray arr ... %这种写法。这种代码在 Tomcat 下能编译但维护的人必须懂 Java、JSP 又要在 HTML 间来回切换改了后端字段之后页面时不时报错。JSP 的规范历史里scriptlet 是早期做法EL 表达式和 JSTL 标签才是视图层该有的样子。EL 表达式${employee.empName}负责输出数据JSTL 的c:forEach、c:if负责循环和条件展示。这样 JSP 页面里没有任何业务逻辑只展示 Controller 放进 Model 的数据。一个常见的反模式是在 JSP 顶部写% page importorg.json.JSONArray %然后使用new JSONArray(...)。如果纯粹是为了把后端数据转成 JSON这应该在 Controller 或 Service 里完成而不是在页面里现场转换。JSP 里出现import和new基本就能判断这段代码需要重构。4.2 分页列表和表单回显的核心 JSP 片段企业人事系统的员工列表页一般需要展示工号、姓名、部门、职位、入职日期和操作按钮。最省事的做法是使用 PageHelper 插件在 Controller 里设置当前页和每页条数返回一个 PageInfo 对象。% taglib prefixc urihttp://java.sun.com/jsp/jstl/core % % taglib prefixfmt urihttp://java.sun.com/jsp/jstl/fmt % table classtable table-bordered thead tr th工号/thth姓名/thth部门/thth职位/thth入职日期/thth操作/th /tr /thead tbody c:forEach items${page.list} varemp tr td${emp.empNo}/td td${emp.empName}/td td${emp.deptName}/td td${emp.position}/td tdfmt:formatDate value${emp.hireDate} patternyyyy-MM-dd//td td a href${pageContext.request.contextPath}/employee/edit?id${emp.id}编辑/a a href${pageContext.request.contextPath}/employee/delete?id${emp.id} onclickreturn confirm(确认删除该员工);删除/a /td /tr /c:forEach /tbody /table c:if test${page.hasPreviousPage} a href?pageNum${page.prePage}empName${param.empName}上一页/a /c:if span第 ${page.pageNum} / ${page.pages} 页/span c:if test${page.hasNextPage} a href?pageNum${page.nextPage}empName${param.empName}下一页/a /c:if这里有个细节查询条件的回显不要用 Controller 重新传一次直接用${param.empName}读取 URL 上的请求参数这样即使 Controller 忘了把查询对象放回 Model分页链接也依然能保留查询条件。pageContext.request.contextPath是 JSP 内置对象用来拼接项目根路径避免应用部署在非 ROOT 路径时链接 404。4.3 屏蔽 JSP 离开页面提示的正确思路人事系统的员工编辑页通常需要填写入职日期、所属部门、薪资等多项信息用户如果没保存就点其他导航应该得到提醒。这个功能常用beforeunload事件做但直接写window.onbeforeunload function() { return 您确定要离开吗; };会有一个大坑点击“保存”按钮时也会触发这个提示提交表单被卡住。更稳的做法是只在表单内容发生变化后才绑定提示逻辑let formChanged false; const form document.getElementById(empForm); form.addEventListener(input, () { formChanged true; }); document.querySelectorAll(#empForm select).forEach(el { el.addEventListener(change, () { formChanged true; }); }); window.addEventListener(beforeunload, (event) { if (!formChanged) return; event.preventDefault(); event.returnValue 存在未保存的修改确定离开吗; }); // 保存成功后重置标记 form.addEventListener(submit, () { formChanged false; });顺便说一个 JSP 调试问题很多人在浏览器看到 500 错误但不知道 Tomcat 把 JSP 翻译成什么样子。其实 Tomcat 会把 JSP 编译成_xxx_jsp.java和对应的 class 文件默认存放在work/Catalina/localhost/你的应用名/org/apache/jsp目录下。如果 JSP 页面里 EL 表达式写错或者依赖了某个不存在的 Java 方法打开这个编译后的 java 文件就能看到 Tomcat 生成的 Servlet 具体在哪一行报错这比对着浏览器 500 页面瞎猜快得多。5. 打包交付把 SSM 人事系统 zip 包交给 Tomcat5.1 zip 包里的目录结构和构建命令拿到一个名为ssm企业人事管理系统的设计与实现jsp.zip的项目解压后通常是标准的 Maven 结构。在真正交付时我习惯保留以下内容hr-system/ ├── pom.xml ├── sql/ │ └── hr_init.sql ├── src/ │ ├── main/ │ │ ├── java/com/company/hr/ │ │ │ ├── controller/ │ │ │ ├── service/ │ │ │ ├── mapper/ │ │ │ └── entity/ │ │ ├── resources/ │ │ │ ├── spring/ │ │ │ ├── mapper/ │ │ │ └── jdbc.properties │ │ └── webapp/ │ │ ├── WEB-INF/ │ │ │ ├── web.xml │ │ │ └── jsp/ │ │ └── static/ └── README.md这个目录里sql 目录是整个 zip 包能否跑起来的前提。人事系统里的表结构、初始管理员账号、演示数据都放在这里如果只有源码没有 SQL拿到包的人需要自己逆向建库成本极高。README.md 里至少要写清楚 JDK 版本、Tomcat 版本、MySQL 版本和初始化步骤。如果项目是 Maven 工程打包命令很简单mvn clean package -DskipTests执行完会得到target/hr-system.war。把这个 war 包放到 Tomcat 的webapps目录Tomcat 会自动解压。如果希望访问路径不带应用名就把解压后的目录改名成ROOT再把原来的 war 包删掉。这样做的好处是 JSP 里的${pageContext.request.contextPath}始终为空开发期和生产环境的行为更一致。5.2 部署到 Tomcat 后启动失败的典型排查路径部署后第一件事不是打开浏览器而是看日志。Tomcat 的日志在logs目录下启动信息看catalina.out应用报错看localhost.日期.log。如果是 spring 相关错误优先确认以下三点第一ClassNotFoundException出现org.springframework.web.context.ContextLoaderListener说明依赖 jar 没有进去。检查 war 包里的WEB-INF/lib目录确认 spring-web、spring-webmvc、mybatis-spring 等核心 jar 都存在。这种情况经常出现在把 zip 包里的源码直接拖进 IDEA 但没有 Maven 刷新的时候。第二JSP 报错Unable to compile class for JSP时去work/Catalina/localhost/hr-system/org/apache/jsp目录查看对应 JSP 的.java文件找到编译出错的行。比如 EL 表达式里写了一个不存在的方法名Tomcat 编译成 Java 代码后会在调用位置报出编译错误出错信息比浏览器页面里的包装信息准确得多。第三JDK 版本问题。SSM 项目如果基于 Spring 4.3 和 Java 8 开发部署机的 JRE 一定不能是 Java 9 以上。Spring 4.3 使用旧的javax.servlet包在较新 JDK 上会出现模块化导致的编译问题表现是 JSP 无法编译、页面 500。排查时先java -version看一下 Tomcat 进程使用的 JDK再确认 Tomcat 版本和 JDK 的兼容表。最后分享一个我每次部署完必做的验证技巧在员工列表页的请求上临时加一个debug1参数在 Controller 中打印当前数据源连接是否正常。确认 SSM 三层都通了调用链没问题后再去调页面样式。如果发现员工列表能打开但点击下一页总是跳回第一页优先检查分页插件拦截的 Mapper 方法是否被多次扫描以及 URL 上是否只保留了pageNum而丢了查询条件。本文还有配套的精品资源点击获取
返回列表