news 2026/10/7 12:39:47

Java Web员工管理系统:Servlet+JSP+JDBC实战指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java Web员工管理系统:Servlet+JSP+JDBC实战指南

简介:这是一套面向Java Web初学者与进阶开发者的员工管理系统实战源码资源,聚焦Servlet、JSP、JDBC及MVC分层架构的综合应用,帮助开发者掌握企业级Web应用从数据库设计到前后端交互的完整开发流程。压缩包共246个文件,包含50个核心Java源文件(如Emploe、DeptDaoImpl等业务类)、28个JSP页面(实现员工信息增删改查、部门管理等前端视图)、102个编译后Class文件,以及SQL建库脚本、web.xml配置、项目元数据(.project/.classpath)和静态资源(PNG/JPG/GIF),整体仅2.45MB,轻量易部署。已有1086人学习下载,资源结构清晰:src目录按包组织业务逻辑,WebContent承载视图与配置,database提供可直接导入的MySQL脚本,配合完整DAO层实现与Manager/EmploeUpdate等典型控制器类,便于理解分层协作与CRUD实践。

1. 为什么一个“Java Web员工管理系统”压缩包,至今仍是校招面试官手里的压轴题?

你打开这个名为java web员工管理系统(源码+数据库).zip的压缩包时,看到的不只是几个.java文件和一个.sql脚本——它是一套被反复验证过的、能跑通「增删改查→权限控制→页面跳转→数据持久化」全链路的最小可行闭环。不是 Spring Boot 自动装配的黑匣子,不是 Vue + Axios 的前端胶水逻辑,而是用 Servlet + JSP + JDBC + MySQL 原生堆出来的、每行代码都暴露在请求生命周期里的老派扎实感。它解决的不是“高并发”或“分布式事务”,而是:当一个刚学完 JDBC 连接池、还没搞懂 Filter 和 Listener 区别的学生,如何在 3 天内把课本上的 DAO 模式真正焊进浏览器地址栏里?适合 Java 初学者做课程设计、转岗者补 Web 开发手感、面试前复盘 MVC 分层逻辑——尤其当你被问到“请手写一个用户登录校验流程”时,这个项目里LoginServlet.doPost()里那 12 行带request.getSession().setAttribute()的代码,就是你的后悔药。


2. 从解压到启动:用最原始的方式跑通这个系统(不依赖任何 IDE 插件)

这个压缩包的结构非常典型:src/下是 Java 源码,WebContent/(或webapp/)下是 JSP 和静态资源,db/或根目录下有.sql文件。它默认面向 Tomcat 7–9,不带 Maven 依赖声明,意味着你要亲手把 jar 包塞进WEB-INF/lib/。这不是缺陷,是刻意为之——它逼你直面 ClassLoader 加载顺序、web.xml的<servlet-mapping>如何劫持 URL、以及为什么jdbc:mysql://localhost:3306/emp?useSSL=false&serverTimezone=UTC里那个serverTimezone=UTC少了就会报java.sql.SQLException: The server time zone value 'XXX' is unrecognized。

2.1 解压与目录结构确认:先看清“战场”

解压后,务必确认以下三个核心目录是否存在且内容合理:

  • src/:应包含com.emp.dao/(DAO 层)、com.emp.servlet/(Servlet 层)、com.emp.entity/(实体类)等标准包结构;
  • WebContent/或webapp/:必须有index.jsp(首页)、login.jsp(登录页)、list.jsp(员工列表页),以及WEB-INF/web.xml;
  • db/或根目录:应有一个.sql文件,如emp_system.sql,内容以CREATE DATABASE IF NOT EXISTS emp_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;开头。

提示:如果src/下全是.class文件而非.java,说明你拿到的是编译后版本——立刻放弃,这不是你要的“源码”。真正的源码包,src/里每个.java文件都该有package com.emp.xxx;声明和清晰的import列表。

2.2 数据库初始化:别让ClassNotFoundException出现在建表之前

先启动 MySQL(5.7 或 8.0),然后执行 SQL 脚本。关键动作不是直接source emp_system.sql,而是先创建数据库并指定编码:

mysql -u root -p
CREATE DATABASE emp_system CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE emp_system; SOURCE /path/to/emp_system.sql;

为什么强调utf8mb4?因为员工姓名可能含 emoji 或生僻字(如“䶮”、“堃”),utf8在 MySQL 8.0+ 中已被标记为 deprecated,且实际只支持 3 字节 UTF-8;utf8mb4才是完整四字节支持。若建库时漏掉,后续插入中文会报Incorrect string value,而错误堆栈里根本不会提示编码问题,只会卡在PreparedStatement.executeUpdate()。

