news 2026/8/6 5:25:46

Java Web开发实战:基于Servlet/JSP构建人员信息管理系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java Web开发实战:基于Servlet/JSP构建人员信息管理系统

1. 从零到一:为什么需要一个人员信息管理系统?

在任何一个超过十个人的团队或组织里,你迟早会面临一个头疼的问题:人员信息怎么管?一开始,可能就是一个Excel表格,大家往里填姓名、电话、部门。随着人越来越多,表格版本开始混乱,A同事改了电话没同步,B同事离职了信息还在表里,领导要个统计报表,你得吭哧吭哧筛选半天。更别提权限了,难道把包含所有人身份证号的表格发到全公司大群里?这种基于文件共享的管理方式,在团队规模小、变动不频繁时还能凑合,一旦业务发展起来,立刻就成了效率的瓶颈和信息安全的隐患。

这就是为什么我们需要一个人员信息管理系统。它本质上是一个将人员数据的增、删、改、查(CRUD)操作,从本地文件搬到网络浏览器中的Web应用。它的核心价值在于集中化、规范化、流程化和安全化。所有数据存在一个统一的数据库里,版本唯一;通过网页表单进行数据录入和修改,确保了格式规范;可以设计审批流程,比如入职信息录入后需要上级确认;更重要的是,可以通过登录账号和角色来精确控制谁能看、谁能改哪些数据。

我最近用Idea 2024配合Tomcat,完整地走了一遍从环境搭建、数据库设计、后端开发、前端页面到最终部署上线的全流程,并把所有代码开源了。这个项目麻雀虽小,五脏俱全,涵盖了单体Web项目最核心的技术栈:Java Web、Servlet、JSP、JDBC、MySQL、HTML/CSS/JavaScript以及Tomcat服务器。无论你是想学习Java Web开发的全貌,还是需要一个基础框架进行二次开发,这个项目都能提供一个清晰的、可运行的参考。接下来,我就把这个项目的构建思路、关键技术选型、具体实现步骤以及我踩过的那些“坑”,毫无保留地分享给你。

2. 技术选型与环境搭建:为什么是这套组合拳?

在启动一个项目前,明确技术栈是第一步。我的选择是:Java + Servlet/JSP + MySQL + Tomcat,开发工具是IntelliJ IDEA 2024。这套组合可能不是最“潮”的(现在更流行Spring Boot + MyBatis/Spring Data JPA + Vue/React),但它绝对是学习Java Web最经典、最透彻的路径。理解了这套底层机制,日后学习任何高级框架都会事半功倍,因为你知道它们到底在帮你简化什么。

2.1 核心组件解析与选型理由

后端语言:Java理由很直接:生态成熟、稳定、跨平台。企业级应用的后端,Java依然是中流砥柱。我们使用纯Java编写业务逻辑和数据访问代码,不依赖任何重型框架,以便看清本质。

Web层:Servlet & JSP这是Java EE(现Jakarta EE)处理Web请求的标准。Servlet是运行在服务器端的Java程序,用于接收和响应HTTP请求;JSP(JavaServer Pages)则可以方便地在HTML中嵌入Java代码,用于动态生成页面。选择它们是为了掌握Web请求从浏览器到服务器再返回的完整生命周期。

数据库:MySQL关系型数据库的代表,免费、开源、应用广泛。人员信息这类结构化数据,用关系型数据库来管理是天然契合的。我们将使用标准的SQL语句和JDBC进行交互。

服务器:Apache Tomcat一个轻量级的、开源的Java Web应用服务器,专门用于运行Servlet和JSP。它配置简单,是学习和开发阶段的不二之选。

开发工具:IntelliJ IDEA 2024 UltimateIDEA在Java开发领域的体验是顶级的。2024版本对Web开发的支持更加智能,特别是创建和配置Web项目、管理依赖、以及集成Tomcat进行热部署,都非常流畅。社区版功能也足够,但Ultimate版对Web和数据库的支持更完善。

2.2 IDEA 2024中创建Web项目的详细步骤

