
简介这套人事管理系统采用JSP动态页面与Servlet处理逻辑后台搭配MySQL数据库是面向课程设计和毕业设计场景的JavaWeb项目覆盖员工信息维护、账号登录与权限校验等核心功能体现了ERP中人力资源管理环节的信息化设计思路。压缩包共一百零八份文件大小约2.04MB含Java源码及class编译文件、JSP页面、HTML静态页、CSS样式、JavaScript脚本、SQL数据库脚本与Eclipse工程配置数据库文件置于lib包目录下便于导入后直接运行。包内另有JPG、PNG界面截图可对照系统页面快速理解各功能模块的布局与交互。目前已有1447人浏览学习对准备毕业设计或JavaWeb课程设计的学生来说参考价值较好。借助完整源码与数据库脚本读者能掌握JSPServlet分层开发、用户登录验证、员工增删改查等典型实现方式并可在此基础上扩展部门管理、考勤统计等功能是一份结构清晰、可直接运行的实用项目。1. 人事管理系统JSPMySQL值不值得做先回答四个实际问题做了这么多年 JavaWeb 项目被问得最多的一句话是“人事管理系统用 JSPMySQL 当毕设会不会显得太老”。我的回答是技术老不等于项目水关键看系统边界、代码规范和答辩时能不能把话说圆。这个资源是一套完整的 JSPServletMySQL 人事管理系统覆盖员工管理、部门管理、考勤录入、薪资查询、管理员登录等核心模块数据库脚本、页面、Servlet 控制层一次给齐适合做毕业设计、JavaWeb 课程设计也适合想快速上手 servlet 编程的初学者。它解决的不是“技术有多新”的问题而是“如何用最稳妥的组合把功能做完、把答辩讲明白”的问题。先看系统长什么样再照着跑通最后把坑一个个排掉这套资源就能真正落地。2. JSPServlet 三层结构功能拆解和请求流转的一手经验2.1 为什么这个组合在毕设里反而是加分项很多同学纠结要不要换 Spring Boot。我的看法是毕业设计答辩评委里总有几位老教师看到 JSPServlet 反而能聊得更细。这类组合的天然优势是分层直观JSP 做页面展示Servlet 接收请求做逻辑分发JDBC 负责和 MySQL 交互每一层都能单独画出来讲。相比 Spring Boot 自动装配的黑匣子Servlet 的每个方法、每次转发、每个 request 和 response 对象都是明摆着的参数怎么传、值怎么取都能一步一句讲清楚。对要用这份资源的人来说理解成本低、改动成本也低。模块设计上常见做法是做成五个功能块登录认证、员工信息 CRUD、部门管理、考勤录入、薪资查询。登录认证用 session 保存当前用户配上过滤器做未登录拦截员工模块是最重的包含分页列表、按姓名或部门搜索、新增、编辑、删除考勤和薪资则主要靠 MySQL 的联表查询来出报表。每个功能块对应一个 Servlet 类Servlet 里只做参数接收、调用 DAO、把结果放进 request、转发到 JSP业务逻辑简单答辩时一条一条说下来很顺。功能模块核心表Servlet 类页面文件登录登录adminLoginServletlogin.jsp / index.jsp员工管理employee / deptEmployeeServletemp_list.jsp / emp_add.jsp / emp_edit.jsp部门管理deptDeptServletdept_list.jsp / dept_add.jsp考勤录入attendanceAttendanceServletatt_list.jsp / att_add.jsp薪资查询salarySalaryServletsalary_list.jsp2.2 一次请求的完整流转从 JSP 表单向 Servlet页面上的form提交请求Servlet 在doGet或doPost里接收参数调用 DAO 查数据库再forward回 JSP。下面这段是 EmployeeServlet 中处理新增员工的典型写法也是这个项目里最核心的请求分发模式。WebServlet(/employee) public class EmployeeServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String action request.getParameter(action); if (add.equals(action)) { String empNo request.getParameter(empNo); String name request.getParameter(name); int deptId Integer.parseInt(request.getParameter(deptId)); String position request.getParameter(position); String hireDate request.getParameter(hireDate); Employee emp new Employee(); emp.setEmpNo(empNo); emp.setName(name); emp.setDeptId(deptId); emp.setPosition(position); emp.setHireDate(java.sql.Date.valueOf(hireDate)); EmployeeDao dao new EmployeeDao(); boolean ok dao.insert(emp); if (ok) { response.sendRedirect(employee?actionlist); } else { request.setAttribute(error, 新增员工失败); request.getRequestDispatcher(/employee_add.jsp).forward(request, response); } } } }这段代码的逻辑是先统一设置请求编码然后读取隐藏字段action判断操作类型新增员工时把表单参数转成Employee对象组装好后交给 DAO 层执行插入。执行成功就用sendRedirect重定向到列表页避免刷新页面重复提交失败则把错误信息放进 request转发回新增页回显。需要注意java.sql.Date.valueOf(hireDate)只接受yyyy-MM-dd格式字符串前端日期控件必须控制好输出格式否则会抛IllegalArgumentException。action参数的命名贯穿整个模块列表、新增、编辑、删除都通过这个参数区分是我在设计这套资源时统一约定的接口风格。2.3 JSP 页面里的数据展示与表单回显列表页核心写法是 JSP 搭配 JSTL 标签在emp_list.jsp中用c:forEach遍历 request 里的员工集合。常见做法是在 Servlet 中查出ListEmployee放进 request然后转发。搜索功能则通过拼接 SQL 条件实现查询参数和分页参数同时作为 request 属性传递。分页参数包含pageIndex和pageSizeDAO 层用LIMIT #{offset}, #{pageSize}做物理分页这是我建议保留的默认参数配置数据量到几千条都不会卡。编辑回显时在 JSP 的value属性里直接输出employee.name这类 EL 表达式再把deptId和下拉框的option做一次比较判断实现选中态。3. MySQL 表结构设计五张表、外键和初始化脚本的一次定稿3.1 建表脚本字段类型和约束的取舍数据库设计是这套资源的重头戏我把它定成五张核心表admin 管理员表、dept 部门表、employee 员工表、attendance 考勤表、salary 薪资表。employee 通过dept_id关联部门attendance 和 salary 通过emp_id关联员工。外面很多毕设项目外键约束写得不严谨删除部门直接报错或者删了员工还留着考勤记录。我这份脚本全部加了外键和索引让DELETE行为在数据层面就有约束。CREATE DATABASE IF NOT EXISTS hrms DEFAULT CHARSET utf8mb4 COLLATE utf8mb4_general_ci; USE hrms; CREATE TABLE admin ( id INT PRIMARY KEY AUTO_INCREMENT COMMENT 管理员ID, username VARCHAR(50) NOT NULL UNIQUE COMMENT 登录名, password VARCHAR(64) NOT NULL COMMENT 登录密码, real_name VARCHAR(50) COMMENT 姓名, create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间 ) ENGINEInnoDB COMMENT 管理员表; CREATE TABLE dept ( dept_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 部门ID, dept_name VARCHAR(100) NOT NULL UNIQUE COMMENT 部门名称, dept_desc VARCHAR(255) COMMENT 部门描述, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB COMMENT 部门表; CREATE TABLE employee ( emp_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 员工ID, emp_no VARCHAR(20) NOT NULL UNIQUE COMMENT 工号, name VARCHAR(50) NOT NULL COMMENT 姓名, gender TINYINT DEFAULT 1 COMMENT 性别 1男 0女, birth_date DATE COMMENT 出生日期, phone VARCHAR(20) COMMENT 联系电话, email VARCHAR(100) COMMENT 邮箱, dept_id INT NOT NULL COMMENT 所属部门ID, position VARCHAR(50) COMMENT 职位, hire_date DATE COMMENT 入职日期, status TINYINT DEFAULT 1 COMMENT 状态 1在职 0离职, photo VARCHAR(255) COMMENT 照片路径, INDEX idx_dept (dept_id), INDEX idx_hire_date (hire_date), CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES dept(dept_id) ) ENGINEInnoDB COMMENT 员工表; CREATE TABLE attendance ( att_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 考勤ID, emp_id INT NOT NULL COMMENT 员工ID, work_date DATE NOT NULL COMMENT 考勤日期, clock_in TIME COMMENT 上班时间, clock_out TIME COMMENT 下班时间, status TINYINT DEFAULT 0 COMMENT 0正常 1迟到 2早退 3缺勤, INDEX idx_emp_date (emp_id, work_date), CONSTRAINT fk_att_emp FOREIGN KEY (emp_id) REFERENCES employee(emp_id) ) ENGINEInnoDB COMMENT 考勤表; CREATE TABLE salary ( salary_id INT PRIMARY KEY AUTO_INCREMENT COMMENT 薪资ID, emp_id INT NOT NULL COMMENT 员工ID, month VARCHAR(6) NOT NULL COMMENT 薪资月份 如202506, basic_salary DECIMAL(10,2) DEFAULT 0.00 COMMENT 基本工资, performance DECIMAL(10,2) DEFAULT 0.00 COMMENT 绩效工资, overtime DECIMAL(10,2) DEFAULT 0.00 COMMENT 加班费, deductions DECIMAL(10,2) DEFAULT 0.00 COMMENT 扣款, net_salary DECIMAL(10,2) DEFAULT 0.00 COMMENT 实发工资, INDEX idx_month (month), CONSTRAINT fk_sal_emp FOREIGN KEY (emp_id) REFERENCES employee(emp_id) ) ENGINEInnoDB COMMENT 薪资表;建库时字符集直接定成utf8mb4不是utf8因为旧版 utf8 在 MySQL 里最多存 3 字节emoji 和部分生僻字会写不进去插入时容易报Incorrect string value。这是我在实际项目里踩过多次的坑毕设中姓名生僻字并不少见。DECIMAL(10,2)用来存工资定长为 10 位、小数 2 位金额字段不能用FLOAT否则答辩演示时可能出现 5999.999999 这种尴尬显示。attendance表的联合索引idx_emp_date(emp_id, work_date)为月度考勤报表服务查某员工某月的记录走联合索引速度明显好过单列索引。3.2 初始化数据让系统跑起来就能演示空表跑系统登录和列表页全空白演示效果大打折扣。脚本里附带的初始化数据我建议至少插一条管理员账号和三个部门、五个员工。管理员密码不建议用明文常见做法是 MD5 加密后存入。INSERT INTO admin (username, password, real_name) VALUES (admin, MD5(123456), 系统管理员); INSERT INTO dept (dept_name, dept_desc) VALUES (技术部, 负责产品研发); INSERT INTO dept (dept_name, dept_desc) VALUES (人事部, 负责员工招聘与考勤); INSERT INTO dept (dept_name, dept_desc) VALUES (财务部, 负责薪资核算); INSERT INTO employee (emp_no, name, gender, dept_id, position, hire_date, status) VALUES (E001, 张三, 1, 1, Java开发工程师, 2024-03-01, 1);登录逻辑里比对密码时用MD5(输入的密码)和库里存储的值比对不能把库里的密文捞出来解密。密码加密字段长度设为 64因为 MD5 是 32 位十六进制VARCHAR(32) 其实也够留到 64 是给以后升级 SHA-256 留余地。首次部署完用admin / 123456登录立刻能请求到数据演示流程不需要再手工造数据。3.3 联表查询的写法考勤和薪资页面怎么取数考勤列表页需要显示员工姓名和部门这就要把 attendance 和 employee、dept 连起来查。常见做法是用三条 SQL 或一条 JOIN我建议直接 JOIN后台代码少JSP 里取值也直观。SELECT a.work_date, a.clock_in, a.clock_out, a.status, e.name, e.emp_no, d.dept_name FROM attendance a JOIN employee e ON a.emp_id e.emp_id JOIN dept d ON e.dept_id d.dept_id WHERE a.work_date BETWEEN ? AND ? ORDER BY a.work_date DESC, e.emp_no ASC;这条查询是考勤模块的核心WHERE a.work_date BETWEEN ? AND ?的两个问号是 PreparedStatement 的参数占位符对应页面上的开始日期和结束日期。JOIN 顺序按attendance → employee → dept走因为最终要展示部门名称必须再关联一次 dept。薪资报表的写法类似多加一个WHERE s.month ?就能按月过滤。所有 SQL 都走PreparedStatement不拼接字符串这是防止 SQL 注入的基本底线我把它写进了每一条 DAO 方法里。4. 部署与数据库连接Tomcat 9 配 MySQL 5.7/8.0 的完整落地4.1 环境选型版本搭配直接影响能不能跑起来这套资源我按 Tomcat 9 JDK 8/11 MySQL 5.7/8.0 来部署。Tomcat 9 对应 Servlet 4.0包名还是javax.servlet网上绝大多数 JSP 教程都基于这个版本遇到问题好搜如果换成 Tomcat 10包名变成jakarta.servlet代码里所有import javax.servlet.*全部报错这个坑会在第五章专门讲。JDBC 驱动版本要和 MySQL 匹配MySQL 5.7 用mysql-connector-java-5.1.49.jarMySQL 8.0 用mysql-connector-java-8.0.33.jar。很多人启动项目时报ClassNotFoundException十有八九是驱动 jar 放错位置或版本不匹配。驱动版本适用 MySQL 版本连接 URL 特征5.1.495.5 / 5.6 / 5.7drivercom.mysql.jdbc.Driver8.0.338.0 及以上drivercom.mysql.cj.jdbc.Driver4.2 数据库连接配置文件一处改动全项目生效连接参数我建议集中放在src/db.properties里Java 代码通过ResourceBundle读取这样换电脑、换数据库、改密码时只需动一个文件。下面是我在这套资源里常用的 JDBC 工具类写法。public class DBUtil { private static String driver; private static String url; private static String username; private static String password; static { try { ResourceBundle bundle ResourceBundle.getBundle(db); driver bundle.getString(driver); url bundle.getString(url); username bundle.getString(username); password bundle.getString(password); Class.forName(driver); } catch (Exception e) { e.printStackTrace(); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(url, username, password); } }对应的db.properties内容如下适用于 MySQL 8.0drivercom.mysql.cj.jdbc.Driver urljdbc:mysql://localhost:3306/hrms?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltrue usernameroot password你的数据库密码serverTimezoneAsia/Shanghai必须加否则 MySQL 8.0 连接时默认使用 UTC 时区java.util.Date存入数据库会差 8 小时。useSSLfalse是为了避免本地连接时 SSL 握手报错allowPublicKeyRetrievaltrue解决 MySQL 8.0 客户端连接时Public Key Retrieval is not allowed的报错。这三组参数是我在配置数据库连接时必写的三项缺一个项目就可能在启动后第一个查询请求时报错。4.3 把项目跑起来Eclipse/IDEA 两种部署路径最常见的部署方式有两种。Eclipse 里把项目导入后右键Run As → Run on Server选择本机配置的 Tomcat 9Eclipse 会自动把项目部署到webapps下。IDEA 则在Run → Edit Configurations里新增 Tomcat ServerDeployment选项卡点加号选Artifact → war explodedApplication context填/hrms。两种工具本质都是把项目路径挂到 Tomcat 上启动日志出现INFO: Server startup in [xxx] milliseconds就是成功。访问地址统一是http://localhost:8080/hrms/。如果端口被占用改 Tomcat 的conf/server.xml里 Connector 的port属性默认8080改成8081即可同时注意 Idea 的HTTP port也要同步改。5. 避坑与排查JSP 毕设最容易翻车的 5 个问题5.1 中文乱码四层都要设 UTF-8现象页面显示问号数据库里存的是乱码或者从数据库查出来在页面上是一堆???。原因JSP 页面、请求参数、服务器响应、数据库连接四个环节只要有一层不是 UTF-8全链路就会乱码。最常见的是只设置了 JSP 的pageEncoding忽略了请求或数据库连接。解决四个位置同步设置。JSP 文件头写成% page contentTypetext/html; charsetUTF-8 pageEncodingUTF-8%HTML 里再加meta charsetUTF-8Servlet 的doGet和doPost里一开始就调用request.setCharacterEncoding(UTF-8)数据库连接 URL 加characterEncodingutf8建库建表统一utf8mb4。四层都检查一遍乱码基本消失。5.2 Tomcat 10 启动后项目直接报 ClassNotFound现象把项目从 Tomcat 9 换到 Tomcat 10启动时一大片ClassNotFoundException: javax.servlet.Filter。原因Tomcat 10 把 JavaEE 迁移到 Jakarta EEServlet 的包名从javax.servlet改成了jakarta.servlet。老项目代码全部基于javax.servlet驱动类找不到是必然的。解决项目如果是 JSPServlet 的老代码别升级 Tomcat直接固定用 Tomcat 9.0.x。如果一定要用 Tomcat 10就得把所有import javax.servlet.*全局替换成import jakarta.servlet.*JSTL 依赖版本也要同步换。对于毕设项目我的建议是别折腾装 Tomcat 9 最稳。5.3 MySQL 8.0 连接时报 Public Key Retrieval 错误现象JDBC 第一次连接 MySQL 8.0 时报Public Key Retrieval is not allowed但驱动已经正确加载。原因MySQL 8.0 默认使用caching_sha2_password认证插件客户端为了安全传输密码需要获取服务器的公钥连接参数里没允许这个行为时就报错。解决连接 URL 加上allowPublicKeyRetrievaltrue。还可以把数据库用户的认证插件改成mysql_native_password但改认证方式属于全局变更不如加参数省事。5.4 表单提交返回 404action 路径和 Servlet 映射不一致现象点击保存按钮跳转 404控制台没有 Servlet 执行日志。原因表单里的actionemployee和form所在 JSP 的相对路径拼起来不对。比如 JSP 在/hrms/employee/emp_add.jsp表单提交employee?actionadd浏览器最终访问的是/hrms/employee/employee?actionadd自然 404。解决表单 action 写绝对路径用${pageContext.request.contextPath}开头form action${pageContext.request.contextPath}/employee?actionadd methodpost。同理Servlet 里的sendRedirect和forward路径也统一加上getServletContext().getContextPath()或直接用/employee这种绝对路径。这个习惯能避免项目部署路径变化带来的路径错乱。5.5 delete 员工时外键约束挡住操作现象删除部门或员工时SQL 报Cannot delete or update a parent row: a foreign key constraint fails。原因employee 表有考勤和薪资的外键引用dept 表有 employee 的外键引用直接删会违反外键约束。解决在 DAO 里先删子表再删父表。删除部门前先把该部门下员工的dept_id改为 NULL 或移到其他部门或者规定有员工的部门不允许删除在 Service 层提前查询并给出友好提示。两者选一即可我在这套资源里采用的是“先查员工数有员工则拒绝删除并提示”比硬删更安全。6. 进阶技巧给员工模块加上照片上传与自测验证流程员工模块里加一个照片上传是我强烈建议保留的功能点。一次multipart/form-data表单提交涉及 Part 接口、文件保存路径、前端回显三个环节答辩时是一个很能讲的“技术亮点”。用如下代码在 EmployeeServlet 中处理文件Part photoPart request.getPart(photo); if (photoPart ! null photoPart.getSize() 0) { String fileName UUID.randomUUID().toString().replace(-, ) .jpg; String uploadPath getServletContext().getRealPath(/) uploads; File dir new File(uploadPath); if (!dir.exists()) dir.mkdirs(); photoPart.write(uploadPath File.separator fileName); emp.setPhoto(uploads/ fileName); }这段逻辑是通过request.getPart(photo)拿到上传的文件对象文件名用 UUID 重命名避免中文文件名和重复名造成的乱码与覆盖getServletContext().getRealPath(/)拿到项目在 Tomcat 里的真实磁盘路径在其下建uploads目录最后把相对路径uploads/文件名存进数据库。JSP 表单需要加上enctypemultipart/form-data重要提示一旦表单用了这个编码就不能再用request.getParameter读取其他字段必须依赖request.getParameter解析表单内其他普通参数否则都会返回 null。这一点是我在实际项目中踩过的坑写代码时优先取出所有普通字段再处理文件 Part顺序不能颠倒。部署完成后建议按这个顺序完整走一遍自测用admin/123456登录确认 session 拦截正常新增一个部门和员工提交时故意不填必填项看页面是否回显错误给员工上传照片查看uploads目录下是否生成文件修改员工信息确认编辑回显删除该员工关联的考勤记录再删除员工最后退出登录直接访问列表页确认被过滤器重定向到登录页。这套验证流程走完项目功能和健壮性基本都能在答辩演示中兜住。我从那以后每次接手 JSP 项目都强制先走一遍“登录→增删改查→上传→删除”这个链路再检查驱动版本和 UTF-8 时区配置这四项定下来项目就成功了大半。希望帮到你。本文还有配套的精品资源点击获取