JSP与Servlet整合数据库开发:构建Java Web应用的全栈指南

概述

在Java Web开发领域,JSP(JavaServer Pages)与Servlet是构建动态Web应用的核心技术组合。Servlet作为Java EE的基础组件,负责处理客户端请求、执行业务逻辑并协调数据交互;JSP则专注于视图渲染,将动态数据与静态页面模板结合生成HTML响应;而关系型数据库则提供了数据的持久化存储能力。

三者的整合本质上是MVC(Model-View-Controller)设计模式在Java Web中的经典实践:Servlet扮演“控制器”(Controller),JSP扮演“视图”(View),数据模型(Model)与DAO(Data Access Object)层则负责数据的封装与数据库交互。这种分层架构能有效分离“业务逻辑”“数据处理”与“页面展示”,大幅提升代码的可维护性、可扩展性和可测试性。

本文将以一个用户管理系统为实战案例,从环境搭建到代码实现,完整讲解如何基于JSP+Servlet+JDBC构建可运行的Java Web应用,并融入安全、性能等最佳实践。

技术栈架构

要理解三者的协同机制,首先需要明确各组件的角色与交互流程。以下架构图清晰展示了请求从客户端发起至响应返回的全链路:

┌─────────┐    HTTP请求/响应    ┌─────────┐    JDBC调用      ┌─────────┐
│ 客户端  │ ◄────────────────► │ Servlet │ ◄──────────────► │ 数据库  │
│(浏览器) │                     │ (控制器) │                 │ (MySQL/ │
└─────────┘                     └─────────┘                 │ Oracle等)△                       └─────────┘│ 设置属性/转发请求│┌─────────┐│   JSP   ││ (视图)  │└─────────┘

各组件核心作用解析

  1. 客户端(浏览器):用户交互入口,负责发送HTTP请求(如GET/POST),并接收服务器返回的HTML/CSS/JS响应进行渲染。
  2. Servlet(控制器)
    • 接收客户端请求,解析请求参数(如表单数据、URL参数);
    • 调用DAO层执行数据库操作(如查询用户、新增数据);
    • 将处理结果封装为“请求属性”(Request Attribute),转发至JSP视图;
    • 或通过重定向(Redirect)引导客户端跳转至其他页面(如新增用户后跳转至列表页)。
  3. JSP(视图)
    • 以HTML为基础,通过EL表达式(${}) 读取Servlet传递的请求属性;
    • 借助JSTL标签库(如<c:forEach>)实现循环、条件判断等动态逻辑;
    • 最终生成完整的HTML页面,通过Servlet返回给客户端。
  4. 数据库:持久化存储应用数据(如用户信息),通过JDBC与Servlet层交互。
  5. JDBC(Java Database Connectivity):Java访问数据库的标准API,提供了连接数据库、执行SQL语句、处理结果集的统一接口,屏蔽了不同数据库的底层差异。

环境准备

在开始编码前,需完成开发环境与项目结构的搭建。以下是详细的准备步骤:

1. 所需技术与版本建议

选择稳定且主流的版本组合,避免因版本兼容问题导致开发受阻:

技术组件推荐版本核心作用
Java SEJDK 11+基础开发语言,Servlet/JSP的运行依赖
Servlet容器Tomcat 10+运行Servlet/JSP的服务器(兼容Servlet 5.0+)
JSP2.3+动态视图生成
JDBC4.3+数据库连接API
数据库MySQL 8.0+关系型数据库,存储应用数据
构建工具(可选)Maven 3.6+依赖管理与项目构建
开发工具IntelliJ IDEA/Eclipse代码编写与调试

2. 环境搭建关键步骤

(1)安装JDK并配置环境变量
  • 下载JDK 11+(如Oracle JDK或OpenJDK);
  • 配置JAVA_HOME(指向JDK安装目录)、PATH(添加%JAVA_HOME%\bin);
  • 验证:命令行执行java -version,显示版本信息即配置成功。
(2)安装Tomcat并测试
  • 下载Tomcat 10+(Apache官网),解压至无空格目录;
  • 启动:运行tomcat/bin/startup.bat(Windows)或startup.sh(Linux);
  • 验证:浏览器访问http://localhost:8080,显示Tomcat默认页面即启动成功。
(3)安装MySQL并创建数据库
  • 安装MySQL 8.0+,配置root用户密码;
  • 通过MySQL命令行或Navicat等工具创建应用数据库(如user_management):
    CREATE DATABASE user_management CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
    

3. 项目结构设计

遵循MVC与分层思想的项目结构能让代码逻辑更清晰,以下是标准的Maven项目结构(非Maven项目可参考此目录划分):

/project-root          # 项目根目录├── /src│   └── /main│       ├── /java    # Java源代码目录(核心业务逻辑)│       │   └── /com│       │       └── /yourapp│       │           ├── /servlets   # 控制器:Servlet类│       │           ├── /models     # 模型:POJO类(封装数据)│       │           ├── /daos       # 数据访问层:DAO接口与实现│       │           ├── /services   # 业务逻辑层:处理复杂业务(可选)│       │           └── /utils      # 工具类:数据库连接、加密等│       ├── /webapp  # Web资源目录(视图、静态资源、配置)│       │   ├── /WEB-INF            # 受保护目录(客户端无法直接访问)│       │   │   ├── web.xml         # Web应用配置文件(Servlet映射等)│       │   │   └── /views          # 视图:JSP文件│       │   ├── /css                # 静态资源:样式表│       │   ├── /js                 # 静态资源:JavaScript│       │   └── /images             # 静态资源:图片│       └── /resources              # 配置资源:数据库连接参数等└── pom.xml                          # Maven配置:依赖坐标
关键目录说明
  • /WEB-INF:核心配置目录,包含web.xml(Servlet 3.0前需手动配置Servlet映射)和JSP视图。由于客户端无法直接访问此目录下的资源,需通过Servlet转发才能访问JSP,保证了视图的安全性。
  • /java/com/yourapp/daos:DAO层负责与数据库直接交互,封装了所有SQL操作,使Servlet层无需关心数据访问细节。
  • /java/com/yourapp/services:当业务逻辑复杂时(如“注册用户前验证邮箱唯一性”),可新增Service层介于Servlet与DAO之间,避免Servlet代码臃肿。

数据库设计

数据是应用的核心,合理的数据库设计是系统稳定运行的基础。以“用户管理系统”为例,我们设计users表存储用户基本信息,遵循以下设计原则:

  • 主键唯一:使用自增ID作为主键;
  • 约束明确:非空(NOT NULL)、唯一(UNIQUE)约束保证数据完整性;
  • 字符集适配:使用utf8mb4支持中文及特殊字符;
  • 时间追踪:添加created_at记录用户注册时间。

1. 创建users表SQL语句

USE user_management; -- 切换至应用数据库CREATE TABLE users (id INT PRIMARY KEY AUTO_INCREMENT COMMENT '用户ID(主键,自增)',username VARCHAR(50) NOT NULL UNIQUE COMMENT '用户名(唯一,非空)',email VARCHAR(100) NOT NULL UNIQUE COMMENT '邮箱(唯一,非空)',password VARCHAR(255) NOT NULL COMMENT '密码(哈希存储,非空)',created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP COMMENT '注册时间(默认当前时间)'
) COMMENT '用户表';