执行后,用SELECT * FROM employee;确认有测试数据(通常含id=1, name=张三, dept=技术部, salary=8000等)。这步验证通过,才代表数据库层已就绪。

2.3 Tomcat 部署:手动拷贝 WAR 包比 IDE 一键部署更能暴露问题

不要用 Eclipse/IntelliJ 的 “Run on Server” 功能——它会自动帮你生成context.xml、注入 JVM 参数、甚至悄悄替换web.xml版本号。我们要的是“裸机部署”:

  1. 将整个项目文件夹(如emp-system/)重命名为emp-system(不能含中文、空格、特殊符号);
  2. 拷贝到TOMCAT_HOME/webapps/目录下;
  3. 启动 Tomcat:TOMCAT_HOME/bin/startup.sh(Linux/Mac)或startup.bat(Windows);
  4. 观察TOMCAT_HOME/logs/catalina.out,等待出现INFO [main] org.apache.catalina.startup.HostConfig.deployDirectory Deploying web application directory。

逻辑说明:Tomcat 启动时会扫描webapps/,将文件夹名作为 Context Path。emp-system/部署后,访问地址就是http://localhost:8080/emp-system/。如果首页打不开,第一反应不是查 JSP 语法,而是看 catalina.out 里有没有SEVERE: Error filterStart或Caused by: java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver——这说明 MySQL 驱动没放对位置。

2.4 驱动 Jar 包落位:WEB-INF/lib/是唯一合法的“藏身之处”

下载mysql-connector-java-8.0.33.jar(适配 MySQL 8.0+)或mysql-connector-java-5.1.47.jar(适配 MySQL 5.7),必须放入emp-system/WEB-INF/lib/目录下,而不是TOMCAT_HOME/lib/。原因:Servlet 规范要求 Web 应用的类加载器(WebAppClassLoader)优先从WEB-INF/lib/加载,这是应用级隔离的基础。若放错位置,会出现两种诡异现象:

  • Class.forName("com.mysql.cj.jdbc.Driver")成功,但DriverManager.getConnection(...)报No suitable driver found(驱动类被加载,但 DriverManager 未注册);
  • 或者更隐蔽的:Connection对象能获取,但执行executeQuery("SELECT * FROM employee")时抛SQLException: Unknown system variable 'query_cache_size'(驱动版本与 MySQL 版本不匹配)。

验证方式:启动后访问http://localhost:8080/emp-system/login.jsp,输入默认账号(常见为admin/123456),若跳转到list.jsp且显示员工表格,则 JDBC 层打通。


3. 代码层拆解:看懂这个系统如何用原生 Servlet 实现 MVC 分层

这个项目的价值不在功能多炫酷,而在它用最朴素的方式实现了教科书级的分层:entity存数据结构,dao封装 SQL,servlet当控制器,jsp做视图。没有 Spring 的@Autowired注入,所有对象靠new创建;没有 MyBatis 的 XML 映射,SQL 字符串硬编码在EmployeeDao.java里。这种“笨功夫”,恰恰是理解 Web 容器工作原理的必经之路。

3.1 Entity 层:为什么private Integer id;比private int id;更安全?

查看src/com/emp/entity/Employee.java,你会发现主键字段声明为:

private Integer id; private String name; private String department; private Double salary;

而非int id、double salary。原因在于:数据库中id允许为 NULL(新增员工时主键由数据库自增,Java 对象初始值应为 null,而非 0)。若用基本类型,new Employee()后id=0,插入时会违反主键约束或覆盖已有 ID。同理,salary可能为空(如实习生未定薪),Double可为null,而double强制为0.0,导致业务语义丢失。

参数说明:Integer是int的包装类,支持null值;JDBC 的rs.getInt("id")若数据库值为 NULL,会返回0,但rs.getObject("id", Integer.class)则返回null——所以 DAO 层读取时必须用后者,否则空值会被误转为0。

3.2 DAO 层:PreparedStatement的占位符不是摆设,是防 SQL 注入的最后防线

打开src/com/emp/dao/EmployeeDao.java,查找insert(Employee emp)方法:

public boolean insert(Employee emp) { String sql = "INSERT INTO employee(name, department, salary) VALUES(?, ?, ?)"; try (Connection conn = JdbcUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, emp.getName()); ps.setString(2, emp.getDepartment()); ps.setDouble(3, emp.getSalary()); return ps.executeUpdate() > 0; } catch (SQLException e) { e.printStackTrace(); return false; } }

