基于Spring Boot与MyBatis构建企业级人力资源管理系统实战

发布时间:2026/9/3 3:57:47
基于Spring Boot与MyBatis构建企业级人力资源管理系统实战 简介这是一套完整的JavaWeb人力资源管理系统源码面向Java初学者与Web开发入门者适用于课程设计、毕业设计及中小型企业HR模块快速原型开发。系统涵盖员工管理、招聘、考勤、薪资、培训、简历审核等核心业务功能采用JSPServletJDBC经典三层架构便于理解MVC模式与Web应用开发全流程。压缩包共268个文件含71个Java源文件、71个编译后Class文件、40个JSP页面、38个XML配置文件含web.xml与数据库映射、22个Jar依赖库辅以CSS、JS、图片及SQL建表脚本等资源整体大小24MB结构清晰、模块划分明确。已有4763人学习下载源码可直接导入Eclipse或MyEclipse运行配套SQL脚本支持MySQL快速部署适合边学边练、调试分析与二次开发。1. 项目概述从零到一构建一个企业级JavaWeb人力资源管理系统最近在整理硬盘翻出来一个几年前做过的企业级人力资源管理系统HRM的源码。这个项目当时是为一家中型制造业公司定制的从需求对接到最终上线前前后后折腾了小半年。今天借着这个机会把整个系统的设计思路、技术选型、核心模块的实现还有那些年踩过的坑系统地梳理一遍。如果你正在学习JavaWeb或者正打算自己动手做一个类似的管理系统来练手、甚至作为毕业设计那这篇内容应该能给你提供一条清晰的路径和不少实用的参考。简单来说这个系统就是一个基于B/S架构使用Java技术栈开发的用于企业人力资源数字化管理的Web应用。它要解决的核心问题就是把传统企业里那些散落在Excel表格、纸质档案甚至员工脑子里的“人”的信息和流程搬到线上来实现规范化、流程化和数据化。核心用户包括HR部门、各部门经理以及普通员工覆盖了从员工入职到离职的全生命周期管理。下面我们就一层层剥开这个系统的“外壳”看看里面到底是怎么运作的。2. 系统整体架构与技术选型背后的思考做一个管理系统技术选型是第一步也是最容易让人纠结的一步。当时市面上可选的技术非常多我们的选择是基于几个核心考量团队技术栈的熟悉度、社区的活跃度与生态成熟度、项目的可维护性以及未来的扩展性。最终敲定的是一套非常经典且稳健的JavaWeb“全家桶”。2.1 后端技术栈为什么是Spring Boot MyBatis后端我们选择了Spring Boot 2.x作为核心框架。没选传统的SSHStruts2 Spring Hibernate或者SSMSpringMVC Spring MyBatis的零散整合方式直接上Spring Boot图的就是一个“开箱即用”。它内嵌了Tomcat约定大于配置能让我们快速搭建起项目骨架把精力集中在业务逻辑上而不是没完没了的XML配置。这对于中小型团队或者个人开发者来说效率提升是巨大的。数据持久层我们放弃了重量级的Hibernate选择了MyBatis。这里有个很重要的权衡HR系统的业务表结构相对固定但查询需求极其复杂多变。比如HR可能需要根据“入职时间在2023年、部门为技术部、学历为本科及以上、并且上月绩效考核为A的员工”这样的组合条件进行筛选。Hibernate的HQL或者Criteria API在处理这种动态多条件查询时代码会显得比较臃肿。而MyBatis的XML映射文件可以非常灵活地编写动态SQL通过if,choose等标签SQL的可读性和可控性更强也方便我们针对复杂查询进行SQL优化。毕竟对于管理系统复杂列表查询的性能是用户体验的关键。注意选择MyBatis意味着你需要自己编写和维护更多的SQL这对开发者的SQL功底有一定要求。但换来的是极致的灵活性和在复杂业务场景下更优的性能表现。如果你的业务非常简单且团队更熟悉JPA的面向对象思维那么Spring Data JPA也是一个很好的选择。2.2 前端技术栈从JSP到前后端分离的演进这个项目最初期用的是JSP JSTL Bootstrap这套经典组合。JSP直接在后端渲染页面开发速度快对于表单提交、页面跳转这类传统操作非常顺手。Bootstrap则能快速搭建出风格统一、响应式的管理界面大大减少了前端UI的工作量。但是随着模块增多尤其是涉及到更多动态交互如无刷新数据加载、复杂表单验证时JSP混合Java代码和HTML的模式就显得有些力不从心代码可维护性下降。因此在项目后期重构部分模块时我们引入了Vue.js作为前端框架尝试了前后端分离的架构。后端Spring Boot只提供RESTful API前端通过Vue进行组件化开发。这样做的好处是前后端职责清晰并行开发效率高前端用户体验也更流畅。不过这也带来了额外的学习成本和部署复杂度。对于初学者或毕业设计我建议可以从JSPBootstrap开始先把核心业务跑通理解整个MVC的流程。有精力再去探索前后端分离这是一个很自然的技术演进路径。2.3 其他关键组件权限控制Apache Shiro。相比Spring SecurityShiro的API更直观易懂配置也相对简单。对于HRM这种权限模型用户-角色-权限清晰的项目Shiro完全够用可以精细控制到菜单、按钮甚至API接口的访问。数据库MySQL 5.7。开源、稳定、生态完善是中型管理系统的首选。我们使用了InnoDB存储引擎并对核心表如员工表、考勤记录表建立了合适的索引。项目构建与管理Maven。依赖管理、项目构建、打包部署一条龙服务。缓存Redis。用于缓存一些不常变动但访问频繁的数据如部门树形结构、系统配置项、用户会话信息等显著减轻数据库压力。3. 核心功能模块设计与实现拆解一个完整的HRMS其核心是围绕“人”的“进、管、出”流程。我们的系统主要包含了以下六大模块每个模块的设计都有其特定的业务考量。3.1 组织架构与员工信息管理系统的基石这是整个系统的数据核心设计的好坏直接影响到其他所有模块。数据库设计要点 员工表employee的设计至关重要。除了基本信息工号、姓名、性别、身份证号、联系方式关键字段包括dept_id部门ID关联部门表。position_id岗位ID关联岗位表。这里将部门和岗位分开是因为一个部门下可能有多个岗位且员工岗位调动可能不涉及部门变动。hire_date入职日期、status员工状态试用、正式、离职等。status字段用于软删除和状态流转控制。敏感信息如身份证号、银行卡号在数据库存储时应进行加密处理。部门树形结构实现 部门通常具有层级关系如公司-事业部-部门-小组。我们在部门表department中设计了一个parent_id字段指向父部门ID顶级部门的parent_id为0或NULL。在Java后端我们通过递归或一次查询后内存组装的方式构建出完整的部门树形结构提供给前端以树形控件展示。实操心得处理树形数据时避免在循环中频繁查询数据库。最佳实践是一次性查询出所有部门数据到内存List然后通过Map以id为key部门对象为value和递归算法在内存中构建树。这样可以减少数据库查询次数性能提升非常明显。3.2 招聘管理流程化与状态驱动招聘模块本质上是一个状态机驱动的流程管理系统。我们设计了一个招聘需求表recruitment_request和一个候选人表candidate。候选人有一个stage字段表示当前所处阶段如“简历筛选”、“初试”、“复试”、“HR面试”、“发放Offer”、“已入职”、“已淘汰”。每个状态的变更都通过一个独立的操作如“安排面试”、“录入面试结果”、“发送Offer”来触发并记录操作人和时间到流程日志表。关键实现我们使用了枚举Enum来定义所有可能的状态和操作并在服务层编写状态转换的逻辑。例如从“初试”到“复试”必须满足“初试结果”为“通过”且由有权限的人执行操作。这种设计保证了流程的规范性和数据的可追溯性。3.3 考勤与薪酬核算规则与计算的复杂性这是业务逻辑最复杂的模块之一。考勤子系统规则定义需要设计考勤规则表定义上班时间、下班时间、迟到早退的容忍分钟数、是否弹性工作制等。数据采集与考勤机对接通过TCP/IP或数据库直连定时任务每天同步原始打卡记录到attendance_record表。异常处理系统根据规则自动判断每条记录的异常状态正常、迟到、早退、缺卡。对于“缺卡”需要提供补卡申请流程由上级审批后更新状态。统计报表按月统计每个员工的出勤天数、各种异常次数为薪酬核算提供依据。薪酬核算子系统薪资结构设计薪资项表salary_item如“基本工资”、“岗位津贴”、“绩效奖金”、“社保个人部分”、“个税”等并标识其为“加项”或“减项”。薪资模板为每个岗位或员工绑定一个薪资模板模板定义了其包含的薪资项及计算规则固定值、公式计算等。核算流程每月在固定时间点如5号系统启动核算任务。任务会获取员工当月考勤统计结果。获取员工的薪资模板。根据模板中的公式如基本工资 - (迟到次数 * 罚款单价)动态计算每个薪资项。调用税务计算接口或内置规则计算个人所得税。汇总所有加项和减项生成最终应发工资和实发工资存入payroll表。审批与发放生成的工资单需要经过财务或HR负责人审批确认后才能锁定数据并进入银行代发流程。踩坑记录薪资计算涉及钱必须保证幂等性。即同一员工、同一月份多次触发核算结果必须一致且不会重复生成记录。我们的做法是在核算前检查payroll表是否已存在该员工该月的记录如果存在且已锁定则跳过或报错。计算过程所有用到的变量如考勤数据必须在计算开始时一次性取好避免在计算过程中数据被修改。3.4 绩效管理目标与评估的闭环我们采用相对常见的KPI关键绩效指标模式。周期与模板定义绩效周期季度、年度并为不同岗位设置绩效评估模板模板中包含若干考核项KPI及其权重、评分标准。目标设定Goal Setting周期初员工与上级共同在系统中设定本期的绩效目标关联到具体的KPI项。自评与上级评周期末员工先进行自评然后上级进行评分并填写评语。系统支持多级评审如上级的上级确认。结果校准与反馈HR或部门负责人可能对评分进行校准如强制分布。最终结果与员工确认后归档并作为奖金、晋升的依据。技术实现关键点这个模块涉及大量的表单动态渲染和审批流。我们使用了一个工作流引擎的轻量级实现也可以自己基于状态机设计来驱动“目标制定-提交-自评-上级评-校准-确认”这个流程。每个节点的操作权限和可见字段都需要精细控制。3.5 培训与发展与员工自助培训管理管理培训课程、讲师、资源员工可报名参加系统记录培训结果。员工自助平台这是提升员工体验的关键。员工可以在此查看自己的档案、工资条、考勤明细提交请假、报销、补卡等各类申请查看公司公告。这极大地减少了HR的日常事务性工作。请假流程示例员工填写请假单类型、起止时间、事由。提交后根据请假天数和公司规则自动路由给相应审批人如直接上级、部门总监。审批人收到待办任务系统消息或邮件通知进行审批。审批通过后状态更新并自动同步到考勤预期数据中。员工和HR均可查看流程状态。4. 数据库设计与核心表结构解析数据库设计是系统的骨架。这里列举几个最核心的表及其关联让你感受一下设计思路。表关系核心思想员工employee是中心实体通过外键与部门department、岗位position关联。各种业务单据如请假单leave_application、报销单expense_report都以employee_id关联到具体员工并包含自己的业务状态和审批流信息。部分核心表字段说明表名关键字段说明与设计理由employeeid,emp_no(工号),name,dept_id,position_id,hire_date,statusemp_no唯一索引。status用于软删除0离职/1在职。departmentid,name,parent_id,leader_id(负责人)parent_id构建树形结构。leader_id关联employee.id。attendance_recordid,employee_id,check_date,check_in_time,check_out_time,status以(employee_id, check_date)建立联合唯一索引防止重复打卡记录。leave_applicationid,employee_id,leave_type,start_time,end_time,duration,status,approver_idstatus跟踪流程提交中/审批中/已批准/已驳回。duration单位可以是天或小时需统一。payrollid,employee_id,pay_period(发薪月份),base_salary,bonus,tax,net_salary,is_confirmed(employee_id, pay_period)唯一索引确保幂等性。is_confirmed标记是否已审批锁定。索引策略主键所有表使用自增整数id作为主键。外键索引在所有作为外键的字段上建立索引如employee.dept_id,leave_application.employee_id。查询优化索引在经常用于WHERE条件、ORDER BY或JOIN的字段上建立索引。例如attendance_record表的(employee_id, check_date)联合索引对于查询某个员工某个月的考勤记录速度极快。唯一索引保证业务唯一性的字段组合如employee.emp_no,payroll.(employee_id, pay_period)。5. 权限系统设计与Shiro整合实战没有权限控制的管理系统就是“裸奔”。我们采用经典的RBAC基于角色的访问控制模型用户 - 角色 - 权限。数据库设计sys_user: 系统用户表与employee表一对一或关联。sys_role: 角色表。sys_permission: 权限表存储权限标识符如employee:view,salary:approve和对应的资源菜单URL、按钮ID、API路径。user_role,role_permission: 用户-角色、角色-权限的关联表。与Shiro整合的关键步骤自定义Realm这是Shiro的核心你需要继承AuthorizingRealm类重写doGetAuthenticationInfo认证和doGetAuthorizationInfo授权方法。在这里你从数据库中查询用户的角色和权限信息并封装成Shiro能识别的对象如SimpleAuthenticationInfo,SimpleAuthorizationInfo。权限标识符我们将权限细化为三级模块:操作:资源例如hrm:employee:view查看员工、hrm:salary:export导出工资单。这样在控制层方法或页面上可以使用注解RequiresPermissions(hrm:employee:view)进行声明式控制。动态菜单用户登录后根据其拥有的权限标识符过滤出有权限访问的菜单项动态生成左侧导航菜单。这通常在前端或后端模板中完成。按钮级控制在JSP或Thymeleaf模板中可以使用Shiro标签shiro:hasPermission namehrm:salary:export来控制一个导出按钮是否显示。注意事项Shiro的权限缓存非常重要。每次授权都查数据库是无法接受的。务必在Realm中配置合理的缓存策略如使用Ehcache或Redis。同时当用户的权限被管理员修改后需要手动清除该用户的授权缓存否则会出现权限更新延迟的问题。6. 典型业务场景代码实现剖析光讲理论不够我们看两个典型业务的后端代码实现片段。6.1 复杂条件分页查询员工列表这是HR最常用的功能。前端传递一堆可能为空的查询条件姓名、部门、状态等后端需要动态拼接SQL。Controller层RestController RequestMapping(/api/employee) public class EmployeeController { Autowired private EmployeeService employeeService; GetMapping(/list) public PageResultEmployeeVO listEmployees(EmployeeQueryDTO queryDTO) { // queryDTO 包含了 pageNum, pageSize, name, deptId, status 等字段 return employeeService.queryEmployeeList(queryDTO); } }Service层与Mapper动态SQL 在EmployeeMapper.xml中我们利用MyBatis的动态SQL功能select idselectEmployeeList parameterTypeEmployeeQueryDTO resultMapEmployeeResult SELECT e.*, d.name as dept_name, p.name as 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 ! DELETED !-- 软删除过滤 -- if testname ! null and name ! AND e.name LIKE CONCAT(%, #{name}, %) /if if testdeptId ! null AND e.dept_id #{deptId} /if if teststatus ! null and status ! AND e.status #{status} /if !-- 可以添加更多条件如入职时间范围 -- if testhireDateStart ! null AND e.hire_date #{hireDateStart} /if if testhireDateEnd ! null AND e.hire_date #{hireDateEnd} /if ORDER BY e.id DESC /selectPageHelper是一个常用的分页插件在Service方法中调用PageHelper.startPage(queryDTO.getPageNum(), queryDTO.getPageSize())后下一次的Mapper查询就会自动进行物理分页。6.2 发起请假申请与审批流转这是一个典型的工作流场景。1. 创建请假单Controller:PostMapping(/leave/apply) public Result applyLeave(RequestBody LeaveApplicationDTO dto, CurrentUser SysUser user) { // 1. 数据校验时间是否合法、假期余额是否足够等 // 2. 设置申请人ID、初始状态为“提交中” dto.setApplicantId(user.getEmployeeId()); dto.setStatus(LeaveStatus.SUBMITTED.getCode()); // 3. 调用服务层 leaveApplicationService.createApplication(dto); return Result.success(请假申请提交成功等待审批); }2. 服务层创建并启动流程Service:Override Transactional // 保证创建单据和启动流程在一个事务里 public void createApplication(LeaveApplicationDTO dto) { // 1. DTO 转 Entity并保存到数据库 LeaveApplication entity convertToEntity(dto); leaveApplicationMapper.insert(entity); // 2. 根据请假规则确定审批人可能是直接上级或更高级别领导 Long approverId findApproverByRule(entity.getApplicantId(), entity.getDuration()); // 3. 更新单据的当前审批人状态改为“审批中” entity.setApproverId(approverId); entity.setStatus(LeaveStatus.UNDER_REVIEW.getCode()); updateById(entity); // 4. 发送通知系统消息、邮件、钉钉/企业微信给审批人 notificationService.sendLeaveApprovalNotification(approverId, entity.getId()); }3. 审批操作Service:Override Transactional public void approveApplication(Long applicationId, Long approverId, boolean approved, String comment) { LeaveApplication application getById(applicationId); // 1. 校验单据是否存在、状态是否为“审批中”、当前用户是否为审批人 validateApprovalPermission(application, approverId); // 2. 更新单据状态和审批意见 if (approved) { application.setStatus(LeaveStatus.APPROVED.getCode()); // 可能触发后续动作扣减假期余额、同步至考勤预期 deductLeaveBalance(application.getApplicantId(), application.getLeaveType(), application.getDuration()); syncToAttendance(application); } else { application.setStatus(LeaveStatus.REJECTED.getCode()); } application.setApprovalComment(comment); application.setApprovalTime(new Date()); updateById(application); // 3. 通知申请人审批结果 notificationService.sendLeaveResultNotification(application.getApplicantId(), approved); }7. 部署、运维与性能优化经验谈项目开发完部署上线才是真正的开始。7.1 部署实践我们采用最经典的Linux Nginx Tomcat MySQL架构。Linux服务器使用CentOS 7配置好防火墙、时区、Java环境。Nginx作为反向代理和静态资源服务器。将前端的HTML/CSS/JS文件放在Nginx下提高访问速度。同时将/api/路径的请求代理到后端的Tomcat。Nginx还能轻松配置负载均衡、SSL证书HTTPS、压缩传输等。Tomcat使用Spring Boot内嵌的Tomcat通过java -jar your-application.jar --server.port8080启动。生产环境建议使用nohup或配置为systemd服务保证进程稳定运行。MySQL单独部署在一台服务器上定期进行备份mysqldump binlog。务必修改默认端口和root密码创建专属的、权限受限的数据库用户给应用使用。7.2 性能优化点数据库层面索引如前所述分析慢查询日志为高频查询条件建立合适的索引。但也要避免过度索引影响写性能。SQL优化避免SELECT *只取需要的字段。多表关联时注意关联字段是否有索引。复杂查询考虑是否能用子查询或JOIN优化。连接池使用HikariCP或Druid等高性能连接池并合理配置最大最小连接数、超时时间。应用层面缓存使用Redis缓存热点数据。例如部门树、数据字典如请假类型、用户权限信息。注意设置合理的过期时间并处理好缓存与数据库的一致性。异步处理对于耗时的操作如发送批量邮件、生成复杂的统计报表不要阻塞主请求线程。可以使用Spring的Async注解或者集成消息队列如RabbitMQ进行异步解耦。JVM调优根据服务器内存大小调整Spring Boot应用的JVM参数如堆内存大小-Xms,-Xmx、垃圾回收器等。前端层面压缩JS、CSS、图片资源。利用浏览器缓存。对于数据量大的表格采用分页或虚拟滚动不要一次性加载所有数据。7.3 常见问题与排查技巧应用启动失败端口被占用netstat -tlnp | grep 8080查找占用8080端口的进程。kill -9 PID结束该进程或修改应用的server.port配置。数据库连接池耗尽现象应用日志中出现Cannot get connection from pool或大量超时错误。排查首先检查数据库服务是否正常网络是否通畅。然后检查应用连接池配置是否合理最大连接数是否太小。最后通过Druid监控或日志分析是否有SQL执行过慢导致连接被长时间占用或者代码中是否有忘记关闭数据库连接的情况如未在finally块中关闭ResultSet, Statement, Connection。前端页面访问慢打开浏览器开发者工具F12的Network面板查看哪个资源加载耗时最长。如果是API接口慢查看后端应用日志和数据库慢查询日志。如果是静态资源如图片、JS慢检查Nginx配置和服务器带宽。权限配置了但不生效检查Shiro的过滤器链配置是否正确请求路径是否被正确拦截。检查自定义Realm中用户的权限信息是否被正确查询和返回。最重要的一点检查Shiro的权限缓存。修改用户权限后尝试退出重新登录或者手动清除该用户的授权缓存securityManager.logout()或直接操作缓存管理器。这个项目做下来最大的体会是开发一个管理系统技术实现只是基础更重要的是对业务逻辑的理解和抽象能力。如何将HR繁杂的线下流程精准地转化为清晰的数据模型和状态机是成败的关键。代码的健壮性、异常处理、日志记录这些在练手时容易忽略的细节在生产环境中却至关重要。建议你在实现每个功能时多思考边界情况如果员工离职了他的历史数据怎么处理如果审批人不在岗流程如何流转这些思考会让你的系统从一个“玩具”升级为一个真正可用的“工具”。本文还有配套的精品资源点击获取