这里是最容易出错的起点。很多教程基于旧版本IDEA,界面和选项已经变化。以下是2024版本中的正确姿势:

  1. 新建项目:打开IDEA,点击“New Project”。在左侧列表中,选择“Java Enterprise”。这是关键,不要选成普通的“Java”项目。
  2. 配置项目模板:在右侧,你会看到“Project Template”部分。确保勾选了“Web Application”。这会自动为你创建标准的Web目录结构(WEB-INF, web.xml等)。下方的“Application Server”需要你提前配置好Tomcat,如果还没配置,点击“New...”然后找到你本地Tomcat的安装目录。
  3. 选择技术:在“Additional Libraries and Frameworks”部分,我们暂时不添加任何框架(如Spring)。确保“Java EE”版本选择了合适的(如Jakarta EE 9Java EE 8),这决定了Servlet等API的版本。点击“Create”。
  4. 项目结构验证:创建完成后,查看项目目录。你应该能看到一个src目录(放Java代码),一个web目录(放Web资源)。web目录下应有WEB-INF文件夹(内含web.xml)和index.jsp文件。这个结构是Tomcat能识别的标准结构。

注意:如果你在创建时漏选了“Web Application”,项目将是一个普通的Java项目,没有Web目录结构。补救办法是:在项目上右键 -> Add Framework Support -> 勾选“Java EE”下的“Web Application”。但不如一开始就选对来得干净。

2.3 本地Tomcat服务器的配置与集成

项目创建好后,需要让IDEA知道如何运行它。

  1. 添加运行配置:点击IDEA右上角运行按钮旁边的下拉菜单,选择“Edit Configurations...”。
  2. 新增Tomcat配置:点击“+”号,选择“Tomcat Server” -> “Local”。如果列表里没有,说明你之前没在IDEA中配置过Tomcat,需要点击“Configure...”来添加本地Tomcat的安装路径。
  3. 部署项目:在配置界面,切换到“Deployment”标签页。点击“+”号,选择“Artifact”。你会看到一个以你的项目名命名的war包或 exploded(展开)目录。强烈推荐选择带“exploded”后缀的选项,这代表是展开的目录,支持热部署(修改JSP或静态资源后,无需重启Tomcat即可生效)。
  4. 配置上下文路径:在“Application context”栏,你可以设置访问路径,比如改成/personnel,那么应用启动后的访问地址就是http://localhost:8080/personnel。默认为/,即根路径。
  5. 启动测试:点击“OK”保存配置,然后点击绿色三角运行按钮。IDEA会自动启动Tomcat并部署你的项目。控制台无报错,并最终看到类似“Server startup in [xxxx] milliseconds”的日志后,打开浏览器访问http://localhost:8080/你的上下文路径,应该能看到默认的index.jsp页面内容。

至此,你的开发环境就绪,一个纯净的Java Web项目骨架已经搭建完成。接下来,我们要开始赋予它灵魂——数据库和业务逻辑。

3. 数据库设计与JDBC连接:如何规划你的数据“仓库”?

任何管理系统的核心都是数据。在设计数据库时,我的原则是:满足当前需求,预留扩展空间,遵循设计范式,但不过度设计。对于人员信息管理系统,我们从一个最核心的employee表开始。

3.1 数据表结构设计

我们先设计一个包含基础信息的员工表。SQL脚本如下:

CREATE DATABASE IF NOT EXISTS `personnel_db` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; USE `personnel_db`; CREATE TABLE `employee` ( `id` INT PRIMARY KEY AUTO_INCREMENT COMMENT '员工ID,主键', `emp_id` VARCHAR(20) NOT NULL UNIQUE COMMENT '员工工号', `name` VARCHAR(50) NOT NULL COMMENT '姓名', `gender` TINYINT DEFAULT NULL COMMENT '性别:0-女,1-男', `age` INT DEFAULT NULL COMMENT '年龄', `department` VARCHAR(100) DEFAULT NULL COMMENT '部门', `position` VARCHAR(100) DEFAULT NULL COMMENT '职位', `phone` VARCHAR(20) DEFAULT NULL COMMENT '电话', `email` VARCHAR(100) DEFAULT NULL COMMENT '邮箱', `hire_date` DATE DEFAULT NULL COMMENT '入职日期', `create_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '记录创建时间', `update_time` TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '记录更新时间' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工信息表';

设计思路解析:

  • id:代理主键。使用自增整数,与业务无关,性能好,且能保证唯一性。emp_id(工号)虽然业务唯一,但可能变更,不宜作主键。
  • emp_id:业务唯一标识。添加UNIQUE约束,确保工号不重复。
  • 字段类型VARCHAR长度根据实际情况估算,utf8mb4字符集支持所有Unicode字符(包括Emoji)。genderTINYINT而非VARCHAR(1),便于程序处理(0/1)。
  • 日期时间hire_dateDATE类型。create_timeupdate_time是审计字段,非常有用,可以追踪数据变化。ON UPDATE CURRENT_TIMESTAMP能让update_time在记录修改时自动更新。
  • 注释:养成写COMMENT的习惯,这对后期维护至关重要。

3.2 使用JDBC建立稳健的数据库连接

在Java中连接MySQL,我们使用最原始的JDBC。为了代码清晰和可维护,我们采用DAO(Data Access Object)模式,将数据库操作封装起来。

首先,需要在项目中引入MySQL驱动。如果你使用Maven,在pom.xml中添加依赖。如果是普通项目,则将mysql-connector-java-8.x.x.jar文件放入WEB-INF/lib目录下。

接着,创建一个数据库连接工具类DBUtil.java

package com.yourcompany.personnel.util; import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class DBUtil { private static final String URL = "jdbc:mysql://localhost:3306/personnel_db?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai"; private static final String USER = "your_username"; private static final String PASSWORD = "your_password"; static { try { // 加载驱动,MySQL 8+ 需要显式加载 Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { e.printStackTrace(); throw new RuntimeException("Failed to load MySQL JDBC driver.", e); } } public static Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } // 关闭连接的方法(重载,用于关闭Connection, Statement, ResultSet) public static void close(AutoCloseable... closeables) { for (AutoCloseable closeable : closeables) { if (closeable != null) { try { closeable.close(); } catch (Exception e) { e.printStackTrace(); // 生产环境应使用日志框架 } } } } }

关键点与避坑指南:

  • 连接字符串参数useSSL=false在本地开发环境通常关闭SSL。serverTimezone=Asia/Shanghai必须的,否则可能遇到“服务器时区”错误。这是MySQL 8.x驱动的一个常见坑。
  • 驱动类名:MySQL 5.x用的是com.mysql.jdbc.Driver,而MySQL 8.x是com.mysql.cj.jdbc.Driver。用错会导致ClassNotFoundException
  • 资源关闭:务必在finally块或使用try-with-resources语句关闭ConnectionStatementResultSet,否则会导致数据库连接泄漏,最终拖垮服务器。上面的close方法是一个简单的工具方法。
  • 密码安全:实际项目中,绝对不要将密码硬编码在代码里。应该使用配置文件(如.properties)或环境变量来管理。

有了连接工具,我们就可以创建EmployeeDAO类来封装对employee表的增删改查操作了。这里以新增和查询为例:

package com.yourcompany.personnel.dao; import com.yourcompany.personnel.model.Employee; import com.yourcompany.personnel.util.DBUtil; import java.sql.*; import java.util.ArrayList; import java.util.List; public class EmployeeDAO { public boolean addEmployee(Employee emp) { String sql = "INSERT INTO employee(emp_id, name, gender, age, department, position, phone, email, hire_date) VALUES(?,?,?,?,?,?,?,?,?)"; try (Connection conn = DBUtil.getConnection(); PreparedStatement pstmt = conn.prepareStatement(sql)) { pstmt.setString(1, emp.getEmpId()); pstmt.setString(2, emp.getName()); pstmt.setObject(3, emp.getGender()); // 使用setObject处理可能为null的Integer pstmt.setObject(4, emp.getAge()); pstmt.setString(5, emp.getDepartment()); pstmt.setString(6, emp.getPosition()); pstmt.setString(7, emp.getPhone()); pstmt.setString(8, emp.getEmail()); pstmt.setDate(9, emp.getHireDate() != null ? new java.sql.Date(emp.getHireDate().getTime()) : null); return pstmt.executeUpdate() > 0; } catch (SQLException e) { e.printStackTrace(); return false; } } public List<Employee> getAllEmployees() { List<Employee> list = new ArrayList<>(); String sql = "SELECT * FROM employee ORDER BY create_time DESC"; try (Connection conn = DBUtil.getConnection(); Statement stmt = conn.createStatement(); ResultSet rs = stmt.executeQuery(sql)) { while (rs.next()) { Employee emp = new Employee(); emp.setId(rs.getInt("id")); emp.setEmpId(rs.getString("emp_id")); emp.setName(rs.getString("name")); emp.setGender(rs.getInt("gender")); emp.setAge(rs.getInt("age")); emp.setDepartment(rs.getString("department")); emp.setPosition(rs.getString("position")); emp.setPhone(rs.getString("phone")); emp.setEmail(rs.getString("email")); emp.setHireDate(rs.getDate("hire_date")); emp.setCreateTime(rs.getTimestamp("create_time")); list.add(emp); } } catch (SQLException e) { e.printStackTrace(); } return list; } // 其他方法:updateEmployee, deleteEmployee, getEmployeeById 等 }

DAO层经验谈:

  • 使用PreparedStatement:所有涉及用户输入的SQL操作(增、删、改、带条件的查),必须使用PreparedStatement,并通过setXxx方法传参。这是防止SQL注入攻击的底线。
  • 处理NULL值:对于可能为null的字段(如年龄、性别),使用setObject或先判断再setNull,避免类型转换异常。
  • Java与SQL类型转换:Java的java.util.Date需要转换为java.sql.DateTimeTimestamp才能存入数据库。从ResultSet取出时,根据字段类型使用对应的getDategetTimestamp等方法。
  • 模型类(Employee):需要创建一个对应的JavaBean(POJO),包含所有字段及其getter/setter方法。这是数据在Java层流转的载体。

数据库和持久层是系统的基石,这部分代码的健壮性直接决定了整个应用的稳定性。务必重视异常处理和资源管理。

4. Servlet控制器与JSP视图:如何打通前后端的任督二脉?

在经典的MVC模式中,Servlet扮演控制器(Controller)的角色,负责接收请求、调用业务逻辑(DAO)、准备数据,并转发给视图(JSP)进行渲染。JSP则负责展示数据。这是理解Web请求响应的关键一环。

4.1 设计核心Servlet:EmployeeServlet

我们通常用一个EmployeeServlet来处理所有与员工相关的请求。为了区分不同的操作(新增、查询、删除等),我们可以在请求中携带一个action参数。

首先,在web.xml中配置Servlet(如果使用Servlet 3.0+注解方式更简单,但显式配置有助于理解机制):

<?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"> <servlet> <servlet-name>EmployeeServlet</servlet-name> <servlet-class>com.yourcompany.personnel.controller.EmployeeServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>EmployeeServlet</servlet-name> <url-pattern>/employee</url-pattern> </servlet-mapping> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> </web-app>

然后,创建EmployeeServlet.java

package com.yourcompany.personnel.controller; import com.yourcompany.personnel.dao.EmployeeDAO; import com.yourcompany.personnel.model.Employee; import javax.servlet.ServletException; import javax.servlet.annotation.WebServlet; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; // 也可以使用注解 @WebServlet("/employee") 替代 web.xml 配置 public class EmployeeServlet extends HttpServlet { private EmployeeDAO employeeDAO = new EmployeeDAO(); @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { doPost(request, response); // 通常我们将GET请求也转到POST处理,简化逻辑 } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setCharacterEncoding("UTF-8"); // 解决POST请求中文乱码 response.setContentType("text/html;charset=UTF-8"); String action = request.getParameter("action"); if ("add".equals(action)) { addEmployee(request, response); } else if ("delete".equals(action)) { deleteEmployee(request, response); } else if ("toUpdate".equals(action)) { toUpdatePage(request, response); } else if ("update".equals(action)) { updateEmployee(request, response); } else { // 默认动作为列出所有员工 listEmployees(request, response); } } // 列出所有员工 private void listEmployees(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { request.setAttribute("employeeList", employeeDAO.getAllEmployees()); request.getRequestDispatcher("/employeeList.jsp").forward(request, response); } // 添加员工 private void addEmployee(HttpServletRequest request, HttpServletResponse response) throws IOException { Employee emp = new Employee(); emp.setEmpId(request.getParameter("empId")); emp.setName(request.getParameter("name")); // 注意:前端传来的gender是字符串,需要转换。空字符串处理是关键。 String genderStr = request.getParameter("gender"); emp.setGender(genderStr != null && !genderStr.isEmpty() ? Integer.parseInt(genderStr) : null); // 同理处理age等可能为空的字段... String ageStr = request.getParameter("age"); emp.setAge(ageStr != null && !ageStr.isEmpty() ? Integer.parseInt(ageStr) : null); emp.setDepartment(request.getParameter("department")); emp.setPosition(request.getParameter("position")); emp.setPhone(request.getParameter("phone")); emp.setEmail(request.getParameter("email")); // 日期处理:SimpleDateFormat不是线程安全的,但Servlet中每次请求新建实例可以接受。生产环境建议用Java 8的DateTimeFormatter。 String hireDateStr = request.getParameter("hireDate"); if (hireDateStr != null && !hireDateStr.isEmpty()) { try { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); emp.setHireDate(sdf.parse(hireDateStr)); } catch (ParseException e) { e.printStackTrace(); // 日期格式错误处理 } } boolean success = employeeDAO.addEmployee(emp); // 添加完成后重定向到列表页,防止表单重复提交 response.sendRedirect(request.getContextPath() + "/employee?action=list"); } // 其他方法:deleteEmployee, toUpdatePage, updateEmployee 实现类似... }

Servlet开发核心要点:

  • 请求分发:通过action参数来区分不同业务操作,这是小型项目常用的简单策略。
  • 中文乱码必须处理request.setCharacterEncoding("UTF-8")解决POST请求体乱码;response.setContentType("text/html;charset=UTF-8")设置响应编码。GET请求的乱码需要在Tomcat的server.xml中配置URIEncoding="UTF-8",或者在代码中对参数手动编解码。
  • 数据封装:将request.getParameter获取的字符串参数,转换为合适的类型并封装到Employee对象中,再传递给DAO层。这个过程称为“数据绑定”。
  • 页面跳转request.getRequestDispatcher("xxx.jsp").forward(request, response)是服务器端转发,地址栏不变,请求域(request)中的数据可以带到JSP。response.sendRedirect("url")是客户端重定向,地址栏变化,会发起新的请求,之前的request数据会丢失。添加、删除、更新等改变数据的操作后,应使用重定向(PRG模式:Post-Redirect-Get),以避免用户刷新页面导致表单重复提交。

4.2 使用JSP和JSTL构建动态页面

JSP页面负责展示。我们将员工列表页面employeeList.jsp放在/WEB-INF/目录下,这样可以防止用户直接通过URL访问JSP,增强安全性(必须通过Servlet转发)。

为了在JSP中优雅地处理Java代码和循环,我们引入JSTL(JSP Standard Tag Library)。它提供了一套标签,可以替代<% ... %>脚本片段,使页面更清晰。

首先,需要将JSTL的jar包(如javax.servlet.jsp.jstl-apijavax.servlet.jsp.jstl)放入WEB-INF/lib

employeeList.jsp的核心部分:

<%@ page contentType="text/html;charset=UTF-8" language="java" %> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html> <html> <head> <title>员工信息列表</title> <style> /* 简单的CSS样式 */ table { border-collapse: collapse; width: 100%; } th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } th { background-color: #f2f2f2; } .actions a { margin-right: 10px; } </style> </head> <body> <h2>员工信息管理</h2> <a href="employee?action=toAdd">新增员工</a> <table> <thead> <tr> <th>工号</th><th>姓名</th><th>性别</th><th>年龄</th><th>部门</th><th>职位</th><th>电话</th><th>邮箱</th><th>入职日期</th><th>操作</th> </tr> </thead> <tbody> <c:choose> <c:when test="${not empty employeeList}"> <c:forEach var="emp" items="${employeeList}"> <tr> <td>${emp.empId}</td> <td>${emp.name}</td> <td> <c:choose> <c:when test="${emp.gender == 1}">男</c:when> <c:when test="${emp.gender == 0}">女</c:when> <c:otherwise>未知</c:otherwise> </c:choose> </td> <td>${emp.age}</td> <td>${emp.department}</td> <td>${emp.position}</td> <td>${emp.phone}</td> <td>${emp.email}</td> <td>${emp.hireDate}</td> <td class="actions"> <a href="employee?action=toUpdate&id=${emp.id}">编辑</a> <a href="javascript:if(confirm('确定删除吗?')) location.href='employee?action=delete&id=${emp.id}'">删除</a> </td> </tr> </c:forEach> </c:when> <c:otherwise> <tr><td colspan="10" style="text-align:center;">暂无员工数据</td></tr> </c:otherwise> </c:choose> </tbody> </table> </body> </html>

JSP与JSTL实战技巧:

  • EL表达式${emp.name}是EL(Expression Language)表达式,用于从pageContext,request,session,application四个作用域中获取属性。它比<%= emp.getName() %>更简洁,且能自动处理null值(显示为空字符串,而不是抛出异常)。
  • JSTL核心标签<c:forEach>用于循环,<c:choose>/<c:when>/<c:otherwise>用于条件判断。它们彻底取代了Java脚本,让页面专注于展示。
  • 数据格式化:对于日期、数字的显示,可以使用JSTL的格式化标签库(fmt),例如<fmt:formatDate value="${emp.hireDate}" pattern="yyyy-MM-dd"/>
  • 路径问题:在JSP中写链接或表单action时,建议使用${pageContext.request.contextPath}来获取应用上下文路径,例如href="${pageContext.request.contextPath}/employee?action=toAdd"。这样即使应用部署的上下文路径变了,链接也不会失效。

至此,一个具备基本CRUD功能的人员信息管理系统就完成了。用户可以通过浏览器访问,查看列表、新增、编辑、删除员工信息。但这只是一个开始,一个健壮的系统还需要考虑更多。

5. 功能增强与实战优化:让系统更健壮、更友好

基础功能跑通后,我们需要从用户体验、代码质量和系统健壮性角度进行优化。这部分往往是区分“玩具项目”和“可用的项目”的关键。

5.1 输入验证与异常处理:守住数据质量的防线

用户输入是不可信的。前端可以做初步验证(如HTML5的requiredpattern属性),但后端必须做最终、最严格的校验。

后端验证示例(在Servlet的addEmployee方法中):

private boolean validateEmployee(Employee emp) { if (emp.getEmpId() == null || emp.getEmpId().trim().isEmpty()) { request.setAttribute("errorMsg", "工号不能为空"); return false; } // 工号格式:字母数字组合,长度6-10位 if (!emp.getEmpId().matches("^[A-Za-z0-9]{6,10}$")) { request.setAttribute("errorMsg", "工号格式错误,应为6-10位字母数字"); return false; } if (emp.getName() == null || emp.getName().trim().isEmpty()) { request.setAttribute("errorMsg", "姓名不能为空"); return false; } // 邮箱格式验证(简单版) if (emp.getEmail() != null && !emp.getEmail().isEmpty()) { String emailRegex = "^[A-Za-z0-9+_.-]+@(.+)$"; if (!emp.getEmail().matches(emailRegex)) { request.setAttribute("errorMsg", "邮箱格式不正确"); return false; } } return true; }

addEmployee方法中,先调用validateEmployee,如果失败,则将错误信息存入request,并转发回添加页面显示错误。

全局异常处理:web.xml中配置错误页面,可以捕获特定状态码或异常类型,跳转到友好页面。

<error-page> <error-code>500</error-code> <location>/error/500.jsp</location> </error-page> <error-page> <exception-type>java.lang.Exception</exception-type> <location>/error/exception.jsp</location> </error-page>

5.2 分页查询:应对海量数据的必备技能

当员工数据成千上万时,一次性加载所有数据到页面是不可行的。我们需要实现分页。

思路:

  1. 数据库分页:使用MySQL的LIMIT子句。SELECT * FROM employee ORDER BY id DESC LIMIT ?, ?。第一个?是起始索引((pageNo-1) * pageSize),第二个?是每页条数(pageSize)。
  2. 前端传递参数:列表页面需要传递pageNo(当前页码)和pageSize(每页大小,可固定)到Servlet。
  3. Servlet处理:接收参数,计算起始索引,调用DAO的分页查询方法。
  4. DAO实现:新增一个方法List<Employee> getEmployeesByPage(int start, int pageSize),执行带LIMIT的SQL。
  5. 计算总页数:需要另一个DAO方法int getTotalCount()获取总记录数,然后totalPages = (totalCount + pageSize - 1) / pageSize
  6. JSP显示分页控件:根据总页数和当前页,生成“上一页”、“下一页”、页码数字的链接。

这是一个非常经典的功能,实现它能让你的项目立刻提升一个档次。

5.3 使用连接池提升性能

我们之前的DBUtil每次操作都新建一个数据库连接,用完后关闭。在高并发场景下,频繁创建和销毁连接开销巨大。数据库连接池(如HikariCP, Druid)是解决这个问题的标准方案。它预先创建一批连接放在池子里,应用需要时从池中取用,用完后归还,而不是真正关闭。

以阿里巴巴的Druid为例,集成非常简单:

  1. 引入Druid的jar包。
  2. WEB-INF下创建druid.properties配置文件,配置连接参数。
  3. 在项目启动时(如实现ServletContextListener)初始化Druid数据源。
  4. 修改DBUtil.getConnection()方法,从Druid数据源获取连接。

连接池的配置参数(如初始连接数、最大连接数、超时时间)需要根据实际压力进行调整,这是后端性能调优的基础知识。

5.4 日志记录:不可或缺的“黑匣子”

千万不要再用System.out.println()e.printStackTrace()了。使用日志框架(如Log4j 2或SLF4J+Logback)来记录程序运行信息、错误和调试信息。

好处:

  • 分级输出:可以控制不同环境(开发/生产)输出不同级别的日志(DEBUG, INFO, WARN, ERROR)。
  • 输出到文件:可以按日期、大小滚动生成日志文件,便于长期保存和查阅。
  • 结构化信息:可以方便地记录线程、类、方法等信息。

在DAO层、Servlet的关键步骤中加入日志记录,当线上出现问题时,这些日志是定位问题的第一手资料。

6. 项目打包与Tomcat部署:从开发环境到生产环境

开发调试完成后,我们需要将项目打包,并部署到独立的Tomcat服务器上,模拟生产环境。

6.1 使用IDEA打包WAR文件

  1. 在IDEA中,点击菜单栏的“Build” -> “Build Artifacts...”。
  2. 选择你的项目artifact(通常就是项目名),点击“Build”。
  3. IDEA会在out/artifacts/或项目根目录下生成一个.war文件(例如personnel_management.war)。

WAR(Web Application Archive)文件是一种标准的Java Web应用打包格式,它包含了编译后的class文件、JSP、HTML、CSS、JS以及WEB-INF/lib下的所有jar包。

6.2 部署WAR包到独立Tomcat

  1. 停止Tomcat:确保你本地的独立Tomcat服务器已经停止运行。
  2. 清理旧部署:进入Tomcat的webapps目录,删除之前可能存在的你的项目文件夹(如personnel)和同名的.war文件。
  3. 放置WAR包:将上一步生成的.war文件复制到Tomcat的webapps目录下。
  4. 启动Tomcat:运行Tomcat的bin/startup.bat(Windows)或bin/startup.sh(Linux/Mac)。
  5. 自动解压:Tomcat启动时,会自动将webapps目录下的.war文件解压成一个同名的文件夹,并加载其中的应用。
  6. 访问应用:打开浏览器,访问http://localhost:8080/你的war包名(不含.war后缀)。例如,如果你的war包叫personnel_management.war,那么访问路径就是http://localhost:8080/personnel_management

6.3 部署过程中的常见问题与解决

  • 端口冲突:如果8080端口被占用,Tomcat会启动失败。可以修改Tomcatconf/server.xml文件中的<Connector port="8080" ...>,改为其他端口,如8081。
  • 中文乱码:如果部署后页面或数据库出现乱码,检查以下几个点:
    1. JSP页面的pageEncodingcontentType是否都是UTF-8
    2. Servlet中是否设置了request.setCharacterEncoding("UTF-8")response.setCharacterEncoding("UTF-8")
    3. 数据库、表、连接字符串的字符集是否都是utf8mb4
    4. Tomcat的conf/server.xml中,<Connector>标签是否添加了URIEncoding="UTF-8"属性(针对GET请求参数乱码)。
  • 找不到类/ClassNotFoundException:通常是因为WAR包中缺少必要的jar包。确保所有依赖(MySQL驱动、JSTL jar包等)都放在了WEB-INF/lib目录下,并且被打包进了WAR文件。
  • 应用上下文路径:部署后访问的路径是/war包名。如果你希望用根路径(/)访问,有两种方法:一是将WAR包重命名为ROOT.war;二是在server.xml<Host>标签内添加<Context path="" docBase="你的war包绝对路径" />

完成部署并成功访问,意味着你的第一个完整的Java Web项目已经可以独立运行了。这个过程让你亲身体验了从编码到上线的完整链路,这是理论学习无法替代的。

整个项目从环境搭建到部署上线的完整代码,我已经整理好并开源。代码中包含了上述所有核心功能、分页查询的实现、Druid连接池的集成以及基本的日志配置。你可以直接下载、导入IDEA运行,也可以以此为骨架,添加更复杂的功能,如用户登录认证、权限管理、文件上传、图表统计等。希望这个详尽的复盘,能帮你打通Java Web开发的任督二脉。

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

3个灵魂拷问!读懂医疗器械手册翻译,避开90%的专业坑

医疗器械手册是医疗设备的“使用指南”&#xff0c;小到家用血压计&#xff0c;大到手术机器人&#xff0c;其翻译质量直接关系到医护操作安全和患者生命健康。很多人觉得“翻译不就是换种语言”&#xff0c;但医疗器械手册翻译远比普通文本翻译严格&#xff0c;其中的门道的不…

作者头像 李华
网站建设 2026/8/6 5:23:52

Linux系统资源监控实战:CPU、内存、磁盘性能排查与优化指南

1. 引言&#xff1a;为什么我们需要时刻关注系统资源&#xff1f;作为一名长期与Linux服务器打交道的运维工程师或开发者&#xff0c;我敢说&#xff0c;查看CPU、内存和磁盘使用情况&#xff0c;是每天打开终端后做得最多的事情之一。这不仅仅是例行公事&#xff0c;更像是给系…

作者头像 李华
网站建设 2026/8/6 5:23:51

抖音内容管家:3个步骤打造个人专属数字收藏馆

抖音内容管家&#xff1a;3个步骤打造个人专属数字收藏馆 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback support. 抖音…

作者头像 李华
网站建设 2026/8/6 5:23:04

ESP32内存管理深度解析:从碎片化到崩溃的排查与优化实践

1. 从一次诡异的“死机”说起那天下午&#xff0c;我正在调试一个基于ESP32的智能家居传感器节点。项目本身不复杂&#xff0c;就是采集温湿度数据&#xff0c;通过Wi-Fi上报到云端&#xff0c;再控制一个继电器。代码写好了&#xff0c;编译通过&#xff0c;烧录一气呵成。上电…

作者头像 李华
网站建设 2026/8/6 5:20:04

免费开源NBT编辑器:NBTExplorer终极指南快速上手

免费开源NBT编辑器&#xff1a;NBTExplorer终极指南快速上手 【免费下载链接】NBTExplorer A graphical NBT editor for all Minecraft NBT data sources 项目地址: https://gitcode.com/gh_mirrors/nb/NBTExplorer 还在为Minecraft游戏数据的复杂二进制格式而头疼吗&am…

作者头像 李华
网站建设 2026/8/6 5:19:40

D-S证据理论:多源信息融合与不确定性决策的数学框架

1. 从“盲人摸象”到“专家会诊”&#xff1a;D-S证据理论到底在解决什么问题&#xff1f;想象一下&#xff0c;你是一个指挥官&#xff0c;面对一个模糊不清的战场态势。雷达A报告&#xff1a;“东北方向&#xff0c;有80%的可能性是敌机&#xff0c;但有20%的可能性是鸟群。”…

作者头像 李华