本文記錄一個經典 Java Web 專案(Manage)從零構建的過程:五層封裝、DBHelper 通用數據訪問、實體映射、Servlet 控制器、JSP 視圖、反射工具類、調試策略與心得體會。
src/
├── dao/ # 數據訪問層
│ ├── UserDao.java
│ └── TestDao.java
├── dbhelper/ # 通用數據庫操作封裝
│ └── DBHelper.java
├── entity/ # 實體類
│ └── User.java
├── servlet/ # 控制層
│ └── UserServlet.java
├── tools/ # 工具類
│ └── ReflectUtil.java # 反射自動映射 ResultSet → Entity
└── web/
├── user.jsp # 列表/新增/編輯頁
├── show.jsp # 詳情頁
└── update.jsp # 修改頁
public class DBHelper {
private static final String URL = "jdbc:mysql://localhost:3306/manage?useSSL=false";
private static final String USER = "root";
private static final String PASS = "123456";
static { Class.forName("com.mysql.jdbc.Driver"); }
public static Connection getConn() throws SQLException {
return DriverManager.getConnection(URL, USER, PASS);
}
// 通用增刪改
public static int executeUpdate(String sql, Object... params) {
try (Connection c = getConn(); PreparedStatement ps = c.prepareStatement(sql)) {
setParams(ps, params);
return ps.executeUpdate();
}
}
// 通用查詢 + 反射映射
public static List executeQuery(String sql, Class clazz, Object... params) {
try (Connection c = getConn(); PreparedStatement ps = c.prepareStatement(sql)) {
setParams(ps, params);
try (ResultSet rs = ps.executeQuery()) {
return ReflectUtil.mapResultSet(rs, clazz);
}
}
}
private static void setParams(PreparedStatement ps, Object[] params) throws SQLException {
for (int i = 0; i < params.length; i++) ps.setObject(i+1, params[i]);
}
}
public class ReflectUtil {
public static List mapResultSet(ResultSet rs, Class clazz) throws Exception {
List list = new ArrayList<>();
ResultSetMetaData meta = rs.getMetaData();
int cols = meta.getColumnCount();
while (rs.next()) {
T obj = clazz.newInstance();
for (int i = 1; i <= cols; i++) {
String colName = meta.getColumnLabel(i);
Object val = rs.getObject(i);
// 駝峰轉換:user_name → setUserName
String methodName = "set" + toCamelCase(colName);
Method m = clazz.getMethod(methodName, val.getClass());
m.invoke(obj, val);
}
list.add(obj);
}
return list;
}
// toCamelCase 實現略
}
public class UserDao {
public List findAll() {
return DBHelper.executeQuery("SELECT * FROM t_user", User.class);
}
public User findById(int id) {
List list = DBHelper.executeQuery("SELECT * FROM t_user WHERE id=?", User.class, id);
return list.isEmpty() ? null : list.get(0);
}
public int insert(User u) {
return DBHelper.executeUpdate("INSERT INTO t_user(name,age,email) VALUES(?,?,?)", u.getName(), u.getAge(), u.getEmail());
}
public int update(User u) {
return DBHelper.executeUpdate("UPDATE t_user SET name=?,age=?,email=? WHERE id=?", u.getName(), u.getAge(), u.getEmail(), u.getId());
}
public int delete(int id) {
return DBHelper.executeUpdate("DELETE FROM t_user WHERE id=?", id);
}
}
@WebServlet("/user")
public class UserServlet extends HttpServlet {
private UserDao dao = new UserDao();
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String action = req.getParameter("action");
if ("list".equals(action)) {
req.setAttribute("users", dao.findAll());
req.getRequestDispatcher("/user.jsp").forward(req, resp);
} else if ("edit".equals(action)) {
req.setAttribute("user", dao.findById(Integer.parseInt(req.getParameter("id"))));
req.getRequestDispatcher("/update.jsp").forward(req, resp);
} else if ("delete".equals(action)) {
dao.delete(Integer.parseInt(req.getParameter("id")));
resp.sendRedirect("user?action=list");
} else {
resp.sendRedirect("user?action=list");
}
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
req.setCharacterEncoding("UTF-8");
String action = req.getParameter("action");
User u = new User();
u.setName(req.getParameter("name"));
u.setAge(Integer.parseInt(req.getParameter("age")));
u.setEmail(req.getParameter("email"));
if ("add".equals(action)) {
dao.insert(u);
} else if ("update".equals(action)) {
u.setId(Integer.parseInt(req.getParameter("id")));
dao.update(u);
}
resp.sendRedirect("user?action=list");
}
}
UserDao 立即在 TestDao.main() 單元測試 CRUD,確認 SQL 與映射無誤。action 參數,部署訪問確認 URL 映射正確。catalina.out 堆疊 → 定位到具體行號 → 修復 → 熱部署驗證。rs.getString() 但引入性能開銷與類型匹配風險,生產環境建議緩存 Method 對象或改用 MyBatis。db.properties,DBHelper 讀取配置文件而非硬編碼。request.setCharacterEncoding("UTF-8")、JSP pageEncoding="UTF-8"、MySQL characterEncoding=UTF-8、Tomcat URIEncoding="UTF-8" 四位一體。DBHelper 單條 SQL 自動提交,多表操作需引入 Connection.setAutoCommit(false) 手動事務管理。這個「手工構建 ORM」的練習,讓人深刻理解框架背後的原理:連接池、PreparedStatement、ResultSet 映射、異常轉換、事務邊界。建議後續引入 MyBatis + Spring,將樣板代碼交給框架,專注業務邏輯創新。