☰
JSP人事管理系统源码解析:从部署到排坑的完整实战指南
2026/10/2 3:25:15 网站建设 项目流程

简介:这是一份基于JSP的企业人事管理系统项目源码,面向Java开发者、毕业设计学生及需要快速搭建人事模块的小型项目团队。资源以ZIP格式打包,整体仅5.8MB,共包含229个文件,其中以88个JSP页面、18个Java源文件及对应class文件为核心,配合16个JAR依赖库、28个TLD标签定义及图片、XML配置等,覆盖前端展示、后端业务处理与运行环境配置,结构清晰便于部署。目前已有175人学习下载,是理解传统JavaWeb分层开发的实用样本。用于毕业设计时,可对照源码掌握业务流程;用于个人研究时,可学习JSP+Servlet+JavaBean开发模式;用于小型项目参考时,可提取人事信息维护、列表查询等常见功能实现,并基于现有代码二次扩展。

1. 从课程设计到生产可用:这份JSP人事管理系统源码到底值不值得打开

这份“jsp-企业人事管理系统.zip项目JAVA源码+资料打包下载”,应该是不少Java学习者硬盘里躺过的第一个完整Web项目。它不是什么高大上的微服务,而是最经典的JSP+Servlet+JavaBean+JDBC组合:JSP做页面展示、Servlet接收请求、JDBC读写MySQL,最后打成WAR放进Tomcat。价值在于把登录、员工增删改查、部门管理、考勤、薪资这条业务线完整串了起来,比单练一个jsp个人信息展示页面更接近真实工作。

适合两类人:一类是做基于jsp的毕设选题或java课程设计案例源码的学生,需要一套能跑、能讲、能改的起步代码;另一类是刚入职就要接手老系统的初级开发,得先学会读懂这套老代码。一个反直觉的结论:2025年SpringBoot占主流,但生产环境里大量老系统仍靠这套技术撑着,读懂它的收益并不比背新框架差。

2. JSP+Servlet+MySQL的人事管理架构:为什么老技术栈仍然值得读源码

2.1 JSP在人事管理系统里扮演的角色:页面渲染与请求转发的边界

JSP页面第一次被访问时,Tomcat会把它翻译成一个Servlet类,再编译成class执行。所以JSP不是纯模板,它本身就是一个Servlet,只是把写Java代码的门槛降低了。这个机制决定了它的边界:负责把数据渲染成HTML页面,而不是承担复杂业务逻辑。小型企业人事管理系统的页面构成通常很清晰:登录页校验session、员工列表页遍历集合、部门管理页做增删改、考勤打卡页登记时间、薪资浏览页按月查询。看一遍真实代码,比背java面试八股里的概念有用得多。

一个典型的人事管理列表请求,在Servlet里长这样:

@WebServlet("/employee/list") public class EmployeeListServlet extends HttpServlet { private EmployeeDao employeeDao = new EmployeeDao(); protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String deptId = req.getParameter("deptId"); List<Employee> list = employeeDao.findByDept(deptId); req.setAttribute("employeeList", list); req.getRequestDispatcher("/WEB-INF/views/employee_list.jsp").forward(req, resp); } }

这段代码的逻辑很直白:读参数、查数据、存request、转发页面,四件事。注意这里用的是forward而不是sendRedirect,区别在于forward不会丢失request里的attribute,JSP里还能用${employeeList}直接遍历;如果改用redirect,集合数据就得塞进session,反而容易造成session膨胀。另一个细节是JSP放在了WEB-INF下,用户没法通过浏览器直接输入路径访问,这是老项目里常见且值得保留的安全习惯。

在这个结构里,JSP的九个隐式对象也能一一对上号:request用来接参数和放数据,response控制输出,session管登录态,application放全局配置。面试里问JSP生命周期,不如亲手在这个项目里断点看一下_jspService方法是怎么被调用的。

2.2 企业人事管理系统的核心功能拆解:员工、部门、考勤、薪资四张表

人事管理系统的数据模型,绕不开四张核心表:部门表、员工表、考勤表、薪资表。部门对员工是一对多,员工对考勤和薪资又是一对多。建表语句的常见做法如下:

CREATE DATABASE hrms DEFAULT CHARSET utf8mb4; USE hrms; CREATE TABLE t_dept ( dept_id INT PRIMARY KEY AUTO_INCREMENT, dept_name VARCHAR(50) NOT NULL, dept_location VARCHAR(100) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_employee ( emp_id INT PRIMARY KEY AUTO_INCREMENT, dept_id INT NOT NULL, emp_no VARCHAR(20) NOT NULL UNIQUE, emp_name VARCHAR(50) NOT NULL, emp_phone VARCHAR(20), hire_date DATE, CONSTRAINT fk_emp_dept FOREIGN KEY (dept_id) REFERENCES t_dept(dept_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_attendance ( att_id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL, att_date DATE NOT NULL, check_in_time TIME, check_out_time TIME, CONSTRAINT fk_att_emp FOREIGN KEY (emp_id) REFERENCES t_employee(emp_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE t_salary ( salary_id INT PRIMARY KEY AUTO_INCREMENT, emp_id INT NOT NULL, month VARCHAR(6) NOT NULL, base_salary DECIMAL(10,2), bonus DECIMAL(10,2), deduct DECIMAL(10,2), real_salary DECIMAL(10,2), CONSTRAINT fk_sal_emp FOREIGN KEY (emp_id) REFERENCES t_employee(emp_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

几个选型理由值得说清楚:员工入职日期用DATE而不是字符串,是为了能走索引做范围查询,比如“查今年入职的人”;薪资字段用DECIMAL(10,2)而不是float/double,否则算工资会出现精度漂移,这是做面向对象编程和数据库设计时最容易忽略的点;emp_no加了UNIQUE约束,防止录入重复工号。网上很多半成品项目只给一张员工表,部门字段直接用字符串存,部门一改名就要批量update,这套四表模型就是避免那种返工的。

2.3 JDBC连接池与DBUtil封装:跑得动三年的数据库访问层

老项目里最怕看到每个DAO里都写一遍DriverManager.getConnection(),那意味着每次请求都要新建物理连接。50个并发就能把MySQL的连接数打满。靠谱的做法是像下面这样,用C3P0做连接池,把连接参数集中到一个类和一个配置文件里:

public class DBUtil { private static DataSource ds; static { try { InputStream in = DBUtil.class.getClassLoader() .getResourceAsStream("db.properties"); Properties props = new Properties(); props.load(in); ComboPooledDataSource cps = new ComboPooledDataSource(); cps.setDriverClass(props.getProperty("jdbc.driver")); cps.setJdbcUrl(props.getProperty("jdbc.url")); cps.setUser(props.getProperty("jdbc.username")); cps.setPassword(props.getProperty("jdbc.password")); cps.setInitialPoolSize(Integer.parseInt(props.getProperty("pool.initSize"))); cps.setMaxPoolSize(Integer.parseInt(props.getProperty("pool.maxSize"))); ds = cps; } catch (Exception e) { throw new ExceptionInInitializerError(e); } } public static Connection getConnection() throws SQLException { return ds.getConnection(); } }

配套的db.properties长这样:

jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/hrms?useUnicode=true&characterEncoding=utf8 jdbc.username=root jdbc.password=root pool.initSize=5 pool.maxSize=20

参数含义要说清楚:initialPoolSize是启动时预热的连接数,5个够用;maxPoolSize是最大连接数,20是个保守值,并不是越大越好——连接池开太大,MySQL自己也有max_connections上限,Tomcat和数据库之间会互相拖累。这里有个常见误解:很多人不敢调用conn.close(),以为会把连接真关掉。其实从连接池拿出来的连接,close只是归还给池子,不写这一行反而会导致连接耗尽。DBUtil这个类封装得好,所有DAO都只依赖它取连接,以后想换成HikariCP或Druid,只改这一个类的代码。

注意:db.properties里jdbc.url中的useUnicode=true和characterEncoding=utf8两个参数决定了中文写入数据库是正常字还是问号,很多系统上线一两年后才暴雷,第五章会专门讲这个坑。

3. 在IDEA里把源码跑起来:从导入到员工列表页出现的完整链路

3.1 动手前先核对三个环境变量:JDK、Tomcat、MySQL,缺一个就白忙

拿到zip解压后别急着双击,先对齐运行环境。我一般按这张表逐项核对:

组件推荐版本验证命令容易踩的坑
JDK1.8(8u202)java -version老项目编译级别基本都是1.8,别用17硬跑
Tomcat8.5.x 或 9.0.xcatalina.bat version10.x开始用Jakarta命名空间,和javax不兼容
MySQL5.7 或 8.0mysql --version驱动类名不同,5.7和8.0不能混用
IDEA2021及以上无社区版也可以跑传统Web项目

命令行里java -version能输出1.8.x,说明JDK就绪。如果输出的是17或21,先去把JAVA_HOME和PATH调好,否则后面全在跟编译版本打架。Tomcat版本这条特别容易翻车:Tomcat 10以上把javax.servlet改成了jakarta.servlet,老项目的源码里全是javax开头,部署上去直接ClassNotFoundException。与其折腾代码去适配新容器,不如直接用JDK8+Tomcat9的组合,这套搭配跑十年前的老项目最省事。MySQL的java环境变量配置和连接驱动版本同样重要,5.7用com.mysql.jdbc.Driver,8.0用com.mysql.cj.jdbc.Driver,写错一步就报找不到驱动类。

3.2 导入源码与配置Tomcat:区分普通Web项目和Maven项目的两种路径

解压后先看根目录有没有pom.xml。有,这是Maven项目;没有,是普通Web项目,源码直接在src或WebRoot目录下。两种情况处理方式不同。

普通Web项目的导入步骤,我一般按这个顺序来:

  1. File -> Open选中项目根目录,IDEA如果提示Unlinked,点Configure并勾选src为Sources Root。
  2. Project Structure -> Project:SDK选1.8,Language level选8。
  3. Project Structure -> Artifacts:点+新增Web Application Exploded,artifact取名hrms。
  4. Run -> Edit Configurations -> 点+选Tomcat Server -> Local,Server标签里选本机Tomcat目录。
  5. Deployment标签点+添加刚建好的artifact,Application context填/hrms。
  6. Apply后启动,浏览器访问http://localhost:8080/hrms/login.jsp。

Maven项目则简单一些:先确认本地Maven配置好阿里云镜像,执行mvn clean package -DskipTests,如果测试代码写得有问题,跳过测试能省很多麻烦。构建成功后按上面第4步开始配置Tomcat即可。

这里有两个关键参数要弄明白。Artifact的Exploded模式指展开的目录结构,修改JSP后刷新页面就能看到效果,调试阶段用这个模式效率最高;WAR模式是压缩包,适合最后部署。Application context决定访问路径,填/hrms,访问地址就是localhost:8080/hrms/。这套逻辑和用IDEA新建jsp项目是相通的,新建项目时IDEA会自动帮你建好Artifact,导入老项目则需要手动补上这一步。很多人卡在“IDEA里能运行但浏览器404”,八成就是Deployment里没加artifact。

3.3 建库、导SQL、初始化管理员:让第一个登录请求不再走弯路

项目里通常会带一份SQL脚本,但要注意它大多只包含建表和基础数据,不包含建库语句。我一般这样初始化:

CREATE DATABASE IF NOT EXISTS hrms DEFAULT CHARSET utf8mb4; USE hrms; -- 项目自带SQL脚本,路径换成你自己的解压目录 source /your_unzip_path/hrms.sql; -- 初始化管理员账号,密码123456对应的MD5值 INSERT INTO t_admin(login_name, pwd, real_name) VALUES('admin', 'e10adc3949ba59abbe56e057f20f883e', '系统管理员');

执行SQL可以选mysql命令行、Navicat客户端或IDEA自带的Database工具,效果一样。这里明确一点:老项目里密码存MD5是当年的通行做法,不是为了安全,是为了数据库管理员也看不到明文。要做新开发建议用BCrypt,但在这套系统里不要随便改密码校验逻辑,否则登录永远对不上。

接下来改db.properties里的连接信息:

jdbc.driver=com.mysql.cj.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/hrms?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai jdbc.username=root jdbc.password=你自己的密码

MySQL 8.0的驱动类名是com.mysql.cj.jdbc.Driver,老代码里写的com.mysql.jdbc.Driver在8.0下会报ClassNotFoundException,所以这里我直接给的是8.0写法。serverTimezone=Asia/Shanghai是避免时区报错的固定参数,useSSL=false是为了消除连接警告。改完启动Tomcat,输入admin和123456,看到带左侧菜单的主界面,就说明全链路通了。

4. 把传统JSP项目打包成WAR:从IDEA导出到Tomcat自动解压的部署套路

4.1 IDEA导出WAR的两种姿势:Build Artifacts与Maven Package,参数差别在哪

传统JSP项目打包war,是部署到服务器前绕不开的一步。IDEA里导出WAR有两种做法,适用不同项目形态。

项目形态导出方式产物
普通Web项目Build -> Build Artifacts -> 选择hrms:warRebuild输出到out/artifacts/hrms_war
Maven项目mvn clean package -DskipTests输出到target/hrms.war

两种方式产出的WAR内容一致:WEB-INF下分classes、lib两个目录,classes是编译后的.class文件,lib是所有依赖jar包。区别在于:Artifact方式依赖IDEA的编译配置,Maven方式依赖pom.xml里的依赖声明。如果项目里既有lib目录又在pom里声明了jar,打包时一定要检查有没有重复依赖,WAR里出现同一类库的两个版本,运行时就是ClassCastException的命。

调试阶段用Exploded目录,部署阶段用WAR压缩包,这个结论不用纠结。Exploded意味着Tomcat直接读项目目录,改一个JSP刷新就生效;WAR则每次改动都要重新打包上传。生产环境没人用Exploded,一个是目录结构暴露细节,另一个是不方便版本管理。

4.2 WAR放进Tomcat的两种方式:扔进webapps与写Context描述符

拿到hrms.war之后,部署到Tomcat有两种常见姿势。

第一种最偷懒:把WAR直接复制到$TOMCAT_HOME/webapps/下,启动时Tomcat会自动解压成hrms目录,然后映射到http://服务器地址:8080/hrms。注意Tomcat默认开启了unpackWARs="true",如果不想每次启动都重新解压,可以保持默认,因为删掉WAR只留下目录也不会影响运行。

第二种可控性更强:在$TOMCAT_HOME/conf/Catalina/localhost/目录下新建一个hrms.xml,内容这样写:

<Context path="/hrms" docBase="/opt/tomcat9/webapps/hrms.war" reloadable="true" />

这里docBase的路径可以指向WAR文件,也可以指向解压后的目录;reloadable="true"的含义是class文件变化时自动触发热加载,开发时方便,生产上建议改成false,否则频繁的类替换会触发多次Full GC,系统越跑越卡。两种方式选哪种?单应用小站点直接扔webapps,需要把应用放在非webapps目录、或者做多版本隔离的时候用Context描述符。

4.3 改context root后页面全404:所有写死的路径都欠一次全局替换

老项目里最常见的部署翻车现场:把WAR部署到Tomcat后登录页能打开,点登录就404。原因往往是项目代码里把路径写死了:

<a href="/hrms/employee/list">员工管理</a> <script src="/hrms/js/jquery.min.js"></script>

一旦部署context root不是/hrms,或者前面挂了nginx做前端入口,这些写死的路径全部失效。治本的办法是在每个JSP开头取上下文路径,再用它拼所有链接:

<%@ page contentType="text/html;charset=UTF-8" %> <% String ctx = request.getContextPath(); %> <script src="<%=ctx%>/js/jquery.min.js"></script> <a href="<%=ctx%>/employee/list">员工管理</a>

request.getContextPath()返回的就是当前应用部署路径。项目名叫hrms时它返回/hrms,改叫hrms-web时自动跟着变,链接再也不会因为换路径而断。很多老项目不敢改代码,就只能永远绑死在/hrms上,换个环境就崩一次。

网上常有人问nginx支持jsp吗,答案是不支持。nginx不解析JSP,它的作用是把静态资源和动态请求转发给后端的Tomcat。当你在nginx里配置请求转发时,Tomcat里的context root和nginx转发的路径必须一致,否则登录成功后session一丢,就出现“明明登录了又跳回登录页”的诡异现象。所以先统一内部路径,再谈入口转发,顺序不能反。

5. 从404到中文乱码:JSP人事系统部署中最常见的五个坑

5.1 坑一:登录后跳转404,但Tomcat控制台干干净净

现象:登录成功后地址栏跳到了/employee/list,返回404,后台日志没有任何异常。

原因:最常见是Servlet映射路径和表单提交地址不一致,比如@WebServlet("/employee/list")里写的是/"employee/list",表单action却提交/employeeList;另一个高频原因是页面放在了WEB-INF下还被浏览器直接访问,WEB-INF下的文件只能通过Servlet转发,不能由URL直达。

解决:打开浏览器开发者工具看状态码——404说明资源不存在,500说明服务器内部异常,两个方向完全不同。然后核对form的action和Servlet注解里的url-pattern,大小写和斜杠都要完全一致。再看tomcat/logs/localhost.日期.log,这里才有真实的类名和行号。顺手用一条命令确认资源存不存在:

curl -I http://localhost:8080/hrms/login.jsp

返回200是正常的,返回404说明Tomcat路径没映射上。

5.2 坑二:新增员工中文名在页面正常,数据库里变成问号

现象:页面回显是“张三”,查数据库是“??”,浏览器看不出任何异常。

原因:字符集断在了三个位置至少一个:db.properties的url没带useUnicode=true&characterEncoding=utf8;JSP页面头部没声明charset=UTF-8;MySQL表本身建在了latin1字符集下。

解决:先检查表结构,如果是latin1,用一条命令转过来:

ALTER TABLE t_employee CONVERT TO CHARACTER SET utf8mb4;

再改db.properties并重启Tomcat。注意MySQL 8.0的驱动会自动识别utf8,但显式写上仍然是最稳妥的写法。这个坑最迷惑的地方在于浏览器显示正常、数据库却乱码,说明编码在Java层是正常的,断在了JDBC写库那一环,原因就是连接串参数缺失。

5.3 坑三:本地能登录,部署到服务器后登录成功又被踢回登录页

现象:本地Win环境一切正常,打包部署到Linux服务器后,输对账号密码还是跳回登录页。

原因:代码里把登录成功的跳转地址写死了http://localhost:8080/hrms/main.jsp,服务器上的浏览器把localhost解析成了自己电脑,当然打不开;另一种可能是在nginx转发场景下,请求经过转发后Tomcat拿到的Host头不对,session每次都被当成新会话。

解决:全局搜索代码里的localhost和绝对地址,统一替换成上下文路径:

response.sendRedirect(request.getContextPath() + "/main.jsp");

如果前面挂了nginx做入口转发,还得保证转发时带着原始的Host头,否则Tomcat的request.getRequestURL()永远是内网地址,session校验逻辑就会认为请求来源不可信。修好这一条,登录跳转才真正稳定。

5.4 坑四:导出报表时NoClassDefFoundError:org/apache/poi

现象:点击导出Excel按钮,页面500,后台抛出NoClassDefFoundError: org/apache/poi/xssf/usermodel/XSSFWorkbook。

原因:项目WEB-INF/lib下同时存在poi-3.9.jar和poi-ooxml-4.1.2.jar两个版本,类被加载器搞混乱了;如果是Maven项目,则可能是poi和poi-ooxml版本号没对齐。

解决:统一保留一个版本组合,推荐poi-4.1.2与poi-ooxml-4.1.2配套,poi-ooxml-schemas也跟着放进去。清理掉旧jar再重新打包部署。顺带说一句,java poi word能生成图表吗这类需求确实能做,但POI的依赖体积和版本兼容问题在传统JSP项目里最容易被低估,能用一个Excel模板循环填充数据,就别从头new Workbook写样式。

5.5 坑五:startup.bat闪退,或提示Address already in use:8080

现象:双击startup.bat窗口一闪而过,或者启动时提示Address already in use: JVM_Bind。

原因:双击startup.bat时JAVA_HOME没配好,脚本找不到java命令;8080端口被其他进程占用;Tomcat目录路径里带了空格。

解决:不要在Windows里双击bat,打开cmd手动执行:

catalina.bat run

这样错误信息会直接留在当前窗口,能看清是哪一行出的错。端口占用则先找到占用者再决定处理:

netstat -ano | findstr 8080 taskkill /PID 你看到的进程号 /F

这个坑的麻烦之处在于启动失败得太快,日志还没写进文件窗口就关了,所以让Tomcat在前台跑是定位问题最快的方式,比我第一次遇到时瞎猜原因高效得多。

6. 拿到源码后的正确打开方式:先验证再改造,三个值得动手的方向

6.1 用一个curl和一条SQL先给项目做体检

改代码前,先确认当前系统是健康的。我用两条命令做基线验证:

curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/hrms/login.jsp

返回200说明页面链路通,返回302说明有登录过滤器在跳转,也正常。再查一下数据完整性:

SELECT d.dept_name, COUNT(e.emp_id) FROM t_dept d LEFT JOIN t_employee e ON d.dept_id = e.dept_id GROUP BY d.dept_id, d.dept_name;

部门表LEFT JOIN员工表,能看出哪些部门是空部门,同时检查外键有没有断。改造前先备份数据库,这是后悔药,老系统维护的第一原则就是先能回滚再动手。

6.2 三个值得动手的小改造,都能写进简历

第一个方向是连接池替换:把DBUtil里的C3P0换成HikariCP,改动只集中在一个类,但依赖管理Maven后在pom里的变化却能让面试官看到你清楚连接池原理。第二个是SQL注入防护:把DAO里用字符串拼接的查询全部换成PreparedStatement,这个安全改造性价比极高。第三个是分页:给员工列表加LIMIT ? OFFSET ?和page参数,顺手把页面上的上一页下一页按钮补上。这三个方向做完,这套老源码基本就变成了一个结构清晰、可以演示的完整项目。

我拿到老JSP源码的习惯动作从来是先跑通、再备份、最后才动手改结构。改之前把db.properties和登录相关的代码读一遍,能少踩一大半坑;改完一定用上面那两条命令做回归检查。这个习惯帮我熬过了不少老系统维护的夜晚,希望对正在折腾这份源码的你也有用。

本文还有配套的精品资源,点击获取

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

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

立即咨询