拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

JSP+Servlet+Tomcat+MySQL人事系统实战搭建指南

JSP+Servlet+Tomcat+MySQL人事系统实战搭建指南

简介:这是一套基于JSP+Servlet+Tomcat+MySQL实现的完整人事管理系统源码,面向计算机、电子信息、软件工程等专业的本科生与初学者,适用于课程设计、期末大作业及毕业设计参考。系统涵盖员工信息管理、部门维护、考勤统计、权限控制等核心模块,采用经典B/S架构与MVC分层设计,便于理解Web开发全流程。压缩包共2000个文件,主体为49个Java后端逻辑类、42个JSP页面、1294个JS交互脚本及404个CSS样式文件,辅以SQL建表语句、配置文件与说明文档,整体容量73.74MB,结构清晰、注释较全。目前已有205人学习下载,读者可直接部署运行,快速掌握Servlet请求处理、JDBC数据库操作、JSP页面渲染及AdminLTE前端框架集成等关键技术点,并基于现有代码拓展功能或调试排错。

1. 这不是“过时技术堆砌”,而是Java Web工程能力的实体切片:用JSP+Servlet+Tomcat+MySQL跑通一个真实可调、可 debug、可交付的人事管理系统

你在网上搜“JSP人事管理系统源码”,大概率会撞上一堆压缩包——解压后文件夹里全是.jsp和WEB-INF/web.xml,IDEA 里右键 Run As → “No artifacts configured”,Tomcat 启动日志刷屏ClassNotFoundException: com.mysql.cj.jdbc.Driver,浏览器打开http://localhost:8080/login.jsp却报 404 或 500。这不是代码写得烂,而是这套组合没被当成一个完整工程生命周期来对待:它不只是一堆能跑起来的页面,更是 Java Web 工程师必须亲手缝合的四块硬骨头——JSP 负责视图层的动态拼接与表单渲染(比如员工头像居中显示、入职日期按年月日分列输入),Servlet 承担控制器职责(处理增删改查请求、校验身份证号格式、拦截未登录跳转),Tomcat 是容器底座(需明确指定 JDK 版本、配置 context path、启用 JNDI 数据源),MySQL 则是数据落盘的最终归宿(字段类型要匹配业务语义:hire_date DATE不能存成VARCHAR(10),salary DECIMAL(10,2)不能用FLOAT)。这套技术栈至今仍是高校课程设计、中小政企内部系统、外包交付项目的高频选择——不是因为它“新”,而是因为它的每一步都暴露在你眼皮底下:没有框架黑匣子,所有请求流转路径清晰可见,出错时 stack trace 直接指向某行 JSP 的<% %>或某个 Servlet 的doPost()方法。如果你正卡在“部署不成功”“数据库连不上”“中文乱码”“表单提交后跳转失败”这些具体问题里,这篇笔记就是为你写的实战切片。


2. 从零搭起运行骨架:Tomcat + MySQL 环境对齐与项目结构初始化

2.1 Tomcat 安装与 JDK 版本强绑定:为什么必须用 JDK 8 而不是 JDK 17?

Tomcat 9 及以下版本(本项目常见为 Tomcat 8.5/9.0)原生不支持 JDK 11+ 的模块化特性。当你用 IDEA 2023+ 新建项目默认选 JDK 17,再把tomcat-8.5.99.zip解压后配置到 Server,启动时必然报错:

SEVERE [main] org.apache.catalina.startup.Catalina.start The required Server component failed to start so Tomcat is unable to start. Caused by: java.lang.UnsupportedClassVersionError: org/apache/catalina/startup/Bootstrap has been compiled by a more recent version of the Java Runtime (class file version 61.0), this version of the Java Runtime only recognizes class file versions up to 52.0

这是典型的 JDK 版本错配:Tomcat 8.5 编译于 JDK 8(class file version 52.0),而 JDK 17 编译出的 class 是 version 61.0。
解决方案只有两个:
①降级 JDK:下载并安装jdk-8u202-windows-x64.exe(Oracle 官方归档版,或 OpenJDK 8u362),在 IDEA 中 Project Structure → Project SDK 选该 JDK;
②升级 Tomcat:改用apache-tomcat-10.1.26.zip(支持 JDK 17+),但注意——Tomcat 10+ 默认使用 Jakarta EE 9+ 命名空间,所有 import 必须从javax.servlet.*改为jakarta.servlet.*,否则编译直接失败。