2. 设计补充说明

  • password字段:此处定义为VARCHAR(255),因为实际开发中严禁存储明文密码,需通过BCrypt等算法哈希后存储(哈希结果通常为60+字符);
  • email字段:添加UNIQUE约束避免重复注册,同时便于后续实现“忘记密码”等功能;
  • TIMESTAMP类型DEFAULT CURRENT_TIMESTAMP表示插入数据时若未指定created_at,自动填充当前时间。

模型层(Model):封装数据

模型层由POJO(Plain Old Java Object,简单Java对象) 构成,其核心作用是“封装数据”——将数据库表中的一条记录映射为一个Java对象(如users表的一条记录对应一个User对象),便于在各层之间传递数据。

POJO类需满足以下规范:

  • 私有属性(与数据库表字段对应);
  • 无参构造方法(便于反射实例化,如JDBC结果集映射);
  • 有参构造方法(便于快速创建对象);
  • Getter/Setter方法(用于访问和修改属性);
  • 可选:toString()方法(便于调试时打印对象信息)。

1. User类实现代码

package com.yourapp.models;import java.sql.Timestamp;/*** 用户模型类:映射users表的一条记录*/
public class User {// 私有属性:与users表字段一一对应private int id;private String username;private String email;private String password;private Timestamp createdAt;// 1. 无参构造方法(必须,JDBC映射时需要)public User() {}// 2. 有参构造方法(新增用户时使用,无需id和createdAt)public User(String username, String email, String password) {this.username = username;this.email = email;this.password = password;}// 3. Getter方法:获取属性值public int getId() {return id;}public String getUsername() {return username;}public String getEmail() {return email;}public String getPassword() {return password;}public Timestamp getCreatedAt() {return createdAt;}// 4. Setter方法:修改属性值(id和createdAt由数据库生成,仅需setter)public void setId(int id) {this.id = id;}public void setUsername(String username) {this.username = username;}public void setEmail(String email) {this.email = email;}public void setPassword(String password) {this.password = password;}public void setCreatedAt(Timestamp createdAt) {this.createdAt = createdAt;}// 可选:toString()方法,便于调试时打印用户信息@Overridepublic String toString() {return "User{" +"id=" + id +", username='" + username + '\'' +", email='" + email + '\'' +", createdAt=" + createdAt +'}';}
}

2. 模型层设计意义

  • 数据封装:将用户的多个属性(id、username等)封装为一个对象,避免方法参数过多(如新增用户时无需传递5个独立参数,只需传递一个User对象);
  • 解耦依赖:Servlet层、DAO层通过User对象交互,无需关心数据的具体存储格式(如数据库字段类型);
  • 扩展性强:若后续users表新增字段(如phone),只需在User类中添加对应属性和Getter/Setter即可,无需大面积修改代码。

数据访问层(DAO):操作数据库

DAO层是“模型层”与“数据库”之间的桥梁,负责所有数据库交互逻辑(如新增、查询、修改、删除用户)。其设计遵循DAO模式,核心思想是“抽象数据访问细节”,使上层(Servlet/Service)无需编写JDBC代码即可操作数据库。

DAO层通常包含两部分:

  • DAO接口:定义数据访问方法(如createUser(User user));
  • DAO实现类:实现接口,编写具体的JDBC代码(连接数据库、执行SQL、处理结果集)。

此外,需创建一个数据库连接工具类,统一管理数据库连接的创建与关闭,避免代码冗余。

1. 工具类:数据库连接管理(DBUtils)

JDBC连接数据库的步骤固定(加载驱动→创建连接),但频繁创建/关闭连接会严重影响性能。此处先实现基础连接工具类,后续会优化为“连接池”。

package com.yourapp.utils;import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;/*** 数据库连接工具类:提供获取连接和关闭连接的静态方法*/
public class DBUtils {// 数据库连接参数(建议从配置文件读取,此处为演示硬编码)private static final String DB_URL = "jdbc:mysql://localhost:3306/user_management?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true";private static final String DB_USER = "root";       // 你的MySQL用户名private static final String DB_PASSWORD = "123456"; // 你的MySQL密码private static final String DRIVER_CLASS = "com.mysql.cj.jdbc.Driver"; // MySQL 8.0+驱动类static {// 静态代码块:加载数据库驱动(仅执行一次)try {Class.forName(DRIVER_CLASS);System.out.println("数据库驱动加载成功!");} catch (ClassNotFoundException e) {// 驱动加载失败时抛出运行时异常,终止程序启动throw new RuntimeException("数据库驱动加载失败,请检查依赖是否正确!", e);}}/*** 获取数据库连接* @return Connection 数据库连接对象* @throws SQLException 连接失败时抛出异常*/public static Connection getConnection() throws SQLException {return DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);}/*** 关闭数据库资源(ResultSet、PreparedStatement、Connection)* 注意关闭顺序:先ResultSet,再PreparedStatement,最后Connection*/public static void closeResources(AutoCloseable... resources) {for (AutoCloseable resource : resources) {if (resource != null) {try {resource.close(); // 关闭资源} catch (Exception e) {e.printStackTrace();}}}}
}
关键代码解释
  • 连接参数
    • useSSL=false:禁用SSL连接(开发环境简化配置);
    • serverTimezone=UTC:设置时区,避免MySQL 8.0+的时区警告;
    • allowPublicKeyRetrieval=true:允许获取服务器公钥,解决连接时的权限问题。
  • 静态代码块:类加载时自动执行,仅加载一次驱动,提高效率。
  • closeResources方法:使用AutoCloseable接口接收所有可关闭资源(ResultSet、PreparedStatement、Connection均实现此接口),简化关闭逻辑。

2. DAO接口:定义数据访问方法(UserDAO)

接口定义了“做什么”,不关心“怎么做”,便于后续替换实现(如从MySQL切换到Oracle时,只需新增Oracle的DAO实现类)。

package com.yourapp.daos;import com.yourapp.models.User;
import java.util.List;/*** 用户DAO接口:定义用户数据的访问方法*/
public interface UserDAO {/*** 新增用户* @param user 待新增的用户对象(包含username、email、password)* @return boolean 新增成功返回true,失败返回false*/boolean createUser(User user);/*** 查询所有用户* @return List<User> 所有用户的列表(无数据时返回空列表,非null)*/List<User> getAllUsers();/*** 根据ID查询用户* @param id 用户ID* @return User 找到返回用户对象,未找到返回null*/User getUserById(int id);/*** 根据用户名查询用户(用于验证用户名是否已存在)* @param username 用户名* @return User 找到返回用户对象,未找到返回null*/User getUserByUsername(String username);/*** 更新用户信息* @param user 待更新的用户对象(包含id及需更新的字段)* @return boolean 更新成功返回true,失败返回false*/boolean updateUser(User user);/*** 根据ID删除用户* @param id 用户ID* @return boolean 删除成功返回true,失败返回false*/boolean deleteUserById(int id);
}

