☰
SpringBoot+MyBatis构建人力资源管理系统:设计思路与实战解析
2026/10/6 9:22:01 网站建设 项目流程

一个多月前,有个学弟抱着笔记本来找我,说毕设选题选了“基于SpringBoot的人力资源管理系统”,但在网上翻了好几天,要么是SSH时代的老古董代码,要么是货不对板的半成品。我帮他重新梳理了业务模块、敲定了技术方案,从数据库建模到权限控制一步步把项目搭了起来,最后不仅顺利过了答辩,还被评委夸了句“业务闭环完整”。今天就把这个项目的完整设计思路、核心代码逻辑以及我踩过的坑一次说清楚,希望能帮到正在为SpringBoot毕设头秃的同学。

这套基于SpringBoot的人力资源管理系统,本质上是一个标准的企业级CRUD项目,但麻雀虽小五脏俱全。它覆盖了员工档案管理、部门岗位维护、考勤记录、薪酬核算、系统用户与权限控制等核心业务,用的是SpringBoot + MyBatis + MySQL这套Java后端最主流的技术组合,前端可以选Vue做前后端分离,也可以直接用Thymeleaf渲染,怎么省事怎么来。适合正在准备毕业设计、想系统学习SpringBoot整合实战、或者打算在校招简历里放一个完整项目的同学参考,尤其是想快速复现一套能跑通、能答辩、能讲清楚设计逻辑的项目的人。

1. 项目全貌与技术选型:为什么是SpringBoot + MyBatis

1.1 人力资源管理系统到底要解决什么

人力资源管理系统,简称HR系统,解决的是一家公司里“人”的信息化问题。往细了拆,它至少有这几块业务:员工档案(入职、转正、离职)、组织架构(部门、岗位)、考勤(打卡、请假、迟到早退)、薪酬(基本工资、绩效、补贴)、招聘(职位发布、简历筛选)、培训(计划、记录)。做毕设不需要全部做完,但至少要把员工、部门、考勤、薪酬这几条主线跑通,形成一个能自圆其说的闭环。

我当时给学弟定的功能边界是:管理员登录后能维护部门树和岗位字典,能对员工档案做增删改查和条件检索;员工每天可以登记上下班时间,系统自动计算当日工时和迟到早退状态;每月月底系统根据考勤汇总和员工薪资标准自动生成当月工资单;不同角色的用户登录后看到的数据范围不一样,普通员工只能看自己的信息,HR能看到全公司。

这几块业务的实体关系并不复杂,核心是员工表,它关联着部门、岗位、用户账号;考勤表和薪酬表都通过员工ID关联到员工,属于典型的一对多。数据库表设计清楚后,后面的代码写起来会非常顺。

1.2 为什么是SpringBoot + MyBatis这套组合

先说SpringBoot。很多同学在学校里学过SSM框架,但SSM的配置文件实在太多了,spring-mvc.xml、spring-mybatis.xml、web.xml,每个都要手写Bean定义,光是让项目启动不报错就能折腾一整天。SpringBoot把这些繁琐的配置几乎全部干掉,通过自动装配机制,你只需要在pom.xml里引入依赖,再写一个启动类,就能跑起一个内嵌Tomcat的Web服务。

SpringBoot最核心的机制是自动装配。@SpringBootApplication这个组合注解里藏着@EnableAutoConfiguration,它通过spring.factories文件里声明的配置类,在项目启动时按条件装配Bean。举个例子,你pom里引入了spring-boot-starter-data-redis,启动时RedisAutoConfiguration就会被加载,再根据你application.yml里有没有配redis相关属性,决定是否创建RedisTemplate的Bean。理解了这个原理,你就能明白为什么SpringBoot能“开箱即用”,也就能跟评委讲清楚为什么选它而不是SSM。

再说MyBatis。SpringBoot整合MyBatis有两种路子,一种是纯注解写SQL,一种是用XML文件写SQL。我的建议是,但凡你做的项目涉及多表关联查询,一定要用XML的方式。因为人力资源管理系统里像“查询员工列表并附带部门和岗位名称”这种SQL,用注解写要么拼字符串拼到怀疑人生,要么可读性差得没法维护。XML里的动态SQL标签足够强大,where、if、foreach一套组合拳下来,复杂条件查询轻轻松松。

