Servlet 数据库访问(手把手讲解)
💡一则或许对你有用的小广告
欢迎加入小哈的星球 ,你将获得:专属的项目实战 / 1v1 提问 / Java 学习路线 / 学习打卡 / 每月赠书 / 社群讨论
- 新项目:《从零手撸:仿小红书(微服务架构)》 正在持续爆肝中,基于
Spring Cloud Alibaba + Spring Boot 3.x + JDK 17...
,点击查看项目介绍 ;- 《从零手撸:前后端分离博客项目(全栈开发)》 2 期已完结,演示链接: http://116.62.199.48/ ;
截止目前, 星球 内专栏累计输出 82w+ 字,讲解图 3441+ 张,还在持续爆肝中.. 后续还会上新更多项目,目标是将 Java 领域典型的项目都整一波,如秒杀系统, 在线商城, IM 即时通讯,权限管理,Spring Cloud Alibaba 微服务等等,已有 2900+ 小伙伴加入学习 ,欢迎点击围观
在 Web 应用开发中,Servlet 数据库访问是连接前端交互与后端数据存储的核心环节。无论是用户注册、订单查询还是实时数据统计,都需要通过 Servlet 与数据库高效通信。对于初学者而言,理解这一流程如同掌握“桥梁建造的技巧”——既要熟悉建筑材料(如 JDBC、连接池),又要确保结构稳固(如事务、安全防护)。本文将从基础概念讲起,逐步拆解实现方法,并通过完整案例演示具体操作,帮助开发者构建稳健的数据交互能力。
一、Servlet 数据库访问的底层逻辑
1.1 Servlet 与数据库的角色分工
Servlet 是 Java Web 开发中的“信使”,负责接收 HTTP 请求并处理业务逻辑;而数据库则是“数据仓库”,存储用户信息、商品库存等关键数据。两者通过 JDBC(Java Database Connectivity)这一“翻译官”实现通信。
1.2 核心流程图解
以用户登录为例,流程可简化为以下步骤:
- 用户提交用户名和密码 → Servlet 收到请求
- Servlet 通过 JDBC 连接数据库
- 执行 SQL 查询验证用户凭证
- 返回结果 → Servlet 构建响应返回给前端
这一过程类似于快递员(Servlet)将包裹(请求参数)送达仓库(数据库),仓库返回签收状态(查询结果)的过程。
二、环境搭建与基础配置
2.1 必备工具与依赖
2.1.1 数据库驱动
以 MySQL 为例,需在项目中添加 mysql-connector-java
依赖。Maven 配置如下:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.26</version>
</dependency>
2.1.2 连接参数配置
通常将数据库 URL、用户名、密码等敏感信息存入 application.properties
文件,避免硬编码:
db.url=jdbc:mysql://localhost:3306/mydb?useSSL=false
db.username=root
db.password=123456
三、实现步骤详解
3.1 获取数据库连接
3.1.1 简单连接方式
通过 DriverManager
动态加载驱动并建立连接:
// 读取配置文件
String url = "jdbc:mysql://localhost:3306/mydb";
String user = "root";
String password = "123456";
// 获取连接
Connection conn = DriverManager.getConnection(url, user, password);
比喻:这就像拨打特定电话号码(URL)联系客服(数据库),并提供身份验证(用户名密码)。
3.1.2 连接池优化
频繁创建连接会导致性能下降。使用 HikariCP 连接池可复用连接,如同“共享快递车”提升效率:
HikariConfig config = new HikariConfig();
config.setJdbcUrl(url);
config.setUsername(user);
config.setPassword(password);
HikariDataSource dataSource = new HikariDataSource(config);
// 后续通过 dataSource.getConnection() 获取连接
3.2 执行 SQL 语句
3.2.1 查询操作示例
String sql = "SELECT * FROM users WHERE username = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username); // 参数化查询防 SQL 注入
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
String storedPassword = rs.getString("password");
// 验证密码逻辑
}
安全提示:?
占位符如同“过滤网”,防止恶意输入(如 ' OR '1'='1
)破坏查询逻辑。
3.2.2 更新操作示例(如注册用户)
String insertSql = "INSERT INTO users (username, password) VALUES (?, ?)";
PreparedStatement insertStmt = conn.prepareStatement(insertSql);
insertStmt.setString(1, username);
insertStmt.setString(2, hashedPassword);
int affectedRows = insertStmt.executeUpdate(); // 返回影响行数
3.3 结果集处理与资源释放
3.3.1 封装数据对象
将查询结果映射为 Java 对象,提升可读性:
public class User {
private String username;
private String email;
// 省略构造方法与Getter/Setter
}
// 在查询后构建对象
User user = new User(rs.getString("username"), rs.getString("email"));
3.3.2 资源管理最佳实践
使用 try-with-resources
自动关闭资源,避免内存泄漏:
try (Connection conn = dataSource.getConnection();
PreparedStatement pstmt = conn.prepareStatement(sql);
ResultSet rs = pstmt.executeQuery()) {
// 业务逻辑
} catch (SQLException e) {
e.printStackTrace();
}
四、事务管理与异常处理
4.1 事务的原子性保障
事务如同“银行转账操作”——若从 A 账户转出 100 元,必须同时成功转入 B 账户,否则全部回滚。
conn.setAutoCommit(false); // 关闭自动提交
try {
// 执行多个 SQL 操作
updateBalance("A", -100);
updateBalance("B", +100);
conn.commit(); // 提交事务
} catch (Exception e) {
conn.rollback(); // 回滚操作
throw e;
} finally {
conn.setAutoCommit(true); // 恢复默认状态
}
4.2 异常捕获与日志记录
catch (SQLException e) {
logger.error("数据库操作失败: {}", e.getMessage(), e);
throw new RuntimeException("服务器繁忙,请稍后再试");
}
五、性能优化与安全加固
5.1 连接池与预编译语句
- 连接池:如 HikariCP 可复用连接,避免每次创建新连接的开销。
- 预编译语句(
PreparedStatement
):将 SQL 语句预编译为数据库内的存储对象,后续执行更快。
5.2 SQL 注入防护
永远避免拼接 SQL 字符串:
// 错误写法(易受攻击)
String dangerSql = "SELECT * FROM users WHERE username = '" + username + "'";
// 正确写法(参数化查询)
String safeSql = "SELECT * FROM users WHERE username = ?";
PreparedStatement pstmt = conn.prepareStatement(safeSql);
pstmt.setString(1, username);
六、实战案例:用户登录功能
6.1 Servlet 代码实现
@WebServlet("/login")
public class LoginServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String username = request.getParameter("username");
String password = request.getParameter("password");
try (Connection conn = DBUtil.getConnection();
PreparedStatement pstmt = conn.prepareStatement(
"SELECT password FROM users WHERE username = ?")) {
pstmt.setString(1, username);
ResultSet rs = pstmt.executeQuery();
if (rs.next()) {
String storedHash = rs.getString("password");
if (BCrypt.checkpw(password, storedHash)) {
request.getSession().setAttribute("user", username);
response.sendRedirect("/success");
} else {
request.setAttribute("error", "密码错误");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
} else {
request.setAttribute("error", "用户不存在");
request.getRequestDispatcher("/login.jsp").forward(request, response);
}
} catch (SQLException e) {
e.printStackTrace();
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
}
}
}
6.2 关键点解析
- 密码加密:使用 BCrypt 加密存储密码,而非明文。
- 会话管理:登录成功后将用户信息存入 Session,实现身份保持。
- 错误处理:区分用户不存在、密码错误等场景,避免信息泄露。
结论
Servlet 数据库访问是 Web 开发的基石,其核心在于“安全、高效、可维护”的代码设计。本文通过分步讲解 JDBC 原理、连接池优化、事务管理及实战案例,帮助开发者构建从基础到进阶的完整认知体系。建议读者通过以下步骤实践:
- 完成一个简单的 CRUD(增删改查)项目
- 对比分析不同数据库连接方式的性能差异
- 尝试使用 MyBatis 等 ORM 框架简化代码
掌握这些技能后,您将能够更从容地应对 Web 开发中复杂的业务场景,为构建高并发、高可靠的系统打下坚实基础。