3. DAO实现类:编写JDBC逻辑(UserDAOImpl)

实现类负责“怎么做”,通过JDBC完成具体的数据库操作。核心要点:

  • 使用PreparedStatement代替Statement,防止SQL注入;
  • 采用try-with-resources语法自动关闭资源(无需手动调用close());
  • 处理SQLException,并向上层传递或返回状态标识。
package com.yourapp.daos.impl;import com.yourapp.daos.UserDAO;
import com.yourapp.models.User;
import com.yourapp.utils.DBUtils;import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;/*** 用户DAO实现类:基于MySQL的JDBC实现*/
public class UserDAOImpl implements UserDAO {// 1. 新增用户@Overridepublic boolean createUser(User user) {// SQL语句:使用?作为占位符,避免拼接SQL(防止注入)String sql = "INSERT INTO users (username, email, password) VALUES (?, ?, ?)";Connection conn = null;PreparedStatement pstmt = null;try {// 获取连接conn = DBUtils.getConnection();// 创建PreparedStatement(预编译SQL)pstmt = conn.prepareStatement(sql);// 绑定参数:按占位符顺序设置值(1表示第一个?,依次类推)pstmt.setString(1, user.getUsername());pstmt.setString(2, user.getEmail());pstmt.setString(3, user.getPassword()); // 注意:实际开发需先哈希密码// 执行更新操作:executeUpdate()返回受影响的行数int rowsAffected = pstmt.executeUpdate();// 受影响行数>0表示新增成功return rowsAffected > 0;} catch (SQLException e) {e.printStackTrace();// 若用户名/邮箱重复(UNIQUE约束冲突),可在此处特殊处理if (e.getErrorCode() == 1062) {System.out.println("用户名或邮箱已存在!");}return false;} finally {// 关闭资源(顺序:pstmt → conn)DBUtils.closeResources(pstmt, conn);}}// 2. 查询所有用户@Overridepublic List<User> getAllUsers() {String sql = "SELECT id, username, email, password, created_at FROM users ORDER BY created_at DESC";List<User> userList = new ArrayList<>(); // 初始化空列表,避免返回nullConnection conn = null;PreparedStatement pstmt = null;ResultSet rs = null;try {conn = DBUtils.getConnection();pstmt = conn.prepareStatement(sql);// 执行查询操作:executeQuery()返回ResultSet结果集rs = pstmt.executeQuery();// 遍历结果集:rs.next()判断是否有下一条记录while (rs.next()) {// 创建User对象,映射结果集字段User user = new User();user.setId(rs.getInt("id")); // 按字段名获取int值user.setUsername(rs.getString("username")); // 按字段名获取String值user.setEmail(rs.getString("email"));user.setPassword(rs.getString("password"));user.setCreatedAt(rs.getTimestamp("created_at")); // 按字段名获取Timestamp值// 添加到列表userList.add(user);}} catch (SQLException e) {e.printStackTrace();} finally {// 关闭资源(顺序:rs → pstmt → conn)DBUtils.closeResources(rs, pstmt, conn);}return userList;}// 3. 根据ID查询用户@Overridepublic User getUserById(int id) {String sql = "SELECT id, username, email, password, created_at FROM users WHERE id = ?";User user = null;Connection conn = null;PreparedStatement pstmt = null;ResultSet rs = null;try {conn = DBUtils.getConnection();pstmt = conn.prepareStatement(sql);pstmt.setInt(1, id); // 绑定ID参数rs = pstmt.executeQuery();// 若找到记录,映射为User对象if (rs.next()) {user = new User();user.setId(rs.getInt("id"));user.setUsername(rs.getString("username"));user.setEmail(rs.getString("email"));user.setPassword(rs.getString("password"));user.setCreatedAt(rs.getTimestamp("created_at"));}} catch (SQLException e) {e.printStackTrace();} finally {DBUtils.closeResources(rs, pstmt, conn);}return user;}// 4. 根据用户名查询用户@Overridepublic User getUserByUsername(String username) {String sql = "SELECT id, username, email, password, created_at FROM users WHERE username = ?";User user = null;Connection conn = null;PreparedStatement pstmt = null;ResultSet rs = null;try {conn = DBUtils.getConnection();pstmt = conn.prepareStatement(sql);pstmt.setString(1, username);rs = pstmt.executeQuery();if (rs.next()) {user = new User();user.setId(rs.getInt("id"));user.setUsername(rs.getString("username"));user.setEmail(rs.getString("email"));user.setPassword(rs.getString("password"));user.setCreatedAt(rs.getTimestamp("created_at"));}} catch (SQLException e) {e.printStackTrace();} finally {DBUtils.closeResources(rs, pstmt, conn);}return user;}// 5. 更新用户信息@Overridepublic boolean updateUser(User user) {// 只更新username、email、password字段(id为条件,created_at不更新)String sql = "UPDATE users SET username = ?, email = ?, password = ? WHERE id = ?";Connection conn = null;PreparedStatement pstmt = null;try {conn = DBUtils.getConnection();pstmt = conn.prepareStatement(sql);pstmt.setString(1, user.getUsername());pstmt.setString(2, user.getEmail());pstmt.setString(3, user.getPassword());pstmt.setInt(4, user.getId()); // 按ID定位待更新记录int rowsAffected = pstmt.executeUpdate();return rowsAffected > 0;} catch (SQLException e) {e.printStackTrace();return false;} finally {DBUtils.closeResources(pstmt, conn);}}// 6. 根据ID删除用户@Overridepublic boolean deleteUserById(int id) {String sql = "DELETE FROM users WHERE id = ?";Connection conn = null;PreparedStatement pstmt = null;try {conn = DBUtils.getConnection();pstmt = conn.prepareStatement(sql);pstmt.setInt(1, id);int rowsAffected = pstmt.executeUpdate();return rowsAffected > 0;} catch (SQLException e) {e.printStackTrace();return false;} finally {DBUtils.closeResources(pstmt, conn);}}
}
核心JDBC操作解析
  • PreparedStatement的优势
    1. 防SQL注入:通过占位符(?)绑定参数,而非拼接SQL字符串(如"INSERT INTO users VALUES ('" + username + "')"),避免恶意用户输入' OR 1=1 --等注入语句;
    2. 预编译优化:SQL语句仅编译一次,多次执行时可复用,提高效率。
  • ResultSet处理rs.next()移动游标至下一条记录,rs.getInt("id")按字段名获取值(比按索引rs.getInt(1)更易维护,字段顺序变化不影响)。
  • 异常处理:捕获SQLException后打印堆栈信息便于调试,同时针对常见错误(如1062:唯一约束冲突)可添加特殊处理逻辑。

控制器层(Servlet):处理请求与协调逻辑

Servlet是Java Web的“控制器”核心,负责接收客户端请求、调用DAO/Service层处理业务、并将结果转发至JSP视图。其生命周期由Servlet容器(如Tomcat)管理,分为三个阶段:

  1. 初始化(init()):Servlet首次被访问时执行,用于初始化资源(如创建DAO实例);
  2. 服务(service()):每次接收请求时执行,根据请求方式(GET/POST)调用doGet()doPost()
  3. 销毁(destroy()):Servlet容器关闭时执行,用于释放资源(如关闭连接池)。

