
简介这是一份基于JSP的企业人事管理系统项目源码面向Java开发者、毕业设计学生及需要快速搭建人事模块的小型项目团队。资源以ZIP格式打包整体仅5.8MB共包含229个文件其中以88个JSP页面、18个Java源文件及对应class文件为核心配合16个JAR依赖库、28个TLD标签定义及图片、XML配置等覆盖前端展示、后端业务处理与运行环境配置结构清晰便于部署。目前已有175人学习下载是理解传统JavaWeb分层开发的实用样本。用于毕业设计时可对照源码掌握业务流程用于个人研究时可学习JSPServletJavaBean开发模式用于小型项目参考时可提取人事信息维护、列表查询等常见功能实现并基于现有代码二次扩展。1. 从课程设计到生产可用这份JSP人事管理系统源码到底值不值得打开这份“jsp-企业人事管理系统.zip项目JAVA源码资料打包下载”应该是不少Java学习者硬盘里躺过的第一个完整Web项目。它不是什么高大上的微服务而是最经典的JSPServletJavaBeanJDBC组合JSP做页面展示、Servlet接收请求、JDBC读写MySQL最后打成WAR放进Tomcat。价值在于把登录、员工增删改查、部门管理、考勤、薪资这条业务线完整串了起来比单练一个jsp个人信息展示页面更接近真实工作。适合两类人一类是做基于jsp的毕设选题或java课程设计案例源码的学生需要一套能跑、能讲、能改的起步代码另一类是刚入职就要接手老系统的初级开发得先学会读懂这套老代码。一个反直觉的结论2025年SpringBoot占主流但生产环境里大量老系统仍靠这套技术撑着读懂它的收益并不比背新框架差。2. JSPServletMySQL的人事管理架构为什么老技术栈仍然值得读源码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); ListEmployee list employeeDao.findByDept(deptId); req.setAttribute(employeeList, list); req.getRequestDispatcher(/WEB-INF/views/employee_list.jsp).forward(req, resp); } }这段代码的逻辑很直白读参数、查数据、存request、转发页面四件事。注意这里用的是forward而不是sendRedirect区别在于forward不会丢失request里的attributeJSP里还能用${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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;几个选型理由值得说清楚员工入职日期用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.drivercom.mysql.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/hrms?useUnicodetruecharacterEncodingutf8 jdbc.usernameroot jdbc.passwordroot pool.initSize5 pool.maxSize20参数含义要说清楚initialPoolSize是启动时预热的连接数5个够用maxPoolSize是最大连接数20是个保守值并不是越大越好——连接池开太大MySQL自己也有max_connections上限Tomcat和数据库之间会互相拖累。这里有个常见误解很多人不敢调用conn.close()以为会把连接真关掉。其实从连接池拿出来的连接close只是归还给池子不写这一行反而会导致连接耗尽。DBUtil这个类封装得好所有DAO都只依赖它取连接以后想换成HikariCP或Druid只改这一个类的代码。注意db.properties里jdbc.url中的useUnicodetrue和characterEncodingutf8两个参数决定了中文写入数据库是正常字还是问号很多系统上线一两年后才暴雷第五章会专门讲这个坑。3. 在IDEA里把源码跑起来从导入到员工列表页出现的完整链路3.1 动手前先核对三个环境变量JDK、Tomcat、MySQL缺一个就白忙拿到zip解压后别急着双击先对齐运行环境。我一般按这张表逐项核对组件推荐版本验证命令容易踩的坑JDK1.88u202java -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。与其折腾代码去适配新容器不如直接用JDK8Tomcat9的组合这套搭配跑十年前的老项目最省事。MySQL的java环境变量配置和连接驱动版本同样重要5.7用com.mysql.jdbc.Driver8.0用com.mysql.cj.jdbc.Driver写错一步就报找不到驱动类。3.2 导入源码与配置Tomcat区分普通Web项目和Maven项目的两种路径解压后先看根目录有没有pom.xml。有这是Maven项目没有是普通Web项目源码直接在src或WebRoot目录下。两种情况处理方式不同。普通Web项目的导入步骤我一般按这个顺序来File - Open选中项目根目录IDEA如果提示Unlinked点Configure并勾选src为Sources Root。Project Structure - ProjectSDK选1.8Language level选8。Project Structure - Artifacts点新增Web Application Explodedartifact取名hrms。Run - Edit Configurations - 点选Tomcat Server - LocalServer标签里选本机Tomcat目录。Deployment标签点添加刚建好的artifactApplication context填/hrms。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.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/hrms?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai jdbc.usernameroot jdbc.password你自己的密码MySQL 8.0的驱动类名是com.mysql.cj.jdbc.Driver老代码里写的com.mysql.jdbc.Driver在8.0下会报ClassNotFoundException所以这里我直接给的是8.0写法。serverTimezoneAsia/Shanghai是避免时区报错的固定参数useSSLfalse是为了消除连接警告。改完启动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_warMaven项目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默认开启了unpackWARstrue如果不想每次启动都重新解压可以保持默认因为删掉WAR只留下目录也不会影响运行。第二种可控性更强在$TOMCAT_HOME/conf/Catalina/localhost/目录下新建一个hrms.xml内容这样写Context path/hrms docBase/opt/tomcat9/webapps/hrms.war reloadabletrue /这里docBase的路径可以指向WAR文件也可以指向解压后的目录reloadabletrue的含义是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 contentTypetext/html;charsetUTF-8 % % String ctx request.getContextPath(); % script src%ctx%/js/jquery.min.js/script a href%ctx%/employee/list员工管理/arequest.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没带useUnicodetruecharacterEncodingutf8JSP页面头部没声明charsetUTF-8MySQL表本身建在了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 坑四导出报表时NoClassDefFoundErrororg/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 use8080现象双击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和登录相关的代码读一遍能少踩一大半坑改完一定用上面那两条命令做回归检查。这个习惯帮我熬过了不少老系统维护的夜晚希望对正在折腾这份源码的你也有用。本文还有配套的精品资源点击获取