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 │
│ (视图) │
└─────────┘
各组件核心作用解析
- 客户端(浏览器):用户交互入口,负责发送HTTP请求(如GET/POST),并接收服务器返回的HTML/CSS/JS响应进行渲染。
- Servlet(控制器):
- 接收客户端请求,解析请求参数(如表单数据、URL参数);
- 调用DAO层执行数据库操作(如查询用户、新增数据);
- 将处理结果封装为“请求属性”(Request Attribute),转发至JSP视图;
- 或通过重定向(Redirect)引导客户端跳转至其他页面(如新增用户后跳转至列表页)。
- JSP(视图):
- 以HTML为基础,通过EL表达式(${}) 读取Servlet传递的请求属性;
- 借助JSTL标签库(如
<c:forEach>
)实现循环、条件判断等动态逻辑; - 最终生成完整的HTML页面,通过Servlet返回给客户端。
- 数据库:持久化存储应用数据(如用户信息),通过JDBC与Servlet层交互。
- JDBC(Java Database Connectivity):Java访问数据库的标准API,提供了连接数据库、执行SQL语句、处理结果集的统一接口,屏蔽了不同数据库的底层差异。
环境准备
在开始编码前,需完成开发环境与项目结构的搭建。以下是详细的准备步骤:
1. 所需技术与版本建议
选择稳定且主流的版本组合,避免因版本兼容问题导致开发受阻:
技术组件 | 推荐版本 | 核心作用 |
---|---|---|
Java SE | JDK 11+ | 基础开发语言,Servlet/JSP的运行依赖 |
Servlet容器 | Tomcat 10+ | 运行Servlet/JSP的服务器(兼容Servlet 5.0+) |
JSP | 2.3+ | 动态视图生成 |
JDBC | 4.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()方法,便于调试时打印用户信息
@Override
public 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. 新增用户
@Override
public 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. 查询所有用户
@Override
public List<User> getAllUsers() {
String sql = "SELECT id, username, email, password, created_at FROM users ORDER BY created_at DESC";
List<User> userList = new ArrayList<>(); // 初始化空列表,避免返回null
Connection 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查询用户
@Override
public 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. 根据用户名查询用户
@Override
public 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. 更新用户信息
@Override
public 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删除用户
@Override
public 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的优势:
- 防SQL注入:通过占位符(?)绑定参数,而非拼接SQL字符串(如
"INSERT INTO users VALUES ('" + username + "')"
),避免恶意用户输入' OR 1=1 --
等注入语句; - 预编译优化:SQL语句仅编译一次,多次执行时可复用,提高效率。
- 防SQL注入:通过占位符(?)绑定参数,而非拼接SQL字符串(如
- ResultSet处理:
rs.next()
移动游标至下一条记录,rs.getInt("id")
按字段名获取值(比按索引rs.getInt(1)
更易维护,字段顺序变化不影响)。 - 异常处理:捕获
SQLException
后打印堆栈信息便于调试,同时针对常见错误(如1062:唯一约束冲突)可添加特殊处理逻辑。
控制器层(Servlet):处理请求与协调逻辑
Servlet是Java Web的“控制器”核心,负责接收客户端请求、调用DAO/Service层处理业务、并将结果转发至JSP视图。其生命周期由Servlet容器(如Tomcat)管理,分为三个阶段:
- 初始化(init()):Servlet首次被访问时执行,用于初始化资源(如创建DAO实例);
- 服务(service()):每次接收请求时执行,根据请求方式(GET/POST)调用
doGet()
或doPost()
; - 销毁(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实例等资源
*/
@Override
public void init() throws ServletException {
super.init();
// 实例化DAO实现类(实际开发中可使用依赖注入框架如Spring管理)
userDAO = new UserDAOImpl();
}
/**
* 处理GET请求:通常用于查询操作(如查看列表、查看详情)
*/
@Override
protected 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(),统一处理逻辑(也可单独实现)
*/
@Override
protected 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"); // 编辑时存在,新增时为null
String 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()) {
// 编辑操作:设置id
user.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()
,自动从pageContext
、request
、session
、application
作用域中查找属性;${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. 新增用户核心步骤详解
- 用户提交表单:在
user-form.jsp
中填写用户名、邮箱、密码,点击“提交”,浏览器发送POST /users?action=create
请求; - Servlet接收请求:
UserServlet.doPost()
被调用,解析表单参数(username
、email
、password
); - 参数验证与封装:验证参数非空,检查用户名是否已存在,封装为
User
对象; - DAO执行插入:
UserServlet
调用UserDAO.createUser()
,DAO通过JDBC执行INSERT
语句; - 重定向至列表页:插入成功后,通过
response.sendRedirect()
引导浏览器发起GET /users?action=list
请求; - 查询并展示列表:
UserServlet
调用UserDAO.getAllUsers()
获取所有用户,将userList
设置为请求属性,转发至user-list.jsp
; - JSP渲染页面:
user-list.jsp
通过JSTL和EL表达式遍历userList
,生成包含所有用户的HTML页面,返回给浏览器。
最佳实践与进阶优化
基础实现完成后,需从安全、性能、可维护性三个维度进行优化,使应用达到生产级标准。
1. 安全优化(重中之重)
(1)密码哈希存储(避免明文)
明文存储密码是严重的安全隐患,需使用BCrypt等算法对密码进行哈希处理(哈希是单向过程,无法反向破解)。
实现步骤:
- 添加BCrypt依赖:
<dependency> <groupId>org.mindrot</groupId> <artifactId>jbcrypt</artifactId> <version>0.4</version> </dependency>
- 新增/更新用户时哈希密码:
// 替换UserServlet.saveOrUpdateUser()中的密码设置 // 生成盐值(自动包含在哈希结果中,无需单独存储) String hashedPassword = BCrypt.hashpw(password, BCrypt.gensalt()); user.setPassword(hashedPassword);
- 登录验证时匹配密码(扩展功能):
// 从数据库查询用户 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(跨站请求伪造)攻击者诱导用户在已登录状态下执行非本意的操作(如删除用户)。防护方案:
- 在Session中生成随机CSRF Token;
- 在表单中添加隐藏字段
<input type="hidden" name="csrfToken" value="${sessionScope.csrfToken}">
; - 在Servlet中验证请求中的Token与Session中的Token是否一致。
2. 性能优化
(1)使用数据库连接池
频繁创建/关闭数据库连接会消耗大量资源,连接池通过预先创建一定数量的连接并复用,大幅提升性能。推荐使用HikariCP(目前性能最优的连接池)。
实现步骤:
- 添加HikariCP依赖:
<dependency> <groupId>com.zaxxer</groupId> <artifactId>HikariCP</artifactId> <version>5.0.1</version> </dependency>
- 修改
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()
会加载所有数据,导致内存溢出和页面卡顿。需实现分页查询:
- 修改
UserDAO
接口:// 分页查询用户 List<User> getUsersByPage(int pageNum, int pageSize); // 查询总用户数(用于计算总页数) int getTotalUserCount();
- 实现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; }
- 在Servlet和JSP中添加分页控件(如“上一页”“下一页”按钮)。
3. 代码可维护性优化
(1)引入Service层
当业务逻辑复杂时(如“注册用户需发送验证邮件”“更新用户时同步修改关联表”),可新增Service层介于Servlet与DAO之间,集中处理业务逻辑:
- 定义Service接口(
UserService
):public interface UserService { boolean registerUser(User user); // 注册用户(包含用户名验证、密码哈希、邮件发送) List<User> getUsersByPage(int pageNum, int pageSize); // 其他业务方法... }
- 实现Service类(
UserServiceImpl
):public class UserServiceImpl implements UserService { private UserDAO userDAO = new UserDAOImpl(); @Override public 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()); } // 其他方法实现... }
- 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
),便于修改:
- 创建
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
- 工具类读取配置文件:
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); } }
- 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开发的基础,理解其核心思想和整合逻辑,能为学习更高阶的框架打下坚实基础。实际开发中,需结合业务场景灵活运用分层架构,并始终将安全和性能作为核心考量。