1. 核心Servlet开发:UserServlet

UserServlet负责处理所有用户相关的请求(如“查看用户列表”“新增用户”“删除用户”),通过action参数区分不同操作。

package com.yourapp.servlets;import com.yourapp.daos.UserDAO;
import com.yourapp.daos.impl.UserDAOImpl;
import com.yourapp.models.User;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.util.List;/*** 用户控制器Servlet:处理所有用户相关的HTTP请求* @WebServlet("/users"):映射URL路径,客户端通过http://localhost:8080/项目名/users访问*/
@WebServlet("/users")
public class UserServlet extends HttpServlet {// 依赖DAO层:通过接口编程,降低耦合(后续可替换为其他实现)private UserDAO userDAO;/*** 初始化方法:Servlet创建时执行,仅一次* 用于初始化DAO实例等资源*/@Overridepublic void init() throws ServletException {super.init();// 实例化DAO实现类(实际开发中可使用依赖注入框架如Spring管理)userDAO = new UserDAOImpl();}/*** 处理GET请求:通常用于查询操作(如查看列表、查看详情)*/@Overrideprotected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {// 1. 设置请求/响应的字符编码,避免中文乱码request.setCharacterEncoding("UTF-8");response.setCharacterEncoding("UTF-8");response.setContentType("text/html;charset=UTF-8");// 2. 获取action参数,区分不同操作(如list、new、edit、delete)// 若未指定action,默认执行list(查看用户列表)String action = request.getParameter("action");if (action == null) {action = "list";}// 3. 根据action执行对应逻辑try {switch (action) {case "new":       // 跳转至新增用户表单页showNewUserForm(request, response);break;case "edit":      // 跳转至编辑用户表单页showEditUserForm(request, response);break;case "delete":    // 删除用户deleteUser(request, response);break;case "list":      // 查看用户列表(默认操作)default:listAllUsers(request, response);break;}} catch (Exception e) {// 捕获所有异常,转发至错误页面request.setAttribute("errorMsg", "操作失败:" + e.getMessage());request.getRequestDispatcher("/WEB-INF/views/error.jsp").forward(request, response);}}/*** 处理POST请求:通常用于提交数据的操作(如新增、更新)* 此处直接调用doGet(),统一处理逻辑(也可单独实现)*/@Overrideprotected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {doGet(request, response);}// ------------------------------ 具体操作方法 ------------------------------/*** 1. 查看所有用户列表* 流程:调用DAO查询所有用户 → 将用户列表设置为请求属性 → 转发至user-list.jsp*/private void listAllUsers(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 调用DAO查询所有用户List<User> userList = userDAO.getAllUsers();// 将用户列表设置为请求属性(key="userList",JSP中通过${userList}获取)request.setAttribute("userList", userList);// 转发请求至JSP视图(路径为/WEB-INF/views/user-list.jsp)// 转发:服务器内部跳转,URL不变,可共享request属性request.getRequestDispatcher("/WEB-INF/views/user-list.jsp").forward(request, response);}/*** 2. 跳转至新增用户表单页* 流程:直接转发至user-form.jsp(无需查询数据)*/private void showNewUserForm(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 转发至新增表单页request.getRequestDispatcher("/WEB-INF/views/user-form.jsp").forward(request, response);}/*** 3. 跳转至编辑用户表单页* 流程:获取用户ID → 调用DAO查询用户 → 将用户对象设置为请求属性 → 转发至user-form.jsp*/private void showEditUserForm(HttpServletRequest request, HttpServletResponse response)throws ServletException, IOException {// 获取URL中的id参数(如/users?action=edit&id=1)String idStr = request.getParameter("id");if (idStr == null || idStr.isEmpty()) {throw new IllegalArgumentException("用户ID不能为空!");}int userId = Integer.parseInt(idStr); // 转换为int类型// 调用DAO根据ID查询用户User user = userDAO.getUserById(userId);if (user == null) {throw new IllegalArgumentException("未找到ID为" + userId + "的用户!");}// 将用户对象设置为请求属性(JSP中用于回显表单数据)request.setAttribute("user", user);// 转发至表单页(与新增共用一个JSP,通过是否存在user对象区分新增/编辑)request.getRequestDispatcher("/WEB-INF/views/user-form.jsp").forward(request, response);}/*** 4. 新增/更新用户(根据是否有id参数区分)* 流程:获取表单参数 → 封装为User对象 → 调用DAO执行新增/更新 → 重定向至用户列表*/private void saveOrUpdateUser(HttpServletRequest request, HttpServletResponse response)throws IOException {// 获取表单参数(name属性对应的值)String idStr = request.getParameter("id"); // 编辑时存在,新增时为nullString username = request.getParameter("username");String email = request.getParameter("email");String password = request.getParameter("password");// 验证参数合法性(简单示例,实际开发需更完善)if (username == null || username.isEmpty() || email == null || email.isEmpty() || password == null || password.isEmpty()) {throw new IllegalArgumentException("用户名、邮箱、密码均不能为空!");}// 验证用户名是否已存在(新增时)if (idStr == null || idStr.isEmpty()) {User existingUser = userDAO.getUserByUsername(username);if (existingUser != null) {throw new IllegalArgumentException("用户名" + username + "已存在!");}}// 封装为User对象User user = new User();if (idStr != null && !idStr.isEmpty()) {// 编辑操作:设置iduser.setId(Integer.parseInt(idStr));}user.setUsername(username);user.setEmail(email);// 实际开发:此处需对密码进行哈希处理(如使用BCrypt),而非直接存储明文user.setPassword(password);// 调用DAO执行新增或更新boolean success;if (user.getId() == 0) {// 新增:id为0表示未设置(User默认id为0)success = userDAO.createUser(user);} else {// 更新:id不为0表示编辑success = userDAO.updateUser(user);}if (!success) {throw new RuntimeException("保存用户失败!");}// 重定向至用户列表页(http://localhost:8080/项目名/users?action=list)// 重定向:客户端跳转,URL改变,避免表单重复提交(刷新页面时不会重新执行POST)response.sendRedirect(request.getContextPath() + "/users?action=list");}/*** 5. 删除用户* 流程:获取用户ID → 调用DAO删除 → 重定向至用户列表*/private void deleteUser(HttpServletRequest request, HttpServletResponse response)throws IOException {// 获取id参数String idStr = request.getParameter("id");if (idStr == null || idStr.isEmpty()) {throw new IllegalArgumentException("用户ID不能为空!");}int userId = Integer.parseInt(idStr);// 调用DAO删除用户boolean success = userDAO.deleteUserById(userId);if (!success) {throw new RuntimeException("删除用户失败!");}// 重定向至用户列表response.sendRedirect(request.getContextPath() + "/users?action=list");}
}

2. Servlet核心知识点解析

(1)URL映射:@WebServlet注解

Servlet 3.0+支持注解配置,无需在web.xml中手动映射。@WebServlet("/users")表示:

  • 客户端可通过http://localhost:8080/项目上下文路径/users访问此Servlet;
  • 上下文路径(Context Path):即项目部署时的名称(如Tomcat中部署为user-management.war,则上下文路径为/user-management)。