提示:本项目源码中web.xml头部声明为<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee",且所有 Servlet 类继承HttpServlet,说明它基于 Java EE 7/8 规范,必须搭配 JDK 8 + Tomcat 8.5/9.0。强行混用高版本 JDK 会导致类加载器找不到javax.servlet.http.HttpServlet。

2.2 MySQL 5.7 安装与字符集预设:为什么utf8mb4是唯一安全选项?

很多教程教你在my.ini里写character-set-server=utf8,结果插入 emoji 表情(如 👍)或生僻汉字(如 “龘”)时直接报错Incorrect string value。这是因为 MySQL 的utf8实际是utf8mb3,最多支持 3 字节编码,而 emoji 需要 4 字节。
正确做法是全程启用utf8mb4:

# my.ini 文件(Windows 下位于 MySQL 安装目录根目录) [client] default-character-set = utf8mb4 [mysql] default-character-set = utf8mb4 [mysqld] character-set-server = utf8mb4 collation-server = utf8mb4_unicode_ci init_connect='SET NAMES utf8mb4' skip-character-set-client-handshake = FALSE

修改后重启 MySQL 服务:

net stop mysql net start mysql

验证是否生效:

SHOW VARIABLES LIKE 'character_set%'; SHOW VARIABLES LIKE 'collation%';

输出中character_set_server和collation_server必须为utf8mb4和utf8mb4_unicode_ci。
关键点:仅改服务器配置不够!JDBC 连接 URL 必须显式声明编码:

// 正确:强制使用 utf8mb4 String url = "jdbc:mysql://localhost:3306/hrms?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=GMT%2B8";

漏掉characterEncoding=utf8mb4,即使数据库设对了,Java 程序读写仍会乱码。

2.3 项目结构标准化:为什么src/main/java不是必须,但WEB-INF/classes和lib必须存在?

传统 Java Web 项目(非 Maven)的目录结构是硬性约定,Tomcat 依赖它定位类和资源:

hrms/ ├── index.jsp ├── login.jsp ├── WEB-INF/ │ ├── web.xml ← 部署描述符,定义 Servlet 映射 │ ├── classes/ ← 编译后的 .class 文件存放处(手动复制或 IDE 自动生成) │ └── lib/ ← 所有 jar 包:servlet-api.jar、mysql-connector-java-5.1.47.jar 等 └── static/ └── css/ └── style.css

特别注意:

  • servlet-api.jar不能打包进WEB-INF/lib!它是 Tomcat 自带的 API,重复引入会导致java.lang.LinkageError。IDEA 中需将该 jar 的 Scope 设为Provided;
  • mysql-connector-java-5.1.47.jar(适配 MySQL 5.7)必须放在WEB-INF/lib下,且版本要匹配——MySQL 5.7 推荐用5.1.x,MySQL 8.0+ 才用8.0.x;
  • WEB-INF/classes是类加载器的根路径,你的com.hrms.servlet.LoginServlet.class必须在此目录下按包路径展开:WEB-INF/classes/com/hrms/servlet/LoginServlet.class。

3. 核心流程打通:从登录请求到员工列表展示的端到端链路实现

3.1 登录功能闭环:JSP 表单 → Servlet 处理 → MySQL 查询 → Session 跳转

JSP 层:表单提交与错误提示

login.jsp中关键代码:

<!-- login.jsp --> <form action="LoginServlet" method="post"> <input type="text" name="username" placeholder="用户名" required> <input type="password" name="password" placeholder="密码" required> <button type="submit">登录</button> </form> <!-- 显示登录失败提示(来自 Servlet 的 request.setAttribute) --> <% if (request.getAttribute("error") != null) { %> <div style="color:red;"><%= request.getAttribute("error") %></div> <% } %>

注意:action="LoginServlet"对应web.xml中<servlet-mapping>的url-pattern,不是物理路径。