为什么不推荐用Spring Data JPA?不是说JPA不好,而是对于毕设项目里的多表关联、复杂统计、动态条件拼SQL,JPA的规则实在太多了,实体关系映射一旦没写好,生成出来的SQL性能难看,排查问题也麻烦。MyBatis的思路就是SQL掌握在你自己手里,性能可控、逻辑透明,答辩的时候你能对着代码讲清楚每一步在干什么。

1.3 数据库建模:五张核心表的字段设计思路

数据库设计是一切的起点。我给学弟设计的表结构里,最核心的五张表是:用户表(sys_user)、员工表(employee)、部门表(department)、考勤表(attendance)、薪酬表(salary)。

员工表是整个系统的枢纽,字段包括员工编号(工号)、姓名、性别、手机号、邮箱、入职日期、转正日期、部门ID、岗位ID、薪资标准、状态。这里有个细节:薪资标准这张表里存还是不存?我建议在员工表里直接放一个base_salary字段,因为毕设项目不需要做太复杂的调薪历史,直接关联反而把简单问题复杂化了。

部门表要注意的是层级关系,用一个parent_id字段表示上级部门,0表示根节点。这种邻接表模型实现起来最简单,查询子部门的时候代码里递归一下就行。

考勤表的设计要特别注意唯一索引。每天每个员工只能有一条考勤记录,所以要在employee_id和work_date这两个字段上建联合唯一索引(UNIQUE KEY uk_emp_work (employee_id, work_date))。这个索引不仅保证了数据不会重复,还能让后续按员工、按日期范围查询的效率高很多。

薪酬表的设计核心是月份+员工ID。一个员工一个月只有一条薪酬记录,同样建议在employee_id和salary_month上建联合唯一索引。薪酬字段包括基本工资、绩效奖金、餐补、社保扣款、实发工资,其中实发工资是计算出来的,不用手工录入。

除了这五张表,如果还做了招聘模块,可以加一张resume表存候选人信息;做了培训模块就加一张training表。资历表的内容不必贪多,把主线的五张表做扎实,数据关系讲得清楚,比堆一堆没用功能的表强得多。

2. 核心机制与关键环节:SpringBoot里那些绕不开的点

2.1 项目分层架构:包结构到底怎么分

很多新手写SpringBoot项目,喜欢把代码全塞进controller里,一个方法干所有事。这种写法在毕设答辩时非常容易被追问,因为你讲不清楚职责边界。我推荐的分层是标准的四层结构:controller、service、mapper、entity,再加上一个config包放配置类,一个common包放统一返回结果和异常处理。

代码结构清晰了,还有一个好处是事务边界好控制。一个业务操作往往要写多张表,比如新增员工的同时要创建登录账号,这时候必须在Service层的方法上加@Transactional注解。如果事务写在Controller层,所有接口共用一套事务控制,粒度太粗,容易出现一个查询接口把连接池占满的问题。

开发时我会在Service接口和实现类分开(Service + ServiceImpl),虽然麻烦一点,但答辩时可以说“这是面向接口编程,方便后续替换实现”,这也算一个加分项。

2.2 统一返回结果与全局异常处理

前后端交互必须有统一的接口规范。我习惯定义这样一个Result类:

package com.hr.common; public class Result<T> { private Integer code; // 200成功,500失败 private String message; // 提示信息 private T data; // 数据 public static <T> Result<T> success(T data) { Result<T> r = new Result<>(); r.code = 200; r.message = "操作成功"; r.data = data; return r; } public static <T> Result<T> error(String message) { Result<T> r = new Result<>(); r.code = 500; r.message = message; return r; } // getter / setter 省略 }

所有Controller的返回值统一用Result包装,前端拿到的永远是{"code":200,"message":"xxx","data":{...}}这种结构。好处有两个:一是前后端联调时不用猜格式,二是配合全局异常处理器,所有报错都能以JSON格式返回,而不是跳到一个白底黑字的错误页。

全局异常处理用@RestControllerAdvice + @ExceptionHandler实现。在类里写一个通用方法,捕获Exception异常,返回Result.error("系统异常,请联系管理员")。还可以针对业务异常自定义一个BizException类,在Service层抛出时携带明确的中文提示,比如“该员工工号已存在”。这样做的好处是代码里不用到处try-catch,异常统一收口,逻辑干净很多。

2.3 登录认证与权限控制的两套方案

登录认证这块,毕设项目最常见的选择有两套:一是Spring Security + JWT,二是自定义拦截器 + Session。

我的个人建议是:如果你想在简历上写“熟悉Spring Security”,那可以硬啃一下,但一定要留够时间,因为Spring Security的过滤链机制、UserDetailsService、PasswordEncoder这些概念,一周之内搞明白不容易。如果只是想快速把项目跑通并保证答辩能讲清楚,那就用拦截器+Session方案,代码量很少,逻辑完全透明。

拦截器的实现思路很简单:写一个LoginInterceptor实现HandlerInterceptor接口,在preHandle方法里从Session或者ThreadLocal里拿用户信息,拿不到就返回401提示未登录。然后在WebMvcConfigurer里注册拦截器,并指定排除路径,比如登录接口、静态资源路径不拦截。

比较实用的一个点是做权限分级。用户表里有个role字段,值是ADMIN、HR、EMPLOYEE之一。在拦截器里从Session取出用户后判断角色,如果要访问的路径是/admin/**且不是ADMIN,就返回403。路径级权限控制虽然粗,但对于人力资源管理系统完全够用,不同角色看到的菜单和数据范围都不一样。

2.4 定时任务在HR系统里的实际用处

人力资源管理系统里有两个典型的定时任务场景。一是考勤状态自动更新:每天晚上12点,把当天没打卡的员工的考勤记录标记为“异常”;二是月底薪酬自动生成:每月1号凌晨,根据考勤汇总和薪资标准,为所有在职员工生成上个月的薪酬记录。

SpringBoot里用定时任务很简单,在启动类上加上@EnableScheduling注解,然后在方法上加@Scheduled(cron = "0 0 0 * * ?")就可以定时执行。要注意的是,定时任务默认是单线程串行执行的,如果你有多个任务并且耗时都长,建议给任务方法加上@Async注解并配置一个线程池。

还要注意一点,定时任务在生产环境要加分布式锁,防止多个实例同时执行,但毕设阶段单机运行没有这个问题。不过答辩时如果你能说出“生产环境要考虑分布式锁,但毕设单机部署暂不需要”,这个逼格就上来了。

3. 核心功能实操:从零到一跑通整个项目

3.1 项目初始化与环境准备

拿到源码后第一步是准备环境。JDK建议用1.8或者11,千万不要一上来就装JDK 21配上SpringBoot 3.x,为什么这么说?因为SpringBoot 3.0之后有大量包名从javax换成了jakarta,很多老教程和老代码直接报错,而网上大部分中文教程还是基于SpringBoot 2.x的,你遇到的每一个问题都可能查不到中文资料。毕设有现成源码的话,先看pom.xml里SpringBoot的版本,如果是2.x,就老老实实装JDK 8或11。

IDEA里新建项目的步骤是:New Project → Spring Initializr → 选Java版本 → 勾选Web、MySQL Driver、MyBatis、Lombok依赖。不过直接从网上拉一份源码再改配置会更稳妥。pom.xml里核心依赖就这几个:spring-boot-starter-web、mybatis-spring-boot-starter、mysql-connector-java、lombok,需要导出Excel的话再加一个easyexcel。

application.yml的配置是重头戏,直接决定项目能不能起来:

server: port: 8080 spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/hrms?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai username: root password: 123456 servlet: multipart: max-file-size: 10MB mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.hr.entity configuration: map-underscore-to-camel-case: true logging: level: com.hr.mapper: debug

这几行配置,密码那栏记得改成自己本机的数据库密码,数据库名hrms需要提前在MySQL里建好。map-underscore-to-camel-case这个配置一定要开,否则你数据库字段是create_time,实体类是createTime,查询结果就映射不上,全是null。引用的source里如果没配,建议自己加上。

3.2 员工管理模块的实现套路

员工管理是HR系统的门面功能,核心就是一张表的分页条件查询、新增、修改、删除。

先说分页查询。手写LIMIT其实最直观,Controller接收pageNum和pageSize,Service层算出offset,Mapper里写LIMIT #{offset}, #{pageSize}。不想手写的话就引入PageHelper依赖,在Service方法里调用PageHelper.startPage(pageNum, pageSize)后紧跟查询语句,查完的数据会被自动包装成PageInfo对象。

条件查询在Mapper的XML里动态拼SQL,这里是最能体现MyBatis优势的地方:

<select id="selectEmployeeList" resultType="com.hr.entity.Employee"> SELECT * FROM employee <where> <if test="name != null and name != ''"> AND name LIKE CONCAT('%', #{name}, '%') </if> <if test="deptId != null"> AND dept_id = #{deptId} </if> <if test="hireDateStart != null"> AND hire_date &gt;= #{hireDateStart} </if> <if test="hireDateEnd != null"> AND hire_date &lt;= #{hireDateEnd} </if> </where> ORDER BY create_time DESC </select>

注意XML里大于号和小于号必须用>和<转义,这个坑新手经常踩。用where标签会自动去掉第一个多余的AND,不会出现SQL语法错误。

新增员工时有一个业务细节:需要同时插入员工表和用户表(登录账号)。这时候就要用@Transactional把两个插入操作绑定在一个事务里,如果用户表插入失败,员工表的数据也要一起回滚,否则会出现“员工档案里有人,但这个人登录不了系统”的脏数据。

删除操作建议用逻辑删除,也就是在员工表加一个deleted字段,0正常1已删除,列表查询自动带AND deleted = 0。物理删除会把历史考勤、薪酬记录全部变成孤儿数据,到时候统计报表对不上账,答辩被评委一问就露馅。

3.3 考勤与薪酬这两个模块的时间处理

考勤模块的核心场景是员工打卡。前端传员工ID、打卡日期、上班时间、下班时间,后端在Service层计算工时和状态。判断迟到:如果上班时间晚于公司规定的9点,就标记“迟到”;早退同理。

这里有一点建议用LocalDateTime而不是java.util.Date,因为JDK 8的时间API在做日期比较、加减时真的方便太多。比如判断某员工某天是否有打卡记录,直接:

LocalDate today = LocalDate.now(); Attendance record = attendanceMapper.selectByEmpIdAndDate(empId, today);

薪酬模块的自动生成逻辑,简单说就是三层遍历:查所有在职员工 → 查该员工本月考勤汇总 → 根据考勤结果和基础薪资计算应发和扣款。注意扣款规则和绩效规则是写死在代码里的,答辩时可以讲“如果把规则抽到数据库配置表,就是一套简单的规则引擎”,这句话也是一个亮点。

生成的薪酬表如果还需要导出Excel,我推荐用EasyExcel。先定义一个实体类,字段上加@ExcelProperty注解,一行代码就能把List导出成xlsx文件:

@ExcelProperty("员工姓名") private String name;

EasyExcel对比POI的优点是API更简单、内存消耗低,写起来不啰嗦。

3.4 前端页面怎么接进来

前端部分如果用的是Vue,最省事的部署方式是前端构建完后把静态文件打进SpringBoot的static目录。Vue项目执行npm run build之后会生成一个dist目录,把这个目录下的所有文件复制粘贴到项目的src/main/resources/static里,重新启动SpringBoot,浏览器直接访问http://localhost:8080就能看到页面。

但这里面有两个坑必须提前知道。第一个坑是Vue路由得用hash模式,也就是地址栏是http://localhost:8080/#/employee这种。如果用history模式,刷新页面时Tomcat会去找对应路径的Controller,找不到就404。第二个坑是跨域问题。开发阶段Vue跑在8081端口,SpringBoot跑在8080端口,前端axios请求必须配代理转发:

// vue.config.js devServer: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, pathRewrite: { '^/api': '' } } } }

生产环境打包放进SpringBoot后,前端请求的地址跟后端同源,不再需要代理。这两个环境切换的配置逻辑,答辩时能讲清楚非常加分。

4. 实操中的常见问题与排查技巧实录

4.1 SpringBoot版本太高引发的连锁问题

这个问题我几乎每隔几天就会在粉丝群里看到一次。症状是:项目跑起来了,控制台报ClassNotFoundException,一查发现是javax.servlet的类找不到。根本原因就是SpringBoot 3.x把包名从javax改成了jakarta,而你其他依赖跟着一起升级就会出错。

应对办法很简单:看到pom.xml里parent的版本号是3.x开头的,直接把SpringBoot版本降回2.7.x,比如<version>2.7.18</version>,同时确认JDK版本是8或11。SpringBoot 2.7是2.x系列的最后一个大版本,稳定性和资料丰富度最好,毕设够用。

4.2 IDEA里怎么改端口与配置启动参数

有很多同学下载源码后跑起来一看,端口被占用了,想改端口却找不到地方。SpringBoot改端口有两种常用方式。第一种最直接:在application.yml里改server.port那一行。第二种更灵活:在IDEA的Run Configuration里,Program arguments加上--server.port=8081,命令行参数优先级高于配置文件。

另外补充一个IDEA小技巧:如果改了端口后还报端口占用,用netstat -ano | findstr 8080查到占用端口的PID,再taskkill /F /PID 该PID强制杀掉进程,避免反复改端口躲来躲去。

如果想配热部署,pom.xml里加spring-boot-devtools依赖,IDEA里再按Ctrl+Shift+Alt+/打开Registry,勾选compiler.automake.allow.when.app.running,之后改了Java代码按Ctrl+F10就能自动重启,改前端资源还能自动刷新页面,别提多舒服。

4.3 MyBatis最常见的几个坑

第一个坑是mapper的namespace写错。XML文件里的namespace必须是你Mapper接口的全限定名,少写一个包名就会启动时报BindingException。第二个坑是查询结果返回null,多半是map-underscore-to-camel-case没开,或者数据库字段和实体类属性名对不上。第三个坑是XML里用大于小于号导致编译失败,记住转义符就行。

排查MyBatis问题有一个利器:在application.yml里把Mapper包的日志级别设成debug,控制台会打印所有执行的SQL和传入参数。看到SQL实际长什么样,基本就能定位问题在哪一步。

4.4 从网上拉下来的源码跑不起来怎么办

最后讲一个通用套路。拿到任何一份SpringBoot源码,第一件事打开pom.xml看版本,第二件事改application.yml里的数据库连接,第三件事在MySQL里执行项目附带的sql脚本建库建表,第四件事看一下是否需要改resources里的其他配置。八成的新手问题是出在数据库用户名密码不对或者表没建成功。

如果建库时报“Access denied for user”,那是MySQL的root密码就是不对,改你自己的密码即可。如果项目启动后发现页面能打开但登录时报错“Table 'xxx' doesn't exist”,说明sql脚本没执行完或者执行了另一份错误的脚本,回MySQL确认表结构即可。

调试的时候有一点要记住,Java项目不是改完代码直接F5刷新就有反应的,改完代码要重启,热门的话也要等编译完。SpringBoot项目的报错信息给得很清楚,永远先看控制台最顶部的第一条异常,那才是真正的根因。

4.5 顺带说明一下,整个项目做完的代码量大概在35个类左右,前后端加起来差不多一万行,这个体量对于毕设来说不多不少。源码里有一条完整的数据库初始化脚本,从建库到造测试数据一步到位,跑起来就能看到效果。答辩前建议自己把源码过一遍,重点看Controller层接收哪些参数、Service层的事务怎么加的、Mapper里最复杂的那个动态SQL长什么样,能讲明白这些,评委对项目深度的评价一定不会差。

最后再分享一个实际体会:做毕设最大的误区不是功能做太少,而是贪多嚼不烂。人力资源管理系统这个选题虽然老,但胜在业务清晰、模块之间逻辑关联强,很适合展示基本功。与其堆一堆页面结果每个功能都有Bug,不如把员工、考勤、薪酬、权限这几个核心模块做得扎扎实实。我见过太多同学栽在不重视数据库设计上,表建得乱七八糟,代码写得再漂亮也白搭。你先花一个晚上把表结构和字段想清楚,后面所有的开发都会顺利得多。之后如果有余力,可以给项目加一个文件上传头像、加一个公告通知模块、或者把防守做得更丰富一些,这些扩展方向都能让你的毕设看起来比别人多一层思考。

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

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

立即咨询