(2)请求处理:doGet()与doPost()
  • doGet():用于“查询型”操作(如查看列表、编辑页跳转),参数通过URL传递(如/users?action=edit&id=1),数据暴露在URL中,有长度限制;
  • doPost():用于“提交型”操作(如新增、更新),参数通过请求体传递,数据不暴露,无长度限制,更安全。
  • 此处将doPost()转发至doGet(),统一处理逻辑,简化代码(也可根据需要单独实现)。
(3)请求转发与重定向的区别

这是Servlet开发中的核心概念,选择错误可能导致功能异常(如表单重复提交):

特性请求转发(Forward)重定向(Redirect)
跳转位置服务器内部跳转客户端跳转(服务器返回302状态码,客户端重新发起请求)
URL地址不变改变
请求对象共享同一个HttpServletRequest对象不同请求,不共享
适用场景跳转至视图(JSP)、传递请求属性表单提交后跳转、避免重复提交
代码示例request.getRequestDispatcher(path).forward(...)response.sendRedirect(url)

示例场景

  • 查看用户列表后跳转至user-list.jsp:用转发(需传递userList属性);
  • 新增用户后跳转至列表页:用重定向(避免刷新页面时重新提交表单)。
(4)中文乱码处理

客户端提交的中文参数若不处理,会出现乱码。解决方案:

  • doGet()/doPost()开头设置请求编码:request.setCharacterEncoding("UTF-8")
  • 设置响应编码:response.setCharacterEncoding("UTF-8")
  • 设置响应内容类型:response.setContentType("text/html;charset=UTF-8")(告诉浏览器用UTF-8解析)。

视图层(JSP):渲染动态页面

JSP是Servlet的“模板化”扩展,允许在HTML中嵌入Java代码、EL表达式和JSTL标签,最终由Servlet容器翻译为Servlet并执行,生成动态HTML响应。

视图层的核心目标是“展示数据”,应避免包含复杂业务逻辑(逻辑应放在Servlet/Service层)。我们将开发两个核心JSP页面:user-list.jsp(用户列表)和user-form.jsp(新增/编辑表单)。

1. 依赖准备:引入JSTL标签库

JSTL(JavaServer Pages Standard Tag Library)提供了一套标准标签,用于替代JSP中的Java脚本(如<% %>),使页面更简洁易维护。需在pom.xml中添加依赖(非Maven项目需手动下载JAR包放入/WEB-INF/lib):

<!-- JSTL核心标签库 -->
<dependency><groupId>javax.servlet.jsp.jstl</groupId><artifactId>jstl-api</artifactId><version>1.2</version>
</dependency>
<dependency><groupId>org.glassfish.web</groupId><artifactId>jstl-impl</artifactId><version>1.2</version><scope>runtime</scope>
</dependency>

2. 用户列表页:user-list.jsp

展示所有用户信息,提供“新增”“编辑”“删除”操作入口。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%-- 引入JSTL核心标签库(prefix="c"表示使用<c:xxx>标签) --%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><title>用户管理系统 - 用户列表</title><!-- 引入CSS样式(${pageContext.request.contextPath}获取上下文路径) --><link rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css">
</head>
<body><div class="container"><h1>用户管理</h1><!-- 新增用户按钮:跳转至新增表单页(/users?action=new) --><a href="${pageContext.request.contextPath}/users?action=new" class="btn-add">添加新用户</a><!-- 错误提示:若Servlet传递了errorMsg属性,显示错误信息 --><c:if test="${not empty errorMsg}"><div class="error">${errorMsg}</div></c:if><!-- 用户列表表格 --><table class="user-table"><thead><tr><th>ID</th><th>用户名</th><th>邮箱</th><th>注册时间</th><th>操作</th></tr></thead><tbody><%-- 循环遍历userList(Servlet传递的请求属性) --%><c:forEach var="user" items="${userList}"><tr><!-- EL表达式:${user.id}获取User对象的id属性(调用getter方法) --><td>${user.id}</td><td>${user.username}</td><td>${user.email}</td><td>${user.createdAt}</td><td class="action-buttons"><!-- 编辑按钮:传递id参数(/users?action=edit&id=${user.id}) --><a href="${pageContext.request.contextPath}/users?action=edit&id=${user.id}" class="btn-edit">编辑</a><!-- 删除按钮:点击时弹出确认框,传递id参数 --><a href="${pageContext.request.contextPath}/users?action=delete&id=${user.id}" class="btn-delete" onclick="return confirm('确定要删除用户【${user.username}】吗?')">删除</a></td></tr></c:forEach><%-- 若用户列表为空,显示提示信息 --%><c:if test="${empty userList}"><tr><td colspan="5" class="empty">暂无用户数据</td></tr></c:if></tbody></table></div>
</body>
</html>

3. 新增/编辑表单页:user-form.jsp

共用一个表单页,通过是否存在user对象区分“新增”和“编辑”(新增时user为null,编辑时user为待编辑的用户对象)。

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<!DOCTYPE html>
<html lang="zh-CN">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title><%-- 动态标题:编辑时显示“编辑用户”,新增时显示“添加新用户” --%><c:if test="${not empty user}">编辑用户</c:if><c:if test="${empty user}">添加新用户</c:if></title><link rel="stylesheet" href="${pageContext.request.contextPath}/css/style.css">
</head>
<body><div class="container"><h1><c:if test="${not empty user}">编辑用户</c:if><c:if test="${empty user}">添加新用户</c:if></h1><!-- 错误提示 --><c:if test="${not empty errorMsg}"><div class="error">${errorMsg}</div></c:if><%-- 表单:新增时提交至action=create,编辑时提交至action=update --%><form action="${pageContext.request.contextPath}/users?action=${empty user ? 'create' : 'update'}" method="post" class="user-form"><%-- 编辑时需要传递id参数(隐藏字段) --%><c:if test="${not empty user}"><input type="hidden" name="id" value="${user.id}"></c:if><div class="form-group"><label for="username">用户名:</label><%-- 表单回显:编辑时设置value为user.username,新增时为空 --%><input type="text" id="username" name="username" value="${not empty user ? user.username : ''}" required maxlength="50"></div><div class="form-group"><label for="email">邮箱:</label><input type="email" id="email" name="email" value="${not empty user ? user.email : ''}" required maxlength="100"></div><div class="form-group"><label for="password">密码:</label><input type="password" id="password" name="password" value="${not empty user ? user.password : ''}" required maxlength="255"><small>提示:密码请至少包含6个字符</small></div><div class="form-actions"><button type="submit" class="btn-submit">提交</button><%-- 取消按钮:返回用户列表页 --%><a href="${pageContext.request.contextPath}/users?action=list" class="btn-cancel">取消</a></div></form></div>
</body>
</html>

4. JSP核心知识点解析

(1)EL表达式(${})

EL(Expression Language)用于简化JSP中数据的访问,核心语法:

  • ${user.id}:等价于((User)request.getAttribute("user")).getId(),自动从pageContextrequestsessionapplication作用域中查找属性;
  • ${empty userList}:判断userList是否为null或空集合;
  • ${not empty user ? user.username : ''}:三元运算符,编辑时回显用户名,新增时为空。