Servlet 层:请求解析、数据库校验、Session 绑定
// src/com/hrms/servlet/LoginServlet.java @WebServlet("/LoginServlet") public class LoginServlet extends HttpServlet { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String username = request.getParameter("username"); String password = request.getParameter("password"); // 1. 简单空值校验 if (username == null || username.trim().isEmpty() || password == null || password.trim().isEmpty()) { request.setAttribute("error", "用户名或密码不能为空"); request.getRequestDispatcher("login.jsp").forward(request, response); return; } // 2. 查询数据库(使用 JDBCUtils 封装的 Connection) String sql = "SELECT id, name, role FROM employee WHERE username = ? AND password = ?"; try (Connection conn = JDBCUtils.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setString(1, username); pstmt.setString(2, password); // 生产环境必须加密存储!此处仅为演示 ResultSet rs = pstmt.executeQuery(); if (rs.next()) { // 3. 登录成功:存入 Session 并重定向 HttpSession session = request.getSession(); session.setAttribute("user_id", rs.getInt("id")); session.setAttribute("user_name", rs.getString("name")); session.setAttribute("user_role", rs.getString("role")); response.sendRedirect("main.jsp"); // 重定向避免重复提交 } else { request.setAttribute("error", "用户名或密码错误"); request.getRequestDispatcher("login.jsp").forward(request, response); } } catch (SQLException e) { e.printStackTrace(); request.setAttribute("error", "系统繁忙,请稍后再试"); request.getRequestDispatcher("login.jsp").forward(request, response); } } }

逻辑说明:

  • 使用response.sendRedirect("main.jsp")而非request.getRequestDispatcher().forward(),是因为登录成功后应跳转到新页面,避免用户刷新导致表单重复提交;
  • JDBCUtils.getConnection()是封装好的工具类,内部通过DriverManager.getConnection(url, user, pwd)获取连接,确保每次请求都用新连接(不推荐连接池,此处为简化);
  • 密码明文存储仅用于教学演示,实际必须用 BCrypt 加密存储、比对。
web.xml 配置映射(Tomcat 8.5+ 可省略,但保留更兼容)
<!-- WEB-INF/web.xml --> <servlet> <servlet-name>LoginServlet</servlet-name> <servlet-class>com.hrms.servlet.LoginServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>LoginServlet</servlet-name> <url-pattern>/LoginServlet</url-pattern> </servlet-mapping>

3.2 员工列表页:JSP EL 表达式 + JSTL 标签库渲染动态表格

list.jsp中展示员工信息,避免 scriptlet(<% %>)硬编码:

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>员工列表</title> </head> <body> <h2>员工管理</h2> <table border="1"> <tr> <th>ID</th> <th>姓名</th> <th>部门</th> <th>职位</th> <th>入职日期</th> <th>操作</th> </tr> <c:forEach items="${employeeList}" var="emp"> <tr> <td>${emp.id}</td> <td>${emp.name}</td> <td>${emp.department}</td> <td>${emp.position}</td> <td>${emp.hireDate}</td> <!-- hireDate 是 java.util.Date,JSP 自动格式化 --> <td> <a href="EditServlet?id=${emp.id}">编辑</a> | <a href="DeleteServlet?id=${emp.id}" onclick="return confirm('确定删除?')">删除</a> </td> </tr> </c:forEach> </table> </body> </html>

关键点:

  • 必须引入 JSTL 库:jstl-1.2.jar和standard-1.1.2.jar放入WEB-INF/lib;
  • ${employeeList}来自 Servlet 的request.setAttribute("employeeList", list);
  • onclick="return confirm(...)"是前端简单确认,防止误删。
查询 Servlet:封装 Employee 实体类与 DAO 模式
// src/com/hrms/dao/EmployeeDAO.java public class EmployeeDAO { public List<Employee> findAll() throws SQLException { String sql = "SELECT id, name, department, position, hire_date FROM employee ORDER BY id"; List<Employee> list = new ArrayList<>(); try (Connection conn = JDBCUtils.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql); ResultSet rs = pstmt.executeQuery()) { while (rs.next()) { Employee emp = new Employee(); emp.setId(rs.getInt("id")); emp.setName(rs.getString("name")); emp.setDepartment(rs.getString("department")); emp.setPosition(rs.getString("position")); emp.setHireDate(rs.getDate("hire_date")); // 自动转为 java.util.Date list.add(emp); } } return list; } } // src/com/hrms/servlet/ListServlet.java @WebServlet("/ListServlet") public class ListServlet extends HttpServlet { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { try { EmployeeDAO dao = new EmployeeDAO(); List<Employee> list = dao.findAll(); request.setAttribute("employeeList", list); request.getRequestDispatcher("list.jsp").forward(request, response); } catch (SQLException e) { e.printStackTrace(); request.setAttribute("error", "查询失败"); request.getRequestDispatcher("error.jsp").forward(request, response); } } }

4. 避坑指南:五个让新手卡死超过 2 小时的真实问题与血泪解法

4.1 现象:Tomcat 启动成功,但访问http://localhost:8080/login.jsp返回 404

