Servlet 是 Java Web 開發的基石,Spring MVC、Struts 等框架底層皆封裝於此。本文針對初學者,記錄在 Eclipse (或 IDEA) 中搭建 **Servlet + MySQL + Tomcat 9/10** 開發環境的完整步驟:環境配置、專案建立、資料庫準備、JDBC 封裝、Servlet CRUD 實作、部署驗證,並記錄常見坑點與解決方案。
將 `mysql-connector-j-8.x.x.jar` 複製到 `WEB-INF/lib` 目錄 (Eclipse 會自動加入 Build Path)。或使用 Maven 專案管理依賴 (推薦)。
CREATE DATABASE servlet_demo CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE servlet_demo;
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(100) NOT NULL,
email VARCHAR(100),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO users (username, password, email) VALUES
('admin', '123456', 'admin@example.com'),
('test', 'abc123', 'test@example.com');
public class DBUtil {
private static final String URL = "jdbc:mysql://localhost:3306/servlet_demo?useSSL=false&serverTimezone=Asia/Taipei&allowPublicKeyRetrieval=true";
private static final String USER = "root";
private static final String PASSWORD = "123456";
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, PASSWORD);
}
public static void close(ResultSet rs, Statement stmt, Connection conn) {
try { if (rs != null) rs.close(); } catch (SQLException e) { e.printStackTrace(); }
try { if (stmt != null) stmt.close(); } catch (SQLException e) { e.printStackTrace(); }
try { if (conn != null) conn.close(); } catch (SQLException e) { e.printStackTrace(); }
}
}
public class User {
private int id;
private String username;
private String password;
private String email;
private Timestamp createdAt;
// Getter/Setter/toString 略
}
public class UserDAO {
public List findAll() {
String sql = "SELECT * FROM users";
try (Connection conn = DBUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
List list = new ArrayList<>();
while (rs.next()) list.add(mapRow(rs));
return list;
} catch (SQLException e) { throw new RuntimeException(e); }
}
public User findById(int id) {
String sql = "SELECT * FROM users WHERE id=?";
try (Connection conn = DBUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, id);
try (ResultSet rs = ps.executeQuery()) {
return rs.next() ? mapRow(rs) : null;
}
} catch (SQLException e) { throw new RuntimeException(e); }
}
public boolean insert(User user) {
String sql = "INSERT INTO users(username,password,email) VALUES(?,?,?)";
try (Connection conn = DBUtil.getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, user.getUsername());
ps.setString(2, user.getPassword());
ps.setString(3, user.getEmail());
return ps.executeUpdate() > 0;
} catch (SQLException e) { throw new RuntimeException(e); }
}
// update、delete 同理
private User mapRow(ResultSet rs) throws SQLException { /* 映射邏輯 */ }
}
@WebServlet("/users/*")
public class UserServlet extends HttpServlet {
private UserDAO dao = new UserDAO();
private ObjectMapper mapper = new ObjectMapper(); // Jackson 處理 JSON
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String pathInfo = req.getPathInfo(); // /users/1, /users, /users/add
resp.setContentType("application/json;charset=UTF-8");
try {
if (pathInfo == null || pathInfo.equals("/")) {
// 列表
List list = dao.findAll();
mapper.writeValue(resp.getWriter(), list);
} else if (pathInfo.matches("/\\d+")) {
// 單條查詢 /users/1
int id = Integer.parseInt(pathInfo.substring(1));
User user = dao.findById(id);
if (user != null) mapper.writeValue(resp.getWriter(), user);
else resp.sendError(HttpServletResponse.SC_NOT_FOUND);
} else {
resp.sendError(HttpServletResponse.SC_NOT_FOUND);
}
} catch (Exception e) {
resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e.getMessage());
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// 新增 /users (JSON Body)
User user = mapper.readValue(req.getInputStream(), User.class);
boolean ok = dao.insert(user);
resp.setStatus(ok ? HttpServletResponse.SC_CREATED : HttpServletResponse.SC_BAD_REQUEST);
mapper.writeValue(resp.getWriter(), Map.of("success", ok));
}
// doPut、doDelete 同理
}
| 問題 | 原因 | 解決 |
|---|---|---|
| ClassNotFoundException: Driver | Jar 未放入 WEB-INF/lib 或 Maven 依賴範圍錯誤 | 確認 Jar 存在於部署後的 WEB-INF/lib |
| Communications link failure | MySQL 服務未啟動、端口錯誤、防火牆、URL 時區參數缺失 | 檢查服務、URL 加 `serverTimezone=Asia/Taipei` |
| Public Key Retrieval is not allowed | MySQL 8 密碼插件 caching_sha2_password | URL 加 `allowPublicKeyRetrieval=true` 或改用 mysql_native_password |
| 中文亂碼 | 請求/響應編碼未設定、資料庫連線編碼 | Filter 統一設定 UTF-8、URL 加 `characterEncoding=utf8` |
| Tomcat 10 jakarta.servlet 找不到類 | Servlet 5.0 包名變更 javax → jakarta | 代碼 import 改為 jakarta.servlet.* 或降級 Tomcat 9 |
完成這個 Servlet + MySQL + Tomcat 專案,你已掌握 Java Web 開發最核心的「請求 → Servlet → JDBC → MySQL → 響應」完整鏈路。雖然生產環境多使用 Spring Boot,但理解底層 Servlet API、JDBC 事務、Tomcat 部署機制,是排查框架疑難雜症、設計高性能系統的不可或缺的內功。