本文通過一個完整的學生信息增刪改查 (CRUD) 系統,演示基於 JDBC、Servlet、JSP 的傳統 Java Web MVC 架構開發全流程。涵蓋數據庫設計、分層架構、DAO 實現、Servlet 控制器、JSP 視圖及常見問題排查,適合 Java Web 初學者鞏固基礎。
單表 student,字段:
| 字段 | 類型 | 說明 |
|---|---|---|
| id | INT PRIMARY KEY | 學號 |
| name | VARCHAR(50) | 姓名 |
| age | INT | 年齡 |
| class | VARCHAR(50) | 班級 |
CREATE TABLE student ( id INT PRIMARY KEY, name VARCHAR(50) NOT NULL, age INT NOT NULL, class VARCHAR(50) );
com.mm.bean.Student — POJO,封裝數據。com.mm.Dao.IStuDao — 定義 CRUD 契約。com.mm.Dao.impl.StuDaoImpl — JDBC 具體操作。com.mm.db.JDBCUtils — 連接獲取與資源釋放。com.mm.sevlet.studentServlet — 接收請求、調度業務、轉發/重定向視圖。package com.mm.bean;
public class Student {
private int id;
private String name;
private int age;
private String classes; // 注意:數據庫列名 class 是關鍵字,實體用 classes
// 構造器、Getter/Setter 省略
}
package com.mm.Dao;
import com.mm.bean.Student;
import java.util.List;
public interface IStuDao {
void addstu(Student stu);
void updatestu(Student stu);
void delstu(int id);
List findall();
Student findStubyId(int id);
}
package com.mm.db;
import java.sql.*;
public class JDBCUtils {
private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC";
private static final String USER = "mxning";
private static final String PASS = "mxning";
static { try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) { throw new ExceptionInInitializerError(e); } }
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(URL, USER, PASS);
}
public static void release(ResultSet rs, PreparedStatement ps, Connection con) {
closeQuietly(rs); closeQuietly(ps); closeQuietly(con);
}
private static void closeQuietly(AutoCloseable c) {
if (c != null) try { c.close(); } catch (Exception ignored) {}
}
}
package com.mm.Dao.impl;
import com.mm.Dao.IStuDao;
import com.mm.bean.Student;
import com.mm.db.JDBCUtils;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
public class StuDaoImpl implements IStuDao {
@Override public void addstu(Student stu) {
String sql = "INSERT INTO student (id, name, age, class) VALUES (?, ?, ?, ?)";
executeUpdate(sql, stu.getId(), stu.getName(), stu.getAge(), stu.getClasses());
}
@Override public void updatestu(Student stu) {
String sql = "UPDATE student SET name=?, age=?, class=? WHERE id=?";
executeUpdate(sql, stu.getName(), stu.getAge(), stu.getClasses(), stu.getId());
}
@Override public void delstu(int id) {
executeUpdate("DELETE FROM student WHERE id=?", id);
}
@Override public List findall() {
String sql = "SELECT * FROM student";
List list = new ArrayList<>();
try (Connection con = JDBCUtils.getConnection();
PreparedStatement ps = con.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) list.add(mapRow(rs));
} catch (SQLException e) { throw new RuntimeException(e); }
return list;
}
@Override public Student findStubyId(int id) {
String sql = "SELECT * FROM student WHERE id=?";
try (Connection con = JDBCUtils.getConnection();
PreparedStatement ps = con.prepareStatement(sql)) {
ps.setInt(1, id);
try (ResultSet rs = ps.executeQuery()) {
if (rs.next()) return mapRow(rs);
}
} catch (SQLException e) { throw new RuntimeException(e); }
return null;
}
private void executeUpdate(String sql, Object... params) {
try (Connection con = JDBCUtils.getConnection();
PreparedStatement ps = con.prepareStatement(sql)) {
for (int i = 0; i < params.length; i++) ps.setObject(i + 1, params[i]);
ps.executeUpdate();
} catch (SQLException e) { throw new RuntimeException(e); }
}
private Student mapRow(ResultSet rs) throws SQLException {
Student s = new Student();
s.setId(rs.getInt("id"));
s.setName(rs.getString("name"));
s.setAge(rs.getInt("age"));
s.setClasses(rs.getString("class"));
return s;
}
}
package com.mm.sevlet;
@WebServlet("/studentServlet")
public class StudentServlet extends HttpServlet {
private IStuDao dao = new StuDaoImpl();
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
String op = req.getParameter("op");
if (op == null) op = "list";
switch (op) {
case "list":
req.setAttribute("list", dao.findall());
req.getRequestDispatcher("studentlist.jsp").forward(req, resp);
break;
case "add":
resp.sendRedirect("studentadd.jsp");
break;
case "edit":
int id = Integer.parseInt(req.getParameter("id"));
req.setAttribute("stu", dao.findStubyId(id));
String cmd = req.getParameter("command");
if ("view".equals(cmd))
req.getRequestDispatcher("studentedit.jsp").forward(req, resp);
else
resp.sendRedirect("studentview.jsp?id=" + id);
break;
case "delete":
dao.delstu(Integer.parseInt(req.getParameter("id")));
resp.sendRedirect("studentServlet?op=list");
break;
case "store":
String idStr = req.getParameter("id");
Student s = new Student();
s.setName(req.getParameter("name"));
s.setAge(Integer.parseInt(req.getParameter("age")));
s.setClasses(req.getParameter("class"));
if (idStr == null || idStr.isEmpty()) dao.addstu(s);
else { s.setId(Integer.parseInt(idStr)); dao.updatestu(s); }
resp.sendRedirect("studentServlet?op=list");
break;
}
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { doGet(req, resp); }
}
<c:forEach items="${list}" var="st"> 遍歷顯示,操作列提供編輯/刪除鏈接。studentServlet?op=store。id,表單回顯現有數據,提交同 add。完整 JSP 代碼請參考源文件,核心在於 EL 表達式 ${stu.name} 取值與表單 action 映射。
| 問題 | 原因 | 改進 |
|---|---|---|
| 中文亂碼 | 請求/響應編碼未統一、DB 連接無 characterEncoding | Filter 統一 UTF-8、URL 加 ?characterEncoding=UTF-8 |
| 資源洩漏 | 手動 close 易漏、異常拋出前未關閉 | JDK 7+ try-with-resources 或連接池 (Druid/HikariCP) |
| SQL 注入風險 | 字符串拼接 SQL(如 findStubyId 初版) | 全面使用 PreparedStatement 佔位符 ? |
| Servlet 線程不安全 | 實例變量存狀態 | 無狀態設計,DAO 可作為單例注入 |
| 代碼重複 | JDBC 模板代碼多 | 封裝 BaseDao / 使用 Spring JdbcTemplate / MyBatis |
| 前後端耦合 | JSP 混合 Java 代碼、業務邏輯 | 引入模板引擎或前後端分離 |
本案例完整展示了 Java Web 基礎開發全棧:JDBC 底層數據訪問、Servlet 請求分發、JSP 視圖渲染、MVC 分層思想。雖然生產環境多用 Spring Boot + MyBatis + 前端框架,但理解這套原生實現機制,對掌握框架原理、排查疑難雜症、進行技術選型至關重要。建議初學者手敲一遍,體會「約定優於配置」框架背後的艱辛與智慧。