原因:项目未正确部署到 Tomcat 的webapps目录,或上下文路径(Context Path)配置错误。
解决:
① 检查 IDEA 中 Tomcat 配置:Run → Edit Configurations → Deployment → 点击+→ Artifact → 选择你的项目名称(如hrms:war exploded)→ Application context 设为/(空则为根路径);
② 手动验证:启动 Tomcat 后,检查apache-tomcat-8.5.99/webapps/目录下是否存在hrms文件夹(对应项目名),且其内含login.jsp和WEB-INF;
③ 若 context path 设为/hrms,则必须访问http://localhost:8080/hrms/login.jsp。

4.2 现象:登录时控制台报java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver

原因:MySQL 8.0+ 驱动类名已从com.mysql.jdbc.Driver改为com.mysql.cj.jdbc.Driver,但项目仍用旧版驱动(如mysql-connector-java-5.1.47.jar)且代码中未显式注册驱动。
解决:
①确认驱动版本:若用 MySQL 5.7,保留mysql-connector-java-5.1.47.jar,代码中无需Class.forName()(JDBC 4.0+ 自动加载);
②若误用 MySQL 8.0+ 驱动:下载mysql-connector-java-8.0.33.jar,并在JDBCUtils.getConnection()中添加:

static { try { Class.forName("com.mysql.cj.jdbc.Driver"); // 显式加载 } catch (ClassNotFoundException e) { throw new RuntimeException(e); } }

③ 检查WEB-INF/lib是否真包含该 jar(IDEA 中右键项目 → Open Module Settings → Libraries 查看)。

4.3 现象:中文姓名存入数据库后变成??,或 JSP 页面显示方框

原因:三处编码未统一:MySQL 服务端、JDBC 连接 URL、JSP 页面声明。
解决:
① MySQL 服务端:my.ini中character-set-server=utf8mb4(见 2.2 节);
② JDBC URL:必须含characterEncoding=utf8mb4(jdbc:mysql://...?characterEncoding=utf8mb4);
③ JSP 页面顶部:<%@ page contentType="text/html;charset=UTF-8" %>;
④ Tomcatconf/server.xml中 Connector 添加URIEncoding="UTF-8":

<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" URIEncoding="UTF-8" />

4.4 现象:点击“编辑”链接跳转到EditServlet?id=1,但页面空白,控制台无报错

原因:EditServlet中未设置Content-Type,且未调用response.getWriter()输出内容,导致浏览器等待响应超时。
解决:
① 在doGet()开头添加:

response.setContentType("text/html;charset=UTF-8"); PrintWriter out = response.getWriter();

② 或更规范的做法:用request.getRequestDispatcher("edit.jsp").forward(request, response)转发到编辑页面,并在edit.jsp中通过request.getParameter("id")获取 ID 查询数据填充表单。

4.5 现象:删除员工后刷新列表页,被删员工仍显示

原因:浏览器缓存了上一次的list.jsp响应,未重新请求 Servlet。
解决:
在list.jsp<head>中添加防缓存头:

<meta http-equiv="Pragma" content="no-cache"> <meta http-equiv="Cache-Control" content="no-cache"> <meta http-equiv="Expires" content="0">

或在ListServlet的doGet()中添加:

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

5. 进阶落地技巧:让这套老技术栈真正扛住小团队日常运维

5.1 数据库连接池化:用 DBCP2 替代每次新建 Connection

手动DriverManager.getConnection()在高并发下会创建大量 TCP 连接,耗尽 MySQL 的max_connections(默认 151)。换成连接池能复用连接、自动回收空闲连接。
步骤:
① 下载commons-dbcp2-2.9.0.jar和commons-pool2-2.12.0.jar,放入WEB-INF/lib;
② 创建DBCPDataSource.java:

public class DBCPDataSource { private static BasicDataSource dataSource; static { dataSource = new BasicDataSource(); dataSource.setUrl("jdbc:mysql://localhost:3306/hrms?useUnicode=true&characterEncoding=utf8mb4&serverTimezone=GMT%2B8"); dataSource.setUsername("root"); dataSource.setPassword("123456"); dataSource.setInitialSize(5); // 初始连接数 dataSource.setMaxTotal(20); // 最大连接数 dataSource.setMaxIdle(10); // 最大空闲连接 dataSource.setMinIdle(5); // 最小空闲连接 dataSource.setTestOnBorrow(true); // 借出前检测连接有效性 dataSource.setValidationQuery("SELECT 1"); } public static Connection getConnection() throws SQLException { return dataSource.getConnection(); } }

③ 将所有JDBCUtils.getConnection()替换为DBCPDataSource.getConnection()。
效果:100 并发请求下,MySQLshow processlist显示活跃连接稳定在 15~20 个,而非飙升至 100+。

5.2 JSP 页面图片坐标精确定位:CSS Grid +object-fit解决头像居中裁剪

人事系统常需在员工列表中显示圆形头像,且要求不同尺寸图片(如 200x300 或 100x50)都能居中显示、不拉伸。
方案:放弃<img>的width/height硬缩放,改用 CSS Grid 容器 +object-fit: cover:

<!-- list.jsp 中员工头像单元格 --> <td> <div style="width:60px; height:60px; display:grid; place-items:center; overflow:hidden; border-radius:50%;"> <img src="images/${emp.photo}" style="width:100%; height:100%; object-fit:cover;" alt="${emp.name}"> </div> </td>

原理:

  • display:grid; place-items:center让图片在 60x60 容器内绝对居中;
  • overflow:hidden配合border-radius:50%实现圆形裁剪;
  • object-fit:cover保持图片宽高比,等比缩放后填满容器,超出部分隐藏——这才是真正的“头像居中显示”。

5.3 Servlet 请求参数批量注入:用 Apache Commons BeanUtils 减少样板代码

传统 Servlet 中获取 10 个表单字段要写 10 行request.getParameter(),极易出错。用BeanUtils.populate()一行搞定:

// edit.jsp 提交表单 <form action="UpdateServlet" method="post"> <input type="hidden" name="id" value="${emp.id}"> <input type="text" name="name" value="${emp.name}"> <input type="text" name="department" value="${emp.department}"> <!-- ... 其他字段 --> </form>
// UpdateServlet.java protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Employee emp = new Employee(); try { // 自动将 request 参数映射到 emp 对象属性(要求属性名与表单 name 一致) BeanUtils.populate(emp, request.getParameterMap()); // 此时 emp.id, emp.name, emp.department 已赋值 new EmployeeDAO().update(emp); response.sendRedirect("ListServlet"); } catch (Exception e) { e.printStackTrace(); request.setAttribute("error", "更新失败"); request.getRequestDispatcher("error.jsp").forward(request, response); } }

前提:引入commons-beanutils-1.9.4.jar和commons-logging-1.2.jar,且Employee类必须有标准 getter/setter。

5.4 日志统一管理:用 Log4j2 替代System.out.println

System.out输出无法分级、无法按日滚动、无法输出到文件,线上排查问题时等于盲人摸象。Log4j2 是轻量级替代:
①log4j2.xml放入WEB-INF/classes:

<?xml version="1.0" encoding="UTF-8"?> <Configuration status="WARN"> <Appenders> <File name="FileAppender" fileName="logs/hrms.log"> <PatternLayout pattern="%d{HH:mm:ss.SSS} [%t] %-5level %logger{36} - %msg%n"/> </File> </Appenders> <Loggers> <Root level="info"> <AppenderRef ref="FileAppender"/> </Root> </Loggers> </Configuration>

② Servlet 中使用:

import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; public class LoginServlet extends HttpServlet { private static final Logger logger = LogManager.getLogger(LoginServlet.class); protected void doPost(...) { logger.info("用户 {} 尝试登录", username); // ... 业务逻辑 if (success) { logger.info("用户 {} 登录成功", username); } else { logger.warn("用户 {} 登录失败,密码错误", username); } } }

效果:所有日志自动写入logs/hrms.log,按天滚动,grep 查找特定用户行为秒级定位。

我带实习生搭这套环境时,最常被问:“老师,现在都用 Spring Boot 了,学这个还有用吗?” 我的回答是:Spring Boot 的@RestController本质是 Servlet 的封装,application.properties里的spring.datasource.url就是 JDBC URL 的包装,Thymeleaf模板和 JSP 的 EL 表达式逻辑一模一样。当你亲手把web.xml配好、把WEB-INF/lib塞满、把mysql-connector-java.jar的版本和 MySQL 服务端对齐、把CharacterEncoding三处写对,你就拿到了 Java Web 的底层 ABI——之后学任何框架,都是在这块基石上盖楼,而不是在云里搭积木。这套组合不是古董,它是工程师的肌肉记忆。希望帮到你。

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

返回列表