简介:这是一份基于 JSP+Servlet 的企业人事管理系统完整源码包,面向 Java 初学者、毕业设计学生及小型企业项目参考。压缩包共 229 个文件,约 5.8MB,主要包含 88 个 JSP 页面、18 个 Java 源码与对应 class 文件,以及 16 个 jar 依赖库和 28 个 tld 标签描述文件,可支撑 MVC 分层架构与前后端交互;另有 32 张 JPG、11 张 GIF 图片素材,4 个 XML 配置、properties 配置和数据库文件(mdf/ldf、db 等),便于快速搭建运行环境。已有 176 人学习下载。读者可从中获取完整项目源码、数据库脚本、配置说明与页面素材,既能学习 JSP/Servlet 开发流程、用户权限与人事业务模块设计,也可直接用于毕业设计或小规模企业管理场景的二次开发参考。
1. JSP企业人事管理系统:老技术栈里的完整练手范本
一个打包下载的 JSP 企业人事管理系统源码,摆在你面前时最该问的不是“JSP 是不是过时了”,而是“这套代码能不能跑起来、里面的分层方式值不值得抄”。JSP 技术本身确实不是新东西,但它至今仍是很多高校课程设计、毕业设计和中小型企业内部系统的底子,原因很简单:Servlet 容器天然处理 HTTP 生命周期,JSP 页面直接嵌 Java 代码,部署到 Tomcat 就能开张。这套源码我拆过一遍,里面覆盖了登录认证、员工信息 CRUD、部门管理、薪资查询这类人事系统的标准模块,用的是 JSP + Servlet + JavaBean + MySQL 的经典组合,配合 SQL 脚本和部署说明打包在一起。适合两类人:一是正在做 Java Web 课程设计、需要一份能跑通做参考的学生,二是想快速搭一个内部人事后台的从业者。这周我把它从环境配置到源码走读完整过了一遍,下面直接说怎么部署、怎么改、以及最容易翻车的那几个位置。
2. 架构认知:JSP + Servlet + JavaBean 的经典三层是怎么组织的
2.1 这套源码的文件结构与模块边界
解压之后,你会看到典型的 MyEclipse / Eclipse 工程目录。src 下是 Servlet 和 JavaBean,WebRoot(或 webroot)下是 JSP 页面、WEB-INF、配置文件。给个典型清单:
| 路径 | 内容 | 作用 |
|---|---|---|
| src/com/company/servlet | EmployeeServlet、LoginServlet、DeptServlet | 请求入口、控制页面跳转 |
| src/com/company/bean | Employee、Department、User | 数据模型,对应数据库表 |
| src/com/company.dao | EmployeeDao、DeptDao | JDBC 数据访问层 |
| src/com/company.util | DBUtil.java | 数据库连接工具类 |
| WebRoot/WEB-INF/web.xml | 部署描述符 | 配置 Servlet 映射、欢迎页 |
| WebRoot/login.jsp、index.jsp | 登录页和主框架页 | 前端展示 |
| sql/company.sql 或 *.sql | 建库建表脚本 | 导入 MySQL 生成表结构和初始数据 |
这个分层的价值在于边界清晰:JSP 只负责展示,Servlet 只做路由和控制,JavaBean 承载数据,DAO 屏蔽 SQL 细节。用现在的话说,这就是最朴素的 MVC,只是没有 Spring 帮你管对象。拆这套源码的时候,你会发现所有 Servlet 都继承 HttpServlet,doPost 里拿 request 参数、调 DAO、往 request 或 session 里塞结果、forward 到 JSP。这套写法虽然原始,但每一步都看得见,对理解请求生命周期非常有帮助。
2.2 数据流转:一次“查询员工列表”请求背后发生了什么
从浏览器输入http://localhost:8080/hrms/EmployeeServlet?action=list开始,到页面渲染出表格,中间走了一条固定的链路。web.xml 里配置的映射决定请求落到哪个 Servlet,Servlet 根据 action 参数决定调哪个 DAO 方法,DAO 通过 DBUtil 拿 Connection 执行 SQL,结果集被封装成 List<Employee>,塞进 request 的 attribute,最后 forward 到 list.jsp,由 JSP 用<c:forEach>或脚本片段遍历输出。
这里特别值得注意的一个设计细节是请求参数action。这套源码用一个 Servlet 承载多个操作,比如同一个 EmployeeServlet 处理增删改查,靠action=add、action=update、action=delete、action=list来分流。优缺点都很明显:优点是类数量少,适合课程设计的篇幅;缺点是方法一多,if-else 链会变得很臃肿,我们后面改造时会讲怎么优化。
2.3 JSP 页面里两种写法的取舍
如果用记事本打开 index.jsp,你会同时看到两种风格的代码:一种是<%开头的 Java 脚本片段,直接写在页面里做循环;另一种是用 JSTL 标签库的<c:forEach>。老项目里前者居多,新一点的项目里后者居多。这套源码大概率两种都混着。脚本片段的写法直观,对初学者友好,但页面里 Java 代码越多,维护越痛苦,改个样式都可能碰到业务逻辑。JSTL 则把循环和判断收敛成标签,页面干净,但需要引入 jstl.jar 和 standard.jar,并且在 web.xml 里不额外配置也能用,只要页面里<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>声明就行。拆源码时如果看到报错说找不到标签,先确认 lib 下有没有这两个 jar。
3. 部署这套源码:从 JDK 到 Tomcat 到 MySQL 的完整步骤
3.1 环境版本选择:这一步定生死
部署 JSP 老项目,环境版本不是越新越好,反而要先看源码用的什么底子。血泪经验是:JDK 8 + Tomcat 8.5 是一对黄金搭档,MySQL 5.7 对老驱动和 SQL 语法的兼容性最好。如果你本机是 JDK 11 甚至 17,Tomcat 8.5 依然能跑,但如果你硬上 Tomcat 10,那 web.xml 头部的命名空间都要换,Servlet 类的包名从 javax.servlet 变成 jakarta.servlet,老源码直接编译不过,这不是配置能解决的,是 API 层面的断代。
先确认 JDK 是否正确配置。命令行里执行:
java -version javac -version echo %JAVA_HOME%如果 javac 不识别,说明 PATH 里没配上 JDK 的 bin 目录。常见做法是把JAVA_HOME指向 JDK 安装根目录,比如C:\Program Files\Java\jdk1.8.0_202,然后在 PATH 里追加%JAVA_HOME%\bin。配置完后重开命令行窗口验证。这步看着基础,但十个部署问题里至少有两个卡在环境变量上——特别是你之前装过多个 JDK 版本时。
Tomcat 这边,下载 zip 版解压就行,不需要安装。解压后 bin 目录下的 startup.bat 是启动脚本,catalina.bat 是核心控制脚本。第一次启动前建议改一下 conf/server.xml 里的端口,尤其是 8080 被占用的时候:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" />把 8080 改成 8088 或 9090,改完重启 Tomcat 才生效。启动后打开浏览器访问http://localhost:8088,看到 Tomcat 默认首页就算环境通了。这一步先把容器跑起来,别急着丢项目,环境不通后面全是噪音。
3.2 导入数据库:别直接用外部工具乱执行
解压目录里的 sql 文件夹通常有一个 company.sql 或 hrms.sql。这里要强调的是:不要双击 .sql 文件用文本编辑器打开复制粘贴到 Navicat 查询窗口执行,因为脚本里可能包含DELIMITER $$这种存储过程定义,也可能包含中文注释,客户端编码不对就会中途报错。更稳的做法是在命令行里执行,编码用 UTF-8:
mysql -uroot -p --default-character-set=utf8 CREATE DATABASE IF NOT EXISTS hrms DEFAULT CHARACTER SET utf8; USE hrms; SOURCE D:/download/hrms/sql/company.sql;执行完用SHOW TABLES;验证一下,能看到 sys_user、employee、department 之类的表就说明导入成功。如果提示某个字段不存在,别急着改脚本,先看是不是导错了数据库——很多人忘了USE hrms,结果表建到了别的库下面。检查SELECT DATABASE();确认当前库。
这种命令行方式的好处是错误提示直接、编码可控,而且能保留脚本里的注释,后续要手动补数据也方便。导入完成后用SELECT COUNT(*) FROM sys_user;确认初始管理员账号在,一般会有一条 admin 记录,密码可能在脚本里写死为 123456 或 admin,也可能封装在 MD5 后的密文里。
3.3 数据源配置:JDBC 连接串里的那几个关键参数
老 JSP 项目的数据库连接配置通常集中在 src 下的 DBUtil.java 里,或者 conf 目录下。找到核心配置那几行,一般长这样:
Class.forName("com.mysql.jdbc.Driver"); String url = "jdbc:mysql://localhost:3306/hrms?useUnicode=true&characterEncoding=utf8"; String user = "root"; String password = "123456";这里最容易改错的是 Driver 类名和 URL 格式。如果用 MySQL 5.x,驱动类是com.mysql.jdbc.Driver;如果你本机是 MySQL 8.x,就得换成com.mysql.cj.jdbc.Driver,并且 URL 后面要加serverTimezone=Asia/Shanghai,否则启动时百分之百报Server returns invalid timezone的错。我的习惯是把密码统一改成自己本机的 root 密码,然后加一个useSSL=false参数,避免 MySQL 8 默认 SSL 握手报错:
String url = "jdbc:mysql://localhost:3306/hrms?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai";注意这里有两个坑:一是字符编码必须和 JSP 页面里pageEncoding="UTF-8"保持一致,否则中文姓名和部门名称在库里就是乱码,这不是显示层问题,是数据源头就坏了;二是如果项目用的数据库名不是 hrms,要同步改 URL 里的库名。改完 DBUtil 需要重新编译,如果你用 Eclipse 或 IDEA 直接跑,项目会自动编译;如果是手工部署到 Tomcat,那就得重新 build 出 class 文件再覆盖到 WEB-INF/classes 下。
3.4 部署到 Tomcat:IDE 内外两条路线
有 IDE 就走 IDE 路线。以 Eclipse 为例,右键项目选择 Properties → Targeted Runtimes,勾选你的 Tomcat 8.5,然后右键项目 → Run As → Run on Server。IDEA 里则是配置 Tomcat Server,Deployment 标签页点 + 号选 Artifact,Application context 填/hrms,这样访问路径就是http://localhost:8080/hrms。
没有 IDE 或想搞清楚手工部署的人在后面,直接把项目导出成 war 包,或者把 WebRoot 下的内容连同编译后的 classes 一起拷贝到 Tomcat 的 webapps 下,改成 hrms 文件夹名。Tomcat 是支持热部署目录的,启动时会自动加载 webapps 下的应用。手工部署时尤其注意 WEB-INF/classes 里要有编译好的 .class 文件,lib 里有 mysql-connector-java.jar,WEB-INF/lib 下还要有 jstl.jar 和 standard.jar。少了 jstl.jar 的典型症状是 JSP 页面报The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved,这是缺包不是写错代码。
启动 Tomcat 后访问http://localhost:8080/hrms/login.jsp,看到登录页就说明部署成功了。如果 Tomcat 起不来,先去 logs/catalina.out 或 logs/localhost.log 里看异常堆栈,这是唯一靠谱的排错起点,别瞎猜。
4. 拆核心模块源码:登录认证、SQL 防注入与 CRUD 的标准写法
4.1 登录逻辑:Session 与请求分发的配合
登录是整个系统的入口,这里的代码质量决定了后续所有页面的鉴权方式。典型的 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 userDao = new UserDao(); User user = userDao.findByUsernameAndPassword(username, password); if (user != null) { request.getSession().setAttribute("loginUser", user); response.sendRedirect("index.jsp"); } else { request.setAttribute("error", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); } }逻辑很直白:拿参数、查库、判断、跳转。有一个值得留意的细节是成功之后用sendRedirect,失败用forward。为什么要区分?因为sendRedirect是重定向,浏览器地址栏会变成 index.jsp,同时会重新发起一次请求,之前的 request 对象失效,但 session 还在;forward是服务端跳转,request 里的属性(error 信息)能带到 JSP 里。如果登录失败你也用重定向,那 error 属性就丢了,页面上永远看不到错误提示。这个差异在面试里也常被问到,建议对照源码多读两遍。
4.2 过滤器与未登录拦截:很多项目缺的一环
另一个值得拆的类是权限过滤器。如果这套源码里带了LoginFilter,它的作用就是拦截所有请求,检查 session 里有没有登录标记。核心逻辑是这样:
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { HttpServletRequest req = (HttpServletRequest) request; HttpServletResponse resp = (HttpServletResponse) response; HttpSession session = req.getSession(); String uri = req.getRequestURI(); if (uri.contains("login.jsp") || uri.contains("LoginServlet") || uri.endsWith(".js") || uri.endsWith(".css") || uri.endsWith(".jpg")) { chain.doFilter(request, response); return; } if (session == null || session.getAttribute("loginUser") == null) { resp.sendRedirect("login.jsp"); } else { chain.doFilter(request, response); } }这个过滤器要注册到 web.xml 里,并配置<url-pattern>/*</url-pattern>拦截所有路径。白名单那段逻辑很重要:登录页本身、登录接口、静态资源(JS、CSS、图片)必须放行,否则页面样式全丢,而且会陷入“访问 login.jsp 也被拦去 login.jsp”的死循环。你可以检查源码里的过滤器有没有处理静态资源放行,很多时候原项目没写这层,我拆包时会在讲解里补上这段,并提醒加过滤器时把白名单做全。
4.3 DAO 层与 PreparedStatement:SQL 注入防线
这家公司的 DAO 层几乎都用 JDBC 直连。查询员工列表的典型逻辑如下:
public List<Employee> findAll() { List<Employee> list = new ArrayList<Employee>(); Connection conn = null; PreparedStatement ps = null; ResultSet rs = null; try { conn = DBUtil.getConnection(); String sql = "SELECT id, emp_no, name, dept_id, phone, hire_date FROM employee"; ps = conn.prepareStatement(sql); rs = ps.executeQuery(); while (rs.next()) { Employee emp = new Employee(); emp.setId(rs.getInt("id")); emp.setEmpNo(rs.getString("emp_no")); emp.setName(rs.getString("name")); emp.setDeptId(rs.getInt("dept_id")); emp.setPhone(rs.getString("phone")); list.add(emp); } } catch (SQLException e) { e.printStackTrace(); } finally { DBUtil.close(rs, ps, conn); } return list; }如果按姓名搜索,SQL 里会带WHERE name LIKE ?,而?参数通过ps.setString(1, "%" + keyword + "%")传进去,而不是字符串拼接。这是一个重要的分水岭:拼接 SQL 容易出注入漏洞,PreparedStatement预编译会把单引号转义掉。我拆过的源码里如果发现有的地方是拼接,建议你改掉,把这个改动写进自查清单。
4.4 添加与更新员工:事务与脏数据风险
EmployeeServlet 里的 add 操作一般会做两件事:接收表单参数、检查必填项、调 DAO 插入。这里最常见的缺口是事务处理——如果需要同时插入 employee 表和 employee_detail 表(员工主表 + 详细履历表),而 DAO 里两次分别获取连接执行 SQL,那中间任何一条失败,数据就半截落库了。正确的做法是在 Service 层包一个事务:
Connection conn = DBUtil.getConnection(); try { conn.setAutoCommit(false); EmployeeDao.save(conn, employee); EmployeeDetailDao.save(conn, detail); conn.commit(); } catch (SQLException e) { conn.rollback(); throw new RuntimeException("保存员工失败", e); } finally { conn.close(); }但很多课程设计级源码压根没有 Service 层,直接在 Servlet 里连调两个 DAO,事务自然就没了。你拿到源码后可以搜一下setAutoCommit,如果一次都没出现,说明整个项目都是单表操作,不存在跨表事务,这也是它的一个简化边界,写报告时值得提一句。
5. 部署避坑:从 JDK 到字符集,最常见的八个启动坑
5.1 404 或跳转到 Tomcat 首页
现象:启动成功后,访问http://localhost:8080/项目名/login.jsp返回 404,或者页面跑到 Tomcat 默认首页去了。
原因:应用没有成功部署。要么是 war 包没放进 webapps,要么是项目名输错,要么是 IDEA/Eclipse 的 Application context 没配对。Tomcat 默认首页说明你访问的是根路径,而应用挂在/hrms下。
解决:确认访问路径带项目名。IDEA 的 Deployment 里检查 Application context 是否为/hrms,手工部署时确认 webapps 下有项目文件夹,并且里面有 WEB-INF 目录。用http://localhost:8080/hrms/login.jsp重新访问。
5.2 启动报 ClassNotFoundException: com.mysql.jdbc.Driver
现象:Tomcat 启动日志里出现找不到 MySQL 驱动类的错误。
原因:mysql-connector-java.jar 没放进 WEB-INF/lib,或者驱动类名写成了 5.x 版本,但实际驱动 jar 是 8.x 的。
解决:先看你用的连接 jar 版本。5.x 版本对应com.mysql.jdbc.Driver,8.x 版本对应com.mysql.cj.jdbc.Driver。把 jar 放到 WEB-INF/lib 下并重新部署。如果看到Public Key Retrieval is not allowed,加参数allowPublicKeyRetrieval=true。
5.3 页面中文全是问号或乱码
现象:登录后员工列表里所有中文姓名显示为???,或者 JSP 页面标题乱码。
原因:三层编码不一致。JSP 文件本身没存成 UTF-8、pageEncoding没设对、数据库连接串没带characterEncoding=utf8、数据库和表的排序规则不是 utf8,任一环节断掉都是乱码。
解决:用下面一条硬规则排查——JSP 文件开头加<%@ page language="java" pageEncoding="UTF-8" contentType="text/html; charset=UTF-8"%>;DBUtil 连接串带useUnicode=true&characterEncoding=utf8;数据库建表脚本确认DEFAULT CHARSET=utf8。三处统一后通常能解决。
5.4 端口被占用,Tomcat 启动一闪而过
现象:双击 startup.bat 后窗口一闪就消失,http://localhost:8080打不开。
原因:8080 端口被其他进程占用,或者之前启动的 Tomcat 没关干净。命令行窗口一闪而过是因为没有保留错误输出。
解决:先用netstat -ano | findstr 8080看占用进程,要么杀掉对应 PID,要么改 Tomcat 的 server.xml 端口。更稳的启动方式是在命令行手动执行catalina.bat run,这样日志直接打在窗口里,能直接看到报错原因。
5.5 HTTP 状态 500:JSP 里脚本片段报空指针
现象:页面能打开,但列表区域报NullPointerException,堆栈指向 JSP 里的 for 循环。
原因:Servlet 里request.setAttribute("empList", list)时 list 是 null,或者往 session 里存的对象类型不匹配。常见场景是请求路径没带action=list,Servlet 没走查询分支,导致 attribute 没设置。
解决:访问路径补齐参数,比如EmployeeServlet?action=list。检查 Servlet 里查询后是否真的把 list 塞进了 request,以及 JSP 里取值时 key 是否和 Servlet 里 set 的一致——这是最典型的低级 bug,用浏览器的 URL 和 Servlet 代码对一遍就能定位。
5.6 登录成功但跳回登录页
现象:输入正确账号密码,点击登录,页面闪一下后回到 login.jsp。
原因:过滤器拦截了 index.jsp 或后续页面的请求,而 session 里没有登录标记。可能是登录成功后sendRedirect的路径写错导致新一轮请求没带 session,也可能过滤器的逻辑判断有问题——有些原项目判断条件是session.getAttribute("loginUser") != null,但登录时存的是user这个 key。
解决:登录 Servlet 里 set 的 key 和过滤器里 get 的 key 必须完全一致。全局搜索setAttribute和getAttribute两处代码,确认是同一个字符串。另外sendRedirect的目标路径要是相对路径或完整上下文路径,比如index.jsp或/hrms/index.jsp,写成了绝对路径会导致新请求绕过你的应用上下文。
6. 验收与改造:把这份源码变成能写进简历的项目
先把验收标准定下来。一个 JSP 人事管理系统能不能算“跑通”,我会走一遍这五个检查:登录退出是否正常流转;员工信息的增删改查是否落地到数据库,重启 Tomcat 后数据还在;部门与员工有没有关联,删除部门时是否误伤了员工数据;三个及以上页面的中文显示无乱码;最后是直接访问受保护页面能否被拦截回登录页。这几项过完,才敢说这份源码能作为基础盘往下走。
改造的优先级,我建议先接手一个影响面最大但改动最小的:把 JDBC 连接换成连接池。Apache DBCP 或 C3P0 都行,DBCP2 配置更简洁。核心变化在 DBUtil:
private static BasicDataSource dataSource = null; static { dataSource = new BasicDataSource(); dataSource.setDriverClassName("com.mysql.jdbc.Driver"); dataSource.setUrl("jdbc:mysql://localhost:3306/hrms?useUnicode=true&characterEncoding=utf8&useSSL=false"); dataSource.setUsername("root"); dataSource.setPassword("123456"); dataSource.setInitialSize(5); dataSource.setMaxTotal(20); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); }原来每次操作都DriverManager.getConnection,现在改成从池里借连接,性能提升是次要的,重点是让 DAO 层在并发查询时不会频繁握手、也不会把连接漏关。改完用ab或者 Jmeter 简单压一下,哪怕只有 50 个并发,差异也很明显。另一个性价比高的改造是给 EmployeeServlet 的分流逻辑引入一个简单的命令模式,或者至少把action的分支抽到一个 switch 里集中管理,扩展性会好很多,写简历时也能说是“重构了请求路由”。
如果想要一个能现场演示、又不太需要新增代码的功能,给员工列表加一个导出 CSV 是最合适的。新建一个ExportServlet,在doGet里查询所有员工,设置响应头Content-Disposition: attachment; filename=employees.csv,然后直接往输出流里写逗号分隔的文本即可,完全不需要引入 POI。这个功能 20 行代码搞定,但演示效果非常直观,而且面试时可以说“通过导出功能加深了对响应头与输出流的理解”。
从那以后我每次拿到这类打包源码,都会先强制走一遍“环境匹配 → 数据库脚本导入 → 编译部署 → 登录走查 → 读核心 Servlet”这五步,缺一步都容易在中途把时间耗在查环境上。这份源码本身不是多复杂的系统,但它把 JSP 时代的标准组织方式和坑都沉淀下来了,你把它完整跑通并改掉其中两处硬伤,这笔时间就花得值。希望帮到你。
本文还有配套的精品资源,点击获取