注意三点:

  • SQL 字符串里用?占位,而非"'" + emp.getName() + "'"拼接——这是防 SQL 注入的基石;
  • ps.setString(1, ...)的参数索引从 1 开始(不是 0),且类型严格匹配(name是VARCHAR,必须用setString);
  • try-with-resources自动关闭Connection和PreparedStatement,避免连接泄漏。

逻辑说明:若此处写成sql = "INSERT INTO employee(name,...) VALUES('" + emp.getName() + "',...)",当emp.getName()为张三'; DROP TABLE employee; --时,整条 SQL 变成INSERT ... VALUES('张三'; DROP TABLE employee; --',...),数据库会执行恶意语句。而PreparedStatement会将?参数作为纯数据传给 MySQL Server,Server 不会将其解析为 SQL 语法。

3.3 Servlet 层:request.getParameter()的返回值永远要判空,这是血泪经验

LoginServlet.java的doPost方法里,必然有:

String username = request.getParameter("username"); String password = request.getParameter("password"); if (username == null || username.trim().isEmpty() || password == null || password.trim().isEmpty()) { request.setAttribute("msg", "用户名或密码不能为空"); request.getRequestDispatcher("login.jsp").forward(request, response); return; }

为什么username.trim().isEmpty()不可省略?因为 HTML 表单提交时,若用户只敲空格再提交,getParameter返回" "(含空格字符串),== null为 false,但业务上它仍是无效输入。若不trim(),会导致username.equals("admin")为 false,登录失败却无提示。

参数说明:request.getParameter()在参数不存在时返回null,存在但为空字符串时返回"",存在且含空格时返回" "。三者必须分别处理,否则NullPointerException或逻辑漏洞必然出现。

3.4 JSP 层:EL 表达式${employee.name}背后是pageContext.findAttribute()的查找链

list.jsp中遍历员工列表:

<c:forEach items="${requestScope.employees}" var="emp"> <tr> <td>${emp.id}</td> <td>${emp.name}</td> <td>${emp.department}</td> <td>${emp.salary}</td> </tr> </c:forEach>

这里${emp.name}能工作,是因为:

  • Servlet 中request.setAttribute("employees", empList)将 List 放入 request 域;
  • JSP 容器在解析 EL 时,对emp变量调用pageContext.findAttribute("emp"),该方法按page → request → session → application顺序查找;
  • emp是<c:forEach>的var,属于 page 域,所以能直接访问其 getter 方法(getname()→getName())。

注意:若Employee类中getName()方法抛出异常(如 NPE),JSP 页面会显示???而非堆栈,调试困难。因此 DAO 层返回的Employee对象,所有字段必须确保非 null(如department为null时,emp.getDepartment()返回null,${emp.department}输出空字符串,安全)。


4. 避坑指南:那些让新手调试到凌晨三点的“玄学”错误

这个项目看似简单,但因技术栈古老、环境敏感、错误信息晦涩,极易陷入“改了十处,只有一处生效”的泥潭。以下是我在带实习生时记录的真实踩坑清单,每一条都附带现象 → 原因 → 解决的闭环。

4.1 现象:登录成功后跳转list.jsp,页面显示空白,查看源码只有<html><body></body></html>

原因:list.jsp中使用了 JSTL 标签(如<c:forEach>),但WEB-INF/lib/下缺少jstl.jar和standard.jar(JSTL 1.1)或jakarta.servlet.jsp.jstl-api-2.0.0.jar+tomcat-jasper-el-9.0.83.jar(JSTL 2.0)。JSP 编译时无法解析<c:前缀,直接忽略标签,导致循环体不渲染。
解决:下载对应 JSTL 版本 jar 包,放入WEB-INF/lib/;同时检查list.jsp顶部是否有<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>(JSTL 1.1)或<%@ taglib prefix="c" uri="http://xmlns.jcp.org/jsp/jstl/core" %>(JSTL 2.0),URI 必须与 jar 包匹配。

4.2 现象:添加新员工时,中文姓名存入数据库后变成??,但数据库和表字符集已设为utf8mb4

原因:JDBC URL 缺少characterEncoding=utf8mb4参数,或 Tomcat 的server.xml中Connector未配置URIEncoding="UTF-8"。前者导致 MySQL Server 接收字节流时按 latin1 解码,后者导致 GET 请求参数(如?name=张三)在 Tomcat 解码时乱码。
解决:

  • JDBC URL 改为:jdbc:mysql://localhost:3306/emp_system?useSSL=false&serverTimezone=UTC&characterEncoding=utf8mb4;
  • TOMCAT_HOME/conf/server.xml中<Connector port="8080" ... />添加属性:URIEncoding="UTF-8";
  • 重启 Tomcat。

