ARTICLE DETAIL

资讯详情

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

基于Java Web的企业人事管理系统:JSP+Servlet+SqlServer实现

基于Java Web的企业人事管理系统:JSP+Servlet+SqlServer实现 简介这是一份基于Java的企业人事管理系统毕业设计文档面向计算机相关专业学生、Java入门开发者以及需要参考人事管理信息化的项目人员可帮助读者快速把握基于Web的人事管理系统从需求到实现的完整设计路径。内容围绕系统分析、总体设计、详细设计展开覆盖人员档案、培训、职称评定、奖惩、人员调动等核心业务模块并阐述了Java、JSP、SqlServer2008、MyEclipse等技术的综合运用与设计思路。资源包内共有1个doc文档压缩包约48KB文件为完整的设计说明书包含摘要、系统功能要求、可行性分析、非功能性需求、功能模块划分、实体属性图及详细设计方案等可作为撰写毕业设计或课题报告的参照模板。目前已有79人学习下载对于想快速理解Web化人事管理系统开发流程的读者该文档能提供清晰的结构化参考帮助节省从零梳理需求与设计的时间同时也能为后续编码实现提供明确的模块边界与数据关系支撑。1. 从档案室到 Web 页面这套人事系统到底解决了什么很多中小企业在人事管理上还停留在Excel 为主、纸质档案为辅的阶段员工入职、调岗、评职称、奖惩记录散落在不同表格里。一旦要统计某个部门的培训情况或者核查一个人的调动履历往往要翻半天文件夹。本文这套基于 Java JSP SqlServer2008 的企业人事管理系统就是把档案、培训、职称评定、奖惩、人员调动这几条核心业务线收拢到一个 Web 系统里让人事专员在浏览器里完成录入、查询、审核和报表统计。选型上很有意思没有用 SSH 那套重量级框架而是最朴素的 Servlet JSP JDBC配合 MyEclipse 开发环境。这对刚接触 Java Web 的人来说反而友好——你能看清楚一个请求从 JSP 页面到数据库再返回的完整链路而不是被框架的封装挡住视线。系统在 javax.servlet 层面处理登录会话、权限流转和数据 CRUD数据库端用 SqlServer2008 的关系表和存储过程兜底。网上大量课程设计和毕业设计都采用这个组合不是因为它新而是因为它足够经典、足够容易复现。下文会按模块设计、数据库建表、登录会话、档案调动实战、部署排错这条线展开既有可直接抄的 SQL 和 Java 代码也有部署时反复踩过的坑。2. 把人事业务拆成模块七个功能域与 SqlServer2008 表结构2.1 模块划分的逻辑先分业务域再分操作权限系统中人员档案管理、培训管理、职称评定管理、奖惩管理、人员调动、系统管理是相互独立的业务域但都围绕员工这条主线展开。划分模块时最值得借鉴的一点是不要让一个模块承担太多跨域职责。比如培训记录只关联员工 ID 和培训结果不直接修改员工薪资奖惩记录也只用冗余字段存员工姓名而不是去 join 档案表——这种设计虽然在严格第三范式上不够漂亮但在课设场景下能保证页面渲染时少写大量 join 查询也能避免不同模块互相影响。以人员调动为例它本质上是一条工作流发起调动申请、更新员工所在部门、在调动记录表中留痕。这个流程拆成三个表操作员工表 update 部门字段、调动记录表 insert 一条新记录、按需 update 历史记录的调动前部门。这种设计在实现时的复杂度远低于引入流程引擎但对理解业务状态变化非常有帮助。2.2 表结构设计员工表、部门表与关联表的字段约束在 SqlServer2008 中设计核心表时通常遵循主键自增、业务编号唯一、时间字段默认当前时间三个原则。员工档案表是整张系统的数据中枢建议结构如下CREATE TABLE employee ( emp_id INT IDENTITY(1,1) PRIMARY KEY, emp_no VARCHAR(20) NOT NULL UNIQUE, emp_name NVARCHAR(50) NOT NULL, dept_id INT NOT NULL, position NVARCHAR(50), salary DECIMAL(10,2), hire_date DATETIME DEFAULT GETDATE(), status INT DEFAULT 1, -- 1在职 0离职 remark NVARCHAR(200) );这里emp_id是自增物理主键emp_no是业务编号如EMP001实际页面查询时用emp_no而非主键关联。NVARCHAR是为了兼容中文录入DEFAULT GETDATE()省去了 Java 端传时间参数。部门表单独维护CREATE TABLE department ( dept_id INT IDENTITY(1,1) PRIMARY KEY, dept_name NVARCHAR(50) NOT NULL, dept_manager NVARCHAR(50) );需要注意员工表的dept_id在此处并未设置外键约束。课程设计阶段常为了演示方便牺牲外键但如果要保证数据一致性建议还是补上ALTER TABLE employee ADD CONSTRAINT FK_emp_dept FOREIGN KEY (dept_id) REFERENCES department(dept_id);加了外键后删除部门时会受限制尤其是该部门下还有员工时。开发时可以先不加演示前再补这样既避免开发期被约束卡住又能保证验收时逻辑严谨。2.3 培训、奖惩、调动记录三类从表的共同写法培训、奖惩和调动记录在结构上有明显共性都带员工编号、业务类型、发生日期和备注。写成一张表也可以但课设中更推荐分表便于分别做统计CREATE TABLE train_record ( train_id INT IDENTITY(1,1) PRIMARY KEY, emp_no VARCHAR(20), train_name NVARCHAR(100), train_date DATETIME, trainer NVARCHAR(50), result NVARCHAR(20) -- 合格/不合格 ); CREATE TABLE reward_punish ( rp_id INT IDENTITY(1,1) PRIMARY KEY, emp_no VARCHAR(20), rp_type INT, -- 1奖励 2惩罚 rp_reason NVARCHAR(200), rp_date DATETIME ); CREATE TABLE transfer_record ( transfer_id INT IDENTITY(1,1) PRIMARY KEY, emp_no VARCHAR(20), old_dept_id INT, new_dept_id INT, transfer_date DATETIME, operate_user VARCHAR(20) );其中transfer_record的old_dept_id和new_dept_id都对应department.dept_id。查询某人的调动历史时拿emp_no关联即可。这三个表结构在 Java 端对应三个实体类基本是属性 getter/setter 的常规写法。逻辑说明rp_type用INT以 0/1 区分类型比字符串更节省空间同时在 JSP 页面用三元表达式输出中文避免乱码问题。如果是自己扩展功能一个实用的建议是每张表都加上create_time和modify_time字段虽然课设文档里可能没要求但在演示最近入职员工或最新调动记录时能直接按时间排序省下不少麻烦。3. 登录会话与权限拦截JSP Servlet 的经典实现姿势3.1 登录验证与 Session 管理登录模块是几乎所有 Java Web 项目的门面。很多新手在写登录时直接在 JSP 里用 JDBC 查库这样虽然能跑通但会带来两个问题代码耦合在页面里难以复用无法在进入业务页面前做统一拦截。这里推荐 Servlet 处理逻辑、JSP 只负责展示的写法。系统登录模块在一个LoginServlet中完成protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); UserDao dao new UserDao(); User user dao.findUser(username, password); if (user ! null) { HttpSession session request.getSession(); session.setAttribute(loginUser, user); response.sendRedirect(main.jsp); } else { request.setAttribute(errorMsg, 用户名或密码错误); request.getRequestDispatcher(login.jsp).forward(request, response); } }参数说明request.setCharacterEncoding(UTF-8)必须放在读取参数之前否则会以 Tomcat 默认的 ISO-8859-1 解析中文请求体出现乱码。findUser内部使用预编译PreparedStatement拼接 SQL传参时不能直接拼字符串。登录成功后把user对象塞进Session后续所有需要身份信息的页面都可以取session.getAttribute(loginUser)使用。sendRedirect和forward在这里的行为差异很关键redirect是两次请求浏览器地址栏会变forward是服务端跳转地址栏不变。登录失败场景用forward的好处是errorMsg能直接渲染在登录页上同时保留用户输入的用户名。3.2 用 Filter 拦截未登录请求没有权限拦截的登录模块形同虚设。如果用户直接访问main.jsp或empList.jsp系统必须有能力把请求挡回去。此时用Filter是最干净的做法public class LoginFilter implements Filter { public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request (HttpServletRequest) req; HttpServletResponse response (HttpServletResponse) res; HttpSession session request.getSession(false); String uri request.getRequestURI(); if (uri.indexOf(login.jsp) -1 || uri.indexOf(LoginServlet) -1 || uri.indexOf(.css) -1 || uri.indexOf(.js) -1) { chain.doFilter(req, res); return; } if (session null || session.getAttribute(loginUser) null) { response.sendRedirect(login.jsp); return; } chain.doFilter(req, res); } }request.getSession(false)是关键传false时不会主动创建新 Session避免给每个静态资源请求都生成无意义的会话。LoginServlet本身必须放行否则用户连登录请求都要被拦截回登录页形成死循环。.css和.js放行是为了避免登录页样式加载失败影响演示观感。在web.xml中配置filter filter-nameLoginFilter/filter-name filter-classcom.hr.filter.LoginFilter/filter-class /filter filter-mapping filter-nameLoginFilter/filter-name url-pattern/*/url-pattern /filter-mappingurl-pattern使用/*后Filter 会拦截所有请求包括 JSP、Servlet 和静态资源。这种全局拦截策略在真实项目里也常见只是会配合Spring Security或Shiro做更细粒度的权限控制但核心思路——没有登录就重定向到登录页——是一致的。3.3 修改密码模块的坑点修改密码是系统管理模块里最容易被忽略、测试时又最容易扣分的部分。典型错误是更新语句写成UPDATE users SET password 123456 WHERE username admin这样写的问题有两个密码明文存储没有任何散列处理更新时没有重新校验旧密码。课设里的合理做法是查询旧密码是否匹配再执行更新。在 SqlServer2008 端更稳妥的做法是用存储过程封装CREATE PROC sp_update_pwd username NVARCHAR(50), old_pwd NVARCHAR(50), new_pwd NVARCHAR(50) AS BEGIN IF EXISTS (SELECT 1 FROM users WHERE username username AND password old_pwd) BEGIN UPDATE users SET password new_pwd WHERE username username SELECT 1 AS result END ELSE SELECT 0 AS result END存储过程的优势在于修改密码的规则集中在数据库端Java 代码只需要调用CallableStatement并传入三个参数逻辑更清晰。实际开发中密码至少应该用MD5或SHA-256散列再入库演示系统里明文虽然能跑但写论文时建议在安全性分析一节说明此处可扩展为加密存储给自己留一个研究点。注意SqlServer2008 的NVARCHAR传参在 JDBC 中要使用setNString而不是setString否则中文对比可能失败。这是老版本的经典问题后面第 5 章还会提到。4. 档案、培训与调动核心业务模块的 JSP Servlet 实现4.1 员工档案列表分页查询与模糊搜索人员档案管理模块是整个系统的重头戏页面一般是搜索条件区 数据表格 分页按钮三段式。查询功能不要一次性把所有员工记录加载出来人员多时页面会非常卡。这里给一个分页查询工具类的常见写法public ListEmployee findEmpByPage(int pageNo, int pageSize, String keyword) { ListEmployee list new ArrayListEmployee(); String sql SELECT * FROM employee WHERE emp_name LIKE ? ORDER BY emp_id OFFSET ? ROWS FETCH NEXT ? ROWS ONLY; try (Connection conn DBUtil.getConnection(); PreparedStatement ps conn.prepareStatement(sql)) { ps.setString(1, % keyword %); ps.setInt(2, (pageNo - 1) * pageSize); ps.setInt(3, pageSize); ResultSet rs ps.executeQuery(); while (rs.next()) { Employee emp new Employee(); emp.setEmpId(rs.getInt(emp_id)); emp.setEmpNo(rs.getString(emp_no)); emp.setEmpName(rs.getString(emp_name)); emp.setDeptId(rs.getInt(dept_id)); emp.setPosition(rs.getString(position)); emp.setSalary(rs.getBigDecimal(salary)); emp.setHireDate(rs.getDate(hire_date)); list.add(emp); } } catch (SQLException e) { e.printStackTrace(); } return list; }需要特别说明的是OFFSET ... FETCH NEXT ... ROWS ONLY是 SqlServer 2012 之后的语法SqlServer2008 并不支持。如果你的环境确实是 2008需要换用ROW_NUMBER() OVER (ORDER BY emp_id) AS rowNum配合子查询来实现分页SELECT * FROM ( SELECT ROW_NUMBER() OVER (ORDER BY emp_id) AS rowNum, * FROM employee WHERE emp_name LIKE ? ) AS t WHERE t.rowNum BETWEEN ? AND ?这个坑在论文答辩时经常被老师追问版本号不对应代码却照搬了新版语法显然是抄的。建议把这条 SQL 换成ROW_NUMBER写法存进自己的代码里既能跑通 2008又能在答辩时讲清楚分页原理。预编译参数位置LIKE的参数传%keyword%在setString中完成拼接而不是把%写死在 SQL 里能有效避免 SQL 注入。JSP 页面对应部分用c:forEach遍历数据即可。列表展示时部门名称不要直接显示dept_id要在后台查一次部门表做映射或者在 SQL 里 join。推荐 SQL 端 join少一层循环SELECT e.emp_id, e.emp_no, e.emp_name, d.dept_name, e.position FROM employee e LEFT JOIN department d ON e.dept_id d.dept_id这里的LEFT JOIN能保证即使某员工dept_id为空也能正常显示档案不会因为关联不到部门而丢数据行。4.2 新增 / 编辑员工档案前端验证与后端验证缺一不可新增员工时前端负责基本格式检查工号不能为空、工资必须为正数后端仍然需要校验一遍。很多课设只做前端onsubmit校验绕过校验直接 POST 会导致脏数据。后端校验通常放在 Servlet 中String empNo request.getParameter(empNo); String salaryStr request.getParameter(salary); if (empNo null || empNo.trim().isEmpty()) { request.setAttribute(error, 工号不能为空); request.getRequestDispatcher(empAdd.jsp).forward(request, response); return; } BigDecimal salary new BigDecimal(salaryStr); if (salary.compareTo(BigDecimal.ZERO) 0) { request.setAttribute(error, 工资不能为负数); request.getRequestDispatcher(empAdd.jsp).forward(request, response); return; }BigDecimal这里值得多说一句工资字段如果用double或float接收浮点运算会带来精度问题比如 8000.1 显示成 8000.099999。Java 端接收时直接用BigDecimal(String)构造避免先parseFloat再转BigDecimal的二段式精度丢失。setSalary时也要求rs.getBigDecimal(salary)取出来这样能保证数据库DECIMAL(10,2)与 Java 对象之间精度完全对齐。系统提交后在EmployeeServlet里通过insert方法写入数据库成功后重定向到列表页。这里用sendRedirect而不是forward是为了防止用户按 F5 刷新导致表单重复提交。4.3 培训管理从添加到考核结果回填培训管理模块在实现上比档案管理多一个状态概念培训前是计划中培训后要回填结果。常见做法是培训表加一个status字段在添加培训时初始为0未完成考核结束后更新为1已完成并写入成绩UPDATE train_record SET result ?, status 1 WHERE train_id ?Java 端的更新方法与之对应。选择在 JSP 页面做状态字段的自动渲染时用下拉框绑定值比如option value0未培训/option而不是让用户每次手动输入。这样既规范了录入也减少了后续统计时的脏数据。在 培训课程管理 方面如果论文中涉及的培训管理需要支持批量选择员工需要注意不要用String[]一次性接收员工编号后循环执行插入语句因为其中任何一条失败会让整个批次处于不明状态。更好的做法是在 Java 端用事务包裹例如在TrainServlet的addTrain方法中Connection conn DBUtil.getConnection(); conn.setAutoCommit(false); try { for (String empNo : empNoArray) { TrainDao.insertTrain(conn, trainId, empNo); } conn.commit(); } catch (SQLException e) { conn.rollback(); e.printStackTrace(); } finally { DBUtil.close(conn); }setAutoCommit(false)是重点默认 JDBC 每执行一条 SQL 就自动提交多条插入中途失败时已插入的数据会残留。关闭自动提交后所有插入要么全部成功要么全部回滚。这是培训模块最容易出 bug 的地方答辩时老师也常拿这个问题来考。4.4 人员调动状态变更与流程记录人员调动模块比档案管理复杂在既要改当前状态又要保留历史轨迹。核心操作方法是先查出员工当前部门再更新员工表最后插入一条调动记录。下面给出一个简化的实现public boolean transfer(String empNo, int newDeptId, String operator) throws SQLException { String sqlGetOld SELECT dept_id FROM employee WHERE emp_no ?; String sqlUpdate UPDATE employee SET dept_id ? WHERE emp_no ?; String sqlInsert INSERT INTO transfer_record(emp_no, old_dept_id, new_dept_id, transfer_date, operate_user) VALUES(?, ?, ?, GETDATE(), ?); Connection conn DBUtil.getConnection(); conn.setAutoCommit(false); try { PreparedStatement psGet conn.prepareStatement(sqlGetOld); psGet.setString(1, empNo); ResultSet rs psGet.executeQuery(); int oldDeptId 0; if (rs.next()) { oldDeptId rs.getInt(dept_id); } PreparedStatement psUpd conn.prepareStatement(sqlUpdate); psUpd.setInt(1, newDeptId); psUpd.setString(2, empNo); psUpd.executeUpdate(); PreparedStatement psIns conn.prepareStatement(sqlInsert); psIns.setString(1, empNo); psIns.setInt(2, oldDeptId); psIns.setInt(3, newDeptId); psIns.setString(4, operator); psIns.executeUpdate(); conn.commit(); return true; } catch (SQLException e) { conn.rollback(); throw e; } finally { DBUtil.close(conn); } }参数说明oldDeptId必须来自调动前的查询而不是页面传入否则用户篡改请求参数就会导致记录失真。transfer_date使用数据库的GETDATE()而非 Java 端new Date()这样时间以数据库服务器为准能避免应用服务器和数据库服务器时区不一致造成的偏差。批次调动的场景下比如部门整体调整这个方法可以稍作改造把empNo参数换成ListString循环更新即可但事务仍然要包在外面。整体逻辑不变边界点还是先查旧值再更新再留痕。5. 答辩演示前必须处理的四个部署级问题最后一个环节不是讲功能而是讲部署和验收。课程设计和实际项目最不一样的地方在于代码正确只是第一步让系统在老师的电脑上能一键跑起来才是关键。围绕 MyEclipse Tomcat SqlServer2008 的组合演示前最容易出问题的是下面四件事。SqlServer2008 的 JDBC 驱动不能漏。官方驱动有两个版本sqljdbc.jar和sqljdbc4.jar在 JDK 1.6 及以上必须用sqljdbc4.jar否则报UnsupportedClassVersionError。驱动要放到 Tomcat 的lib目录或项目的WebRoot/WEB-INF/lib下两种方式都行但后者跟着项目走换机器部署不用再手动拷。连接字符串写成String url jdbc:sqlserver://localhost:1433;DatabaseNamehr_db;1433是 SqlServer 默认端口改过实例名的要查SQL Server Configuration Manager里的实际端口。类名是com.microsoft.sqlserver.jdbc.SQLServerDriver不要和旧版的com.microsoft.jdbc.sqlserver.SQLServerDriver混淆。中文乱码的排查顺序固定是JSP 页面pageEncodingUTF-8、Servletrequest.setCharacterEncoding(UTF-8)、数据库表字段用NVARCHAR、连接字符串加;characterEncodingUTF-8。四个环节任何一个断掉中文在某些路径上就会变成问号。SqlServer2008 的VARCHAR字段对中文支持不好必须用NVARCHAR这和 MySQL 的utf8是两套逻辑。OFFSET FETCH语法在 2008 上跑不通前面已经提到过。这里再补充一个连带的坑即使在 2008 上安装了 SP2OFFSET依然不可用别浪费时间折腾版本。直接改用ROW_NUMBER()方案写一次以后 2008/2012/2016 通吃。另外GETDATE()返回的时间包含毫秒页面上显示时要用SimpleDateFormat格式化或者 SQL 端CONVERT(VARCHAR(19), create_time, 120)处理后再输出。测试时先跑通一条完整的业务链登录 → 新增员工 → 给该员工加一条培训记录 → 变更部门 → 查询该员工的调动历史。这条链路覆盖了系统全部核心表和主要 Servlet 映射跑通了再测奖惩和职称评定。绩效模块如果正文里没有对应的独立页面大概率是和奖惩放在一起做的这时要确认奖惩页面里是否有绩效相关的字段没有的话在数据库设计说明里不要写漏了。整个链路跑通后再考虑系统在多人同时登录时的表现——这块不用太担心课设阶段 Tomcat 单机部署已经足够但论文里提一句后续可通过连接池优化并发性能会显得更有完整性。本文还有配套的精品资源点击获取
返回列表