(2)JSTL标签
  • <c:forEach>:循环遍历集合(如userList),var="user"表示当前循环的元素,items="${userList}"表示要遍历的集合;
  • <c:if>:条件判断,test="${not empty user}"表示当user不为空时执行标签内的内容。
(3)路径处理:${pageContext.request.contextPath}

获取项目的上下文路径(如/user-management),避免硬编码路径导致部署后无法访问。例如:

  • 正确:href="${pageContext.request.contextPath}/css/style.css"
  • 错误:href="/css/style.css"(部署上下文路径变化时失效)。
(4)表单回显

编辑用户时,需将数据库中的用户信息填充到表单中(回显),通过value="${user.username}"实现,简化了代码(无需在JSP中写Java脚本)。

数据流完整过程

理解请求从发起至响应的完整流程,是掌握JSP+Servlet+数据库整合的关键。以下以“新增用户”为例,通过序列图和步骤说明展示数据流转:

1. 数据流序列图

┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐
│ 浏览器  │    │Servlet  │    │  DAO    │    │ 数据库  │
└─────────┘    └─────────┘    └─────────┘    └─────────┘│              │              │              ││ 1. 提交表单  │              │              ││ POST /users?action=create  │              ││───────────────────────────>│              ││              │              │              ││              │ 2. 解析参数  │              ││              │ (username/email/password)  ││              │              │              ││              │ 3. 封装User对象              ││              │              │              ││              │ 4. 调用DAO.createUser()     ││              │─────────────>│              ││              │              │              ││              │              │ 5. 执行SQL插入 ││              │              │─────────────>││              │              │              ││              │              │ 6. 返回受影响行数││              │              │<─────────────││              │              │              ││              │ 7. 重定向至列表页            ││<───────────────────────────│              ││              │              │              ││ 8. 发起列表请求              │              ││ GET /users?action=list      │              ││───────────────────────────>│              ││              │              │              ││              │ 9. 调用DAO.getAllUsers()    ││              │─────────────>│              ││              │              │              ││              │              │ 10. 执行SQL查询││              │              │─────────────>││              │              │              ││              │              │ 11. 返回结果集 ││              │              │<─────────────││              │              │              ││              │ 12. 封装userList属性        ││              │              │              ││              │ 13. 转发至user-list.jsp     ││              │─────────────>│              ││              │              │              ││              │ 14. 生成HTML响应             ││              │<─────────────│              ││              │              │              ││ 15. 返回HTML页面             │              ││<───────────────────────────│              ││              │              │              │

2. 新增用户核心步骤详解

  1. 用户提交表单:在user-form.jsp中填写用户名、邮箱、密码,点击“提交”,浏览器发送POST /users?action=create请求;
  2. Servlet接收请求UserServlet.doPost()被调用,解析表单参数(usernameemailpassword);
  3. 参数验证与封装:验证参数非空,检查用户名是否已存在,封装为User对象;
  4. DAO执行插入UserServlet调用UserDAO.createUser(),DAO通过JDBC执行INSERT语句;
  5. 重定向至列表页:插入成功后,通过response.sendRedirect()引导浏览器发起GET /users?action=list请求;
  6. 查询并展示列表UserServlet调用UserDAO.getAllUsers()获取所有用户,将userList设置为请求属性,转发至user-list.jsp
  7. JSP渲染页面user-list.jsp通过JSTL和EL表达式遍历userList,生成包含所有用户的HTML页面,返回给浏览器。

最佳实践与进阶优化

基础实现完成后,需从安全性能可维护性三个维度进行优化,使应用达到生产级标准。

1. 安全优化(重中之重)

(1)密码哈希存储(避免明文)

明文存储密码是严重的安全隐患,需使用BCrypt等算法对密码进行哈希处理(哈希是单向过程,无法反向破解)。

实现步骤

  1. 添加BCrypt依赖:
    <dependency><groupId>org.mindrot</groupId><artifactId>jbcrypt</artifactId><version>0.4</version>
    </dependency>
    
  2. 新增/更新用户时哈希密码:
    // 替换UserServlet.saveOrUpdateUser()中的密码设置
    // 生成盐值(自动包含在哈希结果中,无需单独存储)
    String hashedPassword = BCrypt.hashpw(password, BCrypt.gensalt());
    user.setPassword(hashedPassword);
    
  3. 登录验证时匹配密码(扩展功能):
    // 从数据库查询用户
    User user = userDAO.getUserByUsername(username);
    // 验证密码:BCrypt.checkpw(明文密码, 哈希密码)
    if (user != null && BCrypt.checkpw(inputPassword, user.getPassword())) {// 登录成功
    }
    
(2)防止SQL注入

已通过PreparedStatement占位符实现基本防护,还需注意:

  • 避免使用动态SQL拼接(如"SELECT * FROM users WHERE username = '" + username + "'");
  • 对用户输入进行过滤(如限制用户名只能包含字母、数字、下划线)。
(3)防止CSRF攻击

CSRF(跨站请求伪造)攻击者诱导用户在已登录状态下执行非本意的操作(如删除用户)。防护方案:

  1. 在Session中生成随机CSRF Token;
  2. 在表单中添加隐藏字段<input type="hidden" name="csrfToken" value="${sessionScope.csrfToken}">
  3. 在Servlet中验证请求中的Token与Session中的Token是否一致。

2. 性能优化

(1)使用数据库连接池

频繁创建/关闭数据库连接会消耗大量资源,连接池通过预先创建一定数量的连接并复用,大幅提升性能。推荐使用HikariCP(目前性能最优的连接池)。

实现步骤

  1. 添加HikariCP依赖:
    <dependency><groupId>com.zaxxer</groupId><artifactId>HikariCP</artifactId><version>5.0.1</version>
    </dependency>
    
  2. 修改DBUtils为连接池实现:
    public class DBUtils {// 连接池实例(单例)private static final HikariDataSource DATA_SOURCE;static {// 配置连接池HikariConfig config = new HikariConfig();config.setJdbcUrl("jdbc:mysql://localhost:3306/user_management?useSSL=false&serverTimezone=UTC");config.setUsername("root");config.setPassword("123456");config.setDriverClassName("com.mysql.cj.jdbc.Driver");config.setMaximumPoolSize(10); // 最大连接数config.setMinimumIdle(2);      // 最小空闲连接数config.setConnectionTimeout(30000); // 连接超时时间(30秒)// 初始化连接池DATA_SOURCE = new HikariDataSource(config);}// 获取连接(从连接池获取,而非新建)public static Connection getConnection() throws SQLException {return DATA_SOURCE.getConnection();}
    }
    
(2)分页查询(避免大量数据加载)

