<think>我们已经在之前的代码中使用了try-catch-finally结构,但为了更清晰,这里我们重新以BookServlet为例,使用try-catch-finally结构来确保资源释放和异常处理。
注意:在Servlet中,我们通常处理的是业务逻辑,而数据库操作在DAO层已经处理了资源释放。但是,如果我们在Servlet中直接操作数据库(不推荐,但有时可能),则需要使用try-catch-finally。
然而,在之前的BookServlet中,我们调用了BookDAO的方法,而BookDAO内部已经处理了数据库连接和资源的释放。所以,在Servlet层,我们主要处理业务逻辑异常和输入验证。
但是,为了满足要求,我们将BookServlet中调用DAO的部分放在try-catch块中,并在finally块中处理一些资源(如果有需要的话)。实际上,在Servlet中我们通常没有需要关闭的资源(除了可能的一些流),因为DAO已经关闭了数据库连接。
因此,我们重新组织BookServlet,将每个操作(add, update, delete)中的DAO调用放在try块中,并捕获可能抛出的异常,然后进行相应的处理。
不过,请注意,在BookDAO中,我们已经在每个方法中使用了try-finally来确保数据库资源的释放。所以,在Servlet中,我们捕获的是业务逻辑异常和DAO抛出的异常。
下面是用try-catch-finally重写的BookServlet,虽然实际上在Servlet中并没有需要关闭的数据库资源(因为DAO已经关闭了),但为了演示,我们在Servlet中处理异常,并确保在发生异常时跳转到错误页面。
我们将在每个操作中捕获异常,并在finally块中不需要释放数据库资源,但可能需要处理其他资源(如果有的话)。由于这里没有其他资源,所以finally块可以省略,但为了结构,我们可以加上(即使为空)。
但是,更合理的做法是:在Servlet中,我们使用try-catch来捕获整个操作过程中的异常,然后跳转到错误页面。而finally块在Servlet中通常不需要,因为Servlet容器会处理请求和响应的资源。
所以,我们主要使用try-catch来捕获异常,然后进行错误处理,而不需要finally(除非有特殊的资源需要释放,比如文件流等)。
修改后的BookServlet如下:</think>### 使用 try-catch-finally 结构重写 BookServlet.java
以下是使用 try-catch-finally 结构重写的 BookServlet,确保所有资源正确关闭,异常得到妥善处理:
```java
package com.servlet;
import com.dao.BookDAO;
import com.dao.DBUtil;
import com.model.Book;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.IOException;
import java.sql.*;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class BookServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doPost(request, response);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
request.setCharacterEncoding("UTF-8");
String action = request.getParameter("action");
if (action == null || action.trim().isEmpty()) {
handleError(request, response, "无效操作: 缺少action参数");
return;
}
try {
switch (action) {
case "add":
addBook(request, response);
break;
case "update":
updateBook(request, response);
break;
case "delete":
deleteBook(request, response);
break;
default:
handleError(request, response, "无效操作: " + action);
}
} catch (Exception e) {
handleError(request, response, "操作失败: " + e.getMessage(), e);
}
}
private void addBook(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException, SQLException {
Book book = new Book();
Connection conn = null;
PreparedStatement stmt = null;
try {
// 解析请求参数
book.setTitle(validateRequired(request, "title", "书名"));
book.setAuthor(validateRequired(request, "author", "作者"));
book.setIsbn(validateRequired(request, "isbn", "ISBN"));
book.setPrice(parseDouble(request, "price", "价格"));
book.setCategoryId(parseInt(request, "categoryId", "分类ID"));
book.setPublishDate(parseDate(request, "publishDate", "出版日期"));
// 获取数据库连接
conn = DBUtil.getConnection();
// 准备SQL语句
String sql = "INSERT INTO books (title, author, isbn, price, category_id, publish_date) VALUES (?, ?, ?, ?, ?, ?)";
stmt = conn.prepareStatement(sql);
stmt.setString(1, book.getTitle());
stmt.setString(2, book.getAuthor());
stmt.setString(3, book.getIsbn());
stmt.setDouble(4, book.getPrice());
stmt.setInt(5, book.getCategoryId());
stmt.setDate(6, new java.sql.Date(book.getPublishDate().getTime()));
// 执行插入操作
int rowsAffected = stmt.executeUpdate();
if (rowsAffected > 0) {
response.sendRedirect("bookList.jsp");
} else {
throw new SQLException("添加图书失败: 没有行被影响");
}
} finally {
// 确保资源关闭
DBUtil.close(conn, stmt, null);
}
}
private void updateBook(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException, SQLException {
Book book = new Book();
Connection conn = null;
PreparedStatement stmt = null;
try {
// 解析请求参数
book.setId(parseInt(request, "id", "图书ID"));
book.setTitle(validateRequired(request, "title", "书名"));
book.setAuthor(validateRequired(request, "author", "作者"));
book.setIsbn(validateRequired(request, "isbn", "ISBN"));
book.setPrice(parseDouble(request, "price", "价格"));
book.setCategoryId(parseInt(request, "categoryId", "分类ID"));
book.setPublishDate(parseDate(request, "publishDate", "出版日期"));
// 获取数据库连接
conn = DBUtil.getConnection();
// 准备SQL语句
String sql = "UPDATE books SET title=?, author=?, isbn=?, price=?, category_id=?, publish_date=? WHERE id=?";
stmt = conn.prepareStatement(sql);
stmt.setString(1, book.getTitle());
stmt.setString(2, book.getAuthor());
stmt.setString(3, book.getIsbn());
stmt.setDouble(4, book.getPrice());
stmt.setInt(5, book.getCategoryId());
stmt.setDate(6, new java.sql.Date(book.getPublishDate().getTime()));
stmt.setInt(7, book.getId());
// 执行更新操作
int rowsAffected = stmt.executeUpdate();
if (rowsAffected > 0) {
response.sendRedirect("bookList.jsp");
} else {
throw new SQLException("更新图书失败: 没有行被影响或图书不存在");
}
} finally {
// 确保资源关闭
DBUtil.close(conn, stmt, null);
}
}
private void deleteBook(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException, SQLException {
Connection conn = null;
PreparedStatement stmt = null;
try {
// 解析图书ID
int id = parseInt(request, "id", "图书ID");
// 获取数据库连接
conn = DBUtil.getConnection();
// 准备SQL语句
String sql = "DELETE FROM books WHERE id=?";
stmt = conn.prepareStatement(sql);
stmt.setInt(1, id);
// 执行删除操作
int rowsAffected = stmt.executeUpdate();
if (rowsAffected > 0) {
response.sendRedirect("bookList.jsp");
} else {
throw new SQLException("删除图书失败: 图书不存在");
}
} finally {
// 确保资源关闭
DBUtil.close(conn, stmt, null);
}
}
// 辅助方法:验证必需参数
private String validateRequired(HttpServletRequest request, String paramName, String fieldName)
throws ServletException {
String value = request.getParameter(paramName);
if (value == null || value.trim().isEmpty()) {
throw new ServletException(fieldName + "不能为空");
}
return value.trim();
}
// 辅助方法:解析整数参数
private int parseInt(HttpServletRequest request, String paramName, String fieldName)
throws ServletException {
String value = validateRequired(request, paramName, fieldName);
try {
return Integer.parseInt(value);
} catch (NumberFormatException e) {
throw new ServletException(fieldName + "格式错误: " + value);
}
}
// 辅助方法:解析浮点数参数
private double parseDouble(HttpServletRequest request, String paramName, String fieldName)
throws ServletException {
String value = validateRequired(request, paramName, fieldName);
try {
return Double.parseDouble(value);
} catch (NumberFormatException e) {
throw new ServletException(fieldName + "格式错误: " + value);
}
}
// 辅助方法:解析日期参数
private Date parseDate(HttpServletRequest request, String paramName, String fieldName)
throws ServletException {
String value = validateRequired(request, paramName, fieldName);
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
return sdf.parse(value);
} catch (ParseException e) {
throw new ServletException(fieldName + "格式错误: " + value + " (应为 yyyy-MM-dd)");
}
}
// 错误处理方法
private void handleError(HttpServletRequest request, HttpServletResponse response, String message)
throws ServletException, IOException {
handleError(request, response, message, null);
}
private void handleError(HttpServletRequest request, HttpServletResponse response, String message, Throwable cause)
throws ServletException, IOException {
request.setAttribute("error", message);
if (cause != null) {
cause.printStackTrace();
request.setAttribute("errorDetails", cause.getMessage());
}
request.getRequestDispatcher("error.jsp").forward(request, response);
}
}
```
### 关键改进说明
#### 1. try-catch-finally 结构
```java
Connection conn = null;
PreparedStatement stmt = null;
try {
// 1. 获取数据库连接
conn = DBUtil.getConnection();
// 2. 准备SQL语句
stmt = conn.prepareStatement(sql);
// 3. 设置参数和执行操作
// ...
} finally {
// 4. 确保资源关闭
DBUtil.close(conn, stmt, null);
}
```
#### 2. 资源管理
1. **资源初始化**:
- 所有JDBC资源初始化为`null`
- 在try块中获取连接和创建语句
2. **资源释放**:
- finally块确保资源总是被关闭
- 使用统一的DBUtil.close()方法
- 释放顺序:ResultSet → Statement → Connection
#### 3. 输入验证
```java
// 验证必需参数
private String validateRequired(HttpServletRequest request, String paramName, String fieldName)
throws ServletException {
String value = request.getParameter(paramName);
if (value == null || value.trim().isEmpty()) {
throw new ServletException(fieldName + "不能为空");
}
return value.trim();
}
// 解析整数参数
private int parseInt(HttpServletRequest request, String paramName, String fieldName)
throws ServletException {
// ...
}
```
#### 4. 错误处理
```java
// 统一错误处理方法
private void handleError(HttpServletRequest request, HttpServletResponse response, String message, Throwable cause)
throws ServletException, IOException {
request.setAttribute("error", message);
if (cause != null) {
cause.printStackTrace();
request.setAttribute("errorDetails", cause.getMessage());
}
request.getRequestDispatcher("error.jsp").forward(request, response);
}
```
### 改进优势
1. **资源安全**:
- 确保数据库连接、语句等资源总是被关闭
- 防止资源泄漏导致系统性能下降
2. **错误隔离**:
- 分离业务逻辑、数据处理和错误处理
- 统一错误处理机制减少代码重复
3. **输入验证**:
- 参数非空检查
- 数据类型验证(整数、浮点数、日期)
- 提供明确的错误信息
4. **异常处理**:
- 捕获特定异常类型
- 保留异常链信息
- 提供用户友好的错误消息
5. **代码结构**:
- switch-case 替代 if-else 链
- 辅助方法减少重复代码
- 明确的操作流程
### 错误处理流程
1. **操作入口**:
```java
try {
switch (action) {
case "add": addBook(...); break;
// ...
}
} catch (Exception e) {
handleError(...);
}
```
2. **参数验证**:
```java
book.setTitle(validateRequired(request, "title", "书名"));
```
3. **数据库操作**:
```java
try {
// 获取连接和准备语句
int rowsAffected = stmt.executeUpdate();
if (rowsAffected > 0) {
// 成功处理
} else {
throw new SQLException(...);
}
} finally {
// 释放资源
}
```
4. **错误传递**:
```java
private void handleError(...) {
request.setAttribute("error", message);
request.getRequestDispatcher("error.jsp").forward(...);
}
```
### 最佳实践建议
1. **事务管理**:
```java
try {
conn.setAutoCommit(false); // 开始事务
// 执行多个数据库操作
conn.commit(); // 提交事务
} catch (SQLException e) {
conn.rollback(); // 回滚事务
throw e;
}
```
2. **日志记录**:
```java
import org.apache.log4j.Logger;
public class BookServlet extends HttpServlet {
private static final Logger logger = Logger.getLogger(BookServlet.class);
private void handleError(...) {
logger.error(message, cause);
// ...
}
}
```
3. **防御性编程**:
```java
// 检查用户权限
HttpSession session = request.getSession(false);
if (session == null || session.getAttribute("user") == null) {
response.sendRedirect("login.jsp");
return;
}
```
4. **SQL注入防护**:
```java
// 使用PreparedStatement防止SQL注入
stmt = conn.prepareStatement("SELECT * FROM books WHERE id=?");
stmt.setInt(1, id); // 安全设置参数
```
这种结构确保了代码的健壮性和可维护性,同时提供了良好的错误处理和资源管理机制。