4.3 现象:修改员工信息后,页面跳回list.jsp,但数据未更新,刷新后才显示新值

原因:浏览器缓存了list.jsp的响应。Servlet 跳转用的是response.sendRedirect("list.jsp")(302 重定向),但list.jsp本身未设置缓存控制头,浏览器可能复用旧缓存。
解决:在list.jsp顶部添加缓存禁用指令:

<% response.setHeader("Cache-Control", "no-cache, no-store, must-revalidate"); response.setHeader("Pragma", "no-cache"); response.setDateHeader("Expires", 0); %>

4.4 现象:点击“删除”按钮,URL 变为delete?id=5,但数据库无变化,控制台无任何日志

原因:delete.jsp或DeleteServlet.java中,request.getParameter("id")返回null,因为 HTML 表单的method="get"时,<a href="delete?id=${emp.id}">的链接参数名是id,但 Servlet 中request.getParameter("id")取不到——因为delete是 JSP 文件名,Tomcat 默认将其映射为delete.jsp,而delete.jsp里没写 Java 代码处理删除逻辑,它只是个静态页面。真实删除逻辑应在DeleteServlet.java中,且web.xml必须有<servlet-mapping>将/deleteURL 映射到该 Servlet。
解决:检查web.xml是否有:

<servlet> <servlet-name>DeleteServlet</servlet-name> <servlet-class>com.emp.servlet.DeleteServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>DeleteServlet</servlet-name> <url-pattern>/delete</url-pattern> </servlet-mapping>

确保链接是<a href="delete?id=${emp.id}">(指向/delete),而非<a href="delete.jsp?id=${emp.id}">(指向静态 JSP)。

4.5 现象:部署到 Tomcat 9 后,web.xml报错The content of element type "web-app" must match "(icon?,display-name?,description?,distributable?,context-param*,filter*,filter-mapping*,listener*,servlet*,servlet-mapping*,session-config?,mime-mapping*,welcome-file-list?,error-page*,taglib*,resource-env-ref*,security-constraint*,login-config?,security-role*,env-entry*,ejb-ref*,ejb-local-ref*)"

原因:web.xml的 DTD 或 schema 声明版本过低。Tomcat 9 要求web-app版本为4.0,而老项目常写2.3或2.4。
解决:将web.xml顶部声明替换为:

<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">

5. 进阶改造:把“教学项目”变成“可交付原型”的 3 个关键动作

跑通只是起点。这个系统真正的价值,在于它提供了一个极简但完整的骨架,让你能快速验证业务想法。我带团队做内部工具时,常以它为基线,三天内完成需求落地。以下是三个最实用、零成本的改造点,每个都附具体代码和效果说明。

5.1 给员工列表加搜索框:10 行代码实现模糊查询

原始list.jsp是全量展示,加搜索只需两步:
Step 1:在list.jsp表格上方加表单

<form action="list" method="post"> <input type="text" name="keyword" placeholder="按姓名或部门搜索" value="${param.keyword}"> <button type="submit">搜索</button> <a href="list">清空搜索</a> </form>