当用户数量过多时,getAllUsers()会加载所有数据,导致内存溢出和页面卡顿。需实现分页查询:

  1. 修改UserDAO接口:
    // 分页查询用户
    List<User> getUsersByPage(int pageNum, int pageSize);
    // 查询总用户数(用于计算总页数)
    int getTotalUserCount();
    
  2. 实现DAO方法:
    @Override
    public List<User> getUsersByPage(int pageNum, int pageSize) {// MySQL分页SQL:LIMIT 起始索引, 每页条数(起始索引 = (页码-1)*每页条数)String sql = "SELECT id, username, email, created_at FROM users ORDER BY created_at DESC LIMIT ?, ?";List<User> userList = new ArrayList<>();Connection conn = null;PreparedStatement pstmt = null;ResultSet rs = null;try {conn = DBUtils.getConnection();pstmt = conn.prepareStatement(sql);pstmt.setInt(1, (pageNum - 1) * pageSize); // 起始索引pstmt.setInt(2, pageSize); // 每页条数rs = pstmt.executeQuery();while (rs.next()) {User user = new User();user.setId(rs.getInt("id"));user.setUsername(rs.getString("username"));user.setEmail(rs.getString("email"));user.setCreatedAt(rs.getTimestamp("created_at"));userList.add(user);}} catch (SQLException e) {e.printStackTrace();} finally {DBUtils.closeResources(rs, pstmt, conn);}return userList;
    }@Override
    public int getTotalUserCount() {String sql = "SELECT COUNT(*) FROM users";try (Connection conn = DBUtils.getConnection();PreparedStatement pstmt = conn.prepareStatement(sql);ResultSet rs = pstmt.executeQuery()) {if (rs.next()) {return rs.getInt(1); // 返回总条数}} catch (SQLException e) {e.printStackTrace();}return 0;
    }
    
  3. 在Servlet和JSP中添加分页控件(如“上一页”“下一页”按钮)。

3. 代码可维护性优化

(1)引入Service层

当业务逻辑复杂时(如“注册用户需发送验证邮件”“更新用户时同步修改关联表”),可新增Service层介于Servlet与DAO之间,集中处理业务逻辑:

  1. 定义Service接口(UserService):
    public interface UserService {boolean registerUser(User user); // 注册用户(包含用户名验证、密码哈希、邮件发送)List<User> getUsersByPage(int pageNum, int pageSize);// 其他业务方法...
    }
    
  2. 实现Service类(UserServiceImpl):
    public class UserServiceImpl implements UserService {private UserDAO userDAO = new UserDAOImpl();@Overridepublic boolean registerUser(User user) {// 1. 验证用户名是否存在if (userDAO.getUserByUsername(user.getUsername()) != null) {return false;}// 2. 哈希密码String hashedPassword = BCrypt.hashpw(user.getPassword(), BCrypt.gensalt());user.setPassword(hashedPassword);// 3. 调用DAO新增用户return userDAO.createUser(user);// 4. 发送验证邮件(扩展功能)// sendVerificationEmail(user.getEmail());}// 其他方法实现...
    }
    
  3. Servlet调用Service层:
    // UserServlet中替换DAO为Service
    private UserService userService = new UserServiceImpl();private void saveOrUpdateUser(HttpServletRequest request, HttpServletResponse response) throws IOException {// ... 参数解析 ...// 调用Service注册用户boolean success = userService.registerUser(user);// ... 后续逻辑 ...
    }
    
(2)配置文件外部化

将数据库连接参数、邮件配置等硬编码信息提取到配置文件(如/resources/db.properties),便于修改:

  1. 创建db.properties
    db.url=jdbc:mysql://localhost:3306/user_management?useSSL=false&serverTimezone=UTC
    db.username=root
    db.password=123456
    db.driver=com.mysql.cj.jdbc.Driver
    
  2. 工具类读取配置文件:
    public class PropertiesUtils {private static Properties props;static {props = new Properties();try {// 读取配置文件InputStream in = PropertiesUtils.class.getClassLoader().getResourceAsStream("db.properties");props.load(in);} catch (IOException e) {throw new RuntimeException("加载配置文件失败!", e);}}public static String getProperty(String key) {return props.getProperty(key);}
    }
    
  3. DBUtils使用配置文件参数:
    config.setJdbcUrl(PropertiesUtils.getProperty("db.url"));
    config.setUsername(PropertiesUtils.getProperty("db.username"));
    config.setPassword(PropertiesUtils.getProperty("db.password"));
    

总结

本文通过“用户管理系统”实战案例,完整讲解了JSP+Servlet+数据库的整合开发流程,核心要点可归纳为:

1. 架构分层清晰

  • 视图层(JSP):专注于页面渲染,通过EL和JSTL减少Java代码;
  • 控制器层(Servlet):接收请求、协调逻辑、转发/重定向,不处理具体业务;
  • 业务层(Service,可选):封装复杂业务逻辑,解耦Servlet与DAO;
  • 数据访问层(DAO):抽象数据库操作,屏蔽JDBC细节;
  • 模型层(POJO):封装数据,作为各层间的传递载体。

2. 核心技术点

  • Servlet:URL映射、请求处理(doGet/doPost)、转发与重定向;
  • JSP:EL表达式、JSTL标签、表单回显;
  • JDBC:PreparedStatement防注入、连接池优化;
  • 安全:密码哈希、CSRF防护;
  • 性能:分页查询、连接池复用。

3. 进阶方向

掌握基础后,可进一步学习更成熟的框架:

  • Spring MVC:替代Servlet的更强大的控制器框架,支持依赖注入、拦截器等;
  • MyBatis/Hibernate:替代JDBC的ORM框架,简化数据库操作;
  • Spring Boot:整合Spring MVC、MyBatis等框架,实现零配置快速开发。

JSP与Servlet是Java Web开发的基础,理解其核心思想和整合逻辑,能为学习更高阶的框架打下坚实基础。实际开发中,需结合业务场景灵活运用分层架构,并始终将安全和性能作为核心考量。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/95742.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/95742.shtml
英文地址,请注明出处:http://en.pswp.cn/bicheng/95742.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

设计五种算法精确的身份证号匹配

问题定义与数据准备 我们有两个Excel文件&#xff1a; small.xlsx: 包含约5,000条记录。large.xlsx: 包含约140,000条记录。 目标&#xff1a;快速、高效地从large.xlsx中找出所有其“身份证号”字段存在于small.xlsx“身份证号”字段中的记录&#xff0c;并将这些匹配的记录保…

Spring 框架(IoC、AOP、Spring Boot) 的必会知识点汇总

目录&#xff1a;&#x1f9e0; 一、Spring 框架概述1. Spring 的核心功能2. Spring 模块化结构&#x1f9e9; 二、IoC&#xff08;控制反转&#xff09;核心知识点1. IoC 的核心思想2. Bean 的定义与管理3. IoC 容器的核心接口4. Spring Bean 的创建方式&#x1f9f1; 三、AOP…

简单工厂模式(Simple Factory Pattern)​​ 详解

✅作者简介&#xff1a;大家好&#xff0c;我是 Meteors., 向往着更加简洁高效的代码写法与编程方式&#xff0c;持续分享Java技术内容。 &#x1f34e;个人主页&#xff1a; Meteors.的博客 &#x1f49e;当前专栏&#xff1a; 设计模式 ✨特色专栏&#xff1a; 知识分享 &…

新电脑硬盘如何分区?3个必知技巧避免“空间浪费症”!

刚到手的新电脑&#xff0c;硬盘就像一间空荡荡的大仓库&#xff0c;文件扔进去没多久就乱成一锅粥&#xff1f;别急&#xff0c;本文会告诉你新电脑硬盘如何分区&#xff0c;这些方法不仅可以帮你给硬盘分区&#xff0c;还可以调整/合并分区大小等。所以&#xff0c;本文的分区…

【微知】git submodule的一些用法总结(不断更新)

文章目录综述要点细节如何新增一个submodule&#xff1f;如何手动.gitmodules修改首次增加一个submodule&#xff1f;git submodule init&#xff0c;init子命令依据.gitmodules.gitmodules如何命令修改某个成员以及同步&#xff1f;如果submodule需要修改分支怎么办&#xff1…

【Spring Cloud微服务】9.一站式掌握 Seata:架构设计与 AT、TCC、Saga、XA 模式选型指南

文章目录一、Seata 框架概述二、核心功能特性三、整体架构与三大角色1. Transaction Coordinator (TC) - 事务协调器&#xff08;Seata Server&#xff09;2. Transaction Manager (TM) - 事务管理器&#xff08;集成在客户端&#xff09;3. Resource Manager (RM) - 资源管理器…

AI赋能!Playwright带飞UI自动化脚本维护

80%的自动化脚本因一次改版报废&#xff1f; 开发随意改动ID导致脚本集体崩溃&#xff1f;背景UI自动化在敏捷开发席卷行业的今天&#xff0c;UI自动化测试深陷一个尴尬困局&#xff1a;需求迭代速度&#xff08;平均2周1次&#xff09;&#xff1e; 脚本维护速度&#xff08;平…

Redis、Zookeeper 与关系型数据库分布式锁方案对比及性能优化实战指南

Redis、Zookeeper 与关系型数据库分布式锁方案对比及性能优化实战指南 1. 问题背景介绍 在分布式系统中&#xff0c;多节点并发访问共享资源时&#xff0c;如果不加锁或加锁不当&#xff0c;会导致数据不一致、超卖超买、竞态条件等问题。常见的分布式锁方案包括基于Redis、Zoo…

网络安全A模块专项练习任务十一解析

任务十一&#xff1a;IP安全协议配置任务环境说明&#xff1a; (Windows 2008)系统&#xff1a;用户名Administrator&#xff0c;密码Pssw0rd1.指定触发SYN洪水攻击保护所必须超过的TCP连接请求数阈值为5&#xff1b;使用组合键winR&#xff0c;输入regedit打开注册表编辑器&am…

金蝶中间件适配HGDB

文章目录环境文档用途详细信息环境 系统平台&#xff1a;Microsoft Windows (64-bit) 10 版本&#xff1a;5.6.5 文档用途 本文章主要介绍金蝶中间件简单适配HGDB。 详细信息 一、金蝶中间件Apusic安装与配置 1.Apusic安装与配置 Windows和Linux下安装部署过程相同。 &…

使用a标签跳转之后,会刷新一次,这个a标签添加的样式就会消失

<ul class"header-link"><li><a href"storeActive.html">到店活动</a></li><li><a href"fuwu.html">服务</a></li><li><a href"store.html">门店</a></l…

线程池实现及参数详解

线程池概述 Java线程池是一种池化技术&#xff0c;用于管理和复用线程&#xff0c;减少线程创建和销毁的开销&#xff0c;提高系统性能。Java通过java.util.concurrent包提供了强大的线程池支持。 线程池参数详解 1. 核心参数 // 创建线程池的完整构造函数 ThreadPoolExecu…

K8S 部署 NFS Dynamic Provisioning(动态存储供应)

K8S 部署 NFS Dynamic Provisioning&#xff08;动态存储供应&#xff09; 本文档提供完整的 K8s NFS 动态存储部署流程&#xff0c;包含命名空间创建、RBAC 权限配置、Provisioner 部署、StorageClass 创建及验证步骤。 2. 部署步骤 2.1 创建命名空间 首先创建独立的命名空间 …

JavaEE 进阶第二期:开启前端入门之旅(二)

专栏&#xff1a;JavaEE 进阶跃迁营 个人主页&#xff1a;手握风云 目录 一、VS Code开发工具的搭建 1.1. 创建.html文件 1.2. 安装插件 1.3. 快速生成代码 二、HTML常见标签 2.1. 换行标签 2.2. 图片标签: img 2.3. 超链接 三、表格标签 四、表单标签 4.1. input标…

【RNN-LSTM-GRU】第二篇 序列模型原理深度剖析:从RNN到LSTM与GRU

本文将深入探讨循环神经网络&#xff08;RNN&#xff09;的核心原理、其面临的长期依赖问题&#xff0c;以及两大革命性解决方案——LSTM和GRU的门控机制&#xff0c;并通过实例和代码帮助读者彻底理解其工作细节。1. 引言&#xff1a;时序建模的数学本质在上一篇概述中&#x…

Qt---状态机框架QState

QState是Qt状态机框架&#xff08;Qt State Machine Framework&#xff09;的核心类&#xff0c;用于建模离散状态以及状态间的转换逻辑&#xff0c;广泛应用于UI交互流程、设备状态管理、工作流控制等场景。它基于UML状态图规范设计&#xff0c;支持层次化状态、并行状态、历史…

GitHub 热榜项目 - 日榜(2025-09-02)

GitHub 热榜项目 - 日榜(2025-09-02) 生成于&#xff1a;2025-09-02 统计摘要 共发现热门项目&#xff1a;14 个 榜单类型&#xff1a;日榜 本期热点趋势总结 本期GitHub热榜呈现AI Agent生态爆发趋势&#xff0c;Koog、Activepieces等项目推动多平台智能体开发框架成熟。语…

华为卫星对星引导技术深度解析:原理、实现与开源替代方案

利号&#xff1a;CNXXXXXX 涉及多传感器融合/自适应波束成形/轨道预测算法一、技术原理剖析&#xff1a;卫星间高精度指向的核心挑战在低轨卫星&#xff08;LEO&#xff09;星座中&#xff0c;卫星间链路&#xff08;ISL&#xff09;的建立面临三大技术难题&#xff1a;1. 动力…

水下管道巡检机器人结构设cad+三维图+设计说明书

目 录 1 绪论 1 1.1 选题的背景及意义 1 1.2 水下管道巡检机器人的分类 2 1.2.1 管道巡检技术的分类 2 1.2.2管道巡检机器人的分类 2 1.3 研究的现状 3 1.3.1 国内的研究现状 3 1.3.2 国外的研究现状 4 1.4 水下管道巡检机器人的发展趋势 5 1.…

[从零开始面试算法] (11/100) LeetCode 226. 反转二叉树:递归的“镜像”魔法

引言 欢迎来到本系列的第十一篇&#xff01;在我们通过“最大深度”问题初步领略了树的递归之美后&#xff0c;今天我们将面对一个更能体现递归“分治”思想的经典问题——LeetCode 226. 反转二叉树。 这道题在面试界的地位非同凡响&#xff0c;它因 Homebrew 的作者 Max How…