Step 2:修改ListServlet.java的doPost方法

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String keyword = request.getParameter("keyword"); List<Employee> employees; if (keyword != null && !keyword.trim().isEmpty()) { employees = new EmployeeDao().search(keyword.trim()); // 新增 search 方法 } else { employees = new EmployeeDao().findAll(); } request.setAttribute("employees", employees); request.getRequestDispatcher("list.jsp").forward(request, response); }

Step 3:在EmployeeDao.java中新增search方法

public List<Employee> search(String keyword) { String sql = "SELECT * FROM employee WHERE name LIKE ? OR department LIKE ?"; try (Connection conn = JdbcUtil.getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { String likeKeyword = "%" + keyword + "%"; ps.setString(1, likeKeyword); ps.setString(2, likeKeyword); ResultSet rs = ps.executeQuery(); List<Employee> list = new ArrayList<>(); while (rs.next()) { Employee emp = new Employee(); emp.setId(rs.getInt("id")); emp.setName(rs.getString("name")); emp.setDepartment(rs.getString("department")); emp.setSalary(rs.getDouble("salary")); list.add(emp); } return list; } catch (SQLException e) { e.printStackTrace(); return new ArrayList<>(); } }

效果:输入“技术”,返回所有name或department含“技术”的员工。无需改数据库、不引入新框架,纯 JDBC 原生实现。

5.2 用 Cookie 记住用户名:让用户下次打开登录页时自动填充

LoginServlet.java登录成功后,添加 Cookie 写入逻辑:

// 登录验证通过后 Cookie userCookie = new Cookie("username", username); userCookie.setMaxAge(60 * 60 * 24 * 7); // 7 天 userCookie.setPath("/"); // 作用域为整个应用 response.addCookie(userCookie);

然后在login.jsp的<input>中读取:

<input type="text" name="username" value="${cookie.username.value}" />

注意:cookie.username.value是 EL 表达式,cookie是隐式对象,username是 Cookie 名。若用户清除 Cookie,value为 null,输入框为空,安全无副作用。

5.3 导出 Excel:用 Apache POI 一行代码生成可下载文件

在ListServlet.java中新增导出逻辑(需引入poi-5.2.4.jar):

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if ("export".equals(request.getParameter("action"))) { List<Employee> employees = new EmployeeDao().findAll(); HSSFWorkbook workbook = new HSSFWorkbook(); HSSFSheet sheet = workbook.createSheet("员工列表"); String[] headers = {"ID", "姓名", "部门", "薪资"}; HSSFRow headerRow = sheet.createRow(0); for (int i = 0; i < headers.length; i++) { headerRow.createCell(i).setCellValue(headers[i]); } int rowNum = 1; for (Employee emp : employees) { HSSFRow row = sheet.createRow(rowNum++); row.createCell(0).setCellValue(emp.getId()); row.createCell(1).setCellValue(emp.getName()); row.createCell(2).setCellValue(emp.getDepartment()); row.createCell(3).setCellValue(emp.getSalary()); } response.setContentType("application/vnd.ms-excel"); response.setHeader("Content-Disposition", "attachment; filename=employees.xls"); workbook.write(response.getOutputStream()); return; } // ... 原有 list 逻辑 }

在list.jsp加导出链接:

<a href="list?action=export">导出 Excel</a>

效果:点击即下载employees.xls,Excel 打开后格式规整。POI 4.x+ 支持.xlsx,但老项目用.xls兼容性更好,无需额外依赖。

我习惯在接手一个新项目时,先用这个员工管理系统搭起本地开发环境,再逐步替换为真实业务模块——它像一把瑞士军刀,不锋利,但每个刃口都磨得恰到好处。希望帮到你。

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

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/7 12:38:36

DeepSeek开源昇腾组件:国产NPU大模型部署实战指南

1. 这次开源到底放了什么料 DeepSeek 这个名字在过去一年多里几乎成了大模型圈的流量密码&#xff0c;从 V3 到 R1&#xff0c;每次动作都能让技术社区热闹好一阵。但这次不太一样——他们开源的不是模型权重&#xff0c;而是 昇腾基础组件 。说白了&#xff0c;就是把自家模…

作者头像 李华
网站建设 2026/10/7 12:37:47

基于SpringBoot2+Vue3的课表管理系统:从数据库设计到部署排障

课表管理系统这题我太熟了&#xff0c;大学校园里天天见的东西&#xff0c;但真把它做成一个完整的、可落地的前后端分离项目&#xff0c;还是有不少门道的。最近刚好梳理了一个基于SpringBoot2 Vue3 MyBatis-Plus MySQL8.0的西安工商学院课表管理系统源码&#xff0c;带完整…

作者头像 李华
网站建设 2026/10/7 12:37:47

500张果树水果图像分类实战:迁移学习与ResNet18微调

简介&#xff1a;7种常见长在果树上的水果图像分类数据集已按深度学习项目标准整理完毕&#xff0c;主要面向计算机视觉初学者和图像分类任务实践者&#xff0c;可直接作为分类网络输入。数据包含草莓、甜瓜、橙子、苹果等7个类别&#xff0c;约500张标注图片&#xff0c;利用配…

作者头像 李华
网站建设 2026/10/7 12:36:18

PADS VX实战:DDR等长布线蛇形走线详解与避坑指南

做了几年PCB Layout&#xff0c;几乎每个高速数字项目都绕不开DDR的等长布线。PADS VX的蛇形走线功能用起来不难&#xff0c;真正难的是理解为什么要这么绕、绕的时候要注意哪些细节&#xff0c;以及绕完之后怎么确认自己绕对了。这篇文章把我在实际项目里调DDR3/DDR4的等长经验…

作者头像 李华
网站建设 2026/10/7 12:36:01

老年心理实训室怎么建才不闲置?教学场景倒推功能与设备配置

大多数学校建老年心理实训室&#xff0c;思路都差不多&#xff1a;划一间教室、买一批设备、贴一张牌子&#xff0c;然后等验收。我这些年参观过不少这类实训室&#xff0c;也帮几个院校做过建设顾问&#xff0c;说实话&#xff0c;真正能把空间用起来的并不多。最典型的一种状…

作者头像 李华