| 維度 | JDBC + Servlet 現狀 | 引入框架後改善 |
|---|---|---|
| 程式碼量 | 每張表需手寫 CRUD:Connection、PreparedStatement、ResultSet 映射、try-catch-finally 關閉資源 → 單表約 100+ 行 | MyBatis/JPA:介面 + XML/註解,零樣板碼 |
| 耦合度 | Servlet 直寫 SQL、處理參數、轉換實體、轉發 JSP → 業務邏輯與資料存取、視圖渲染糅合 | 分層架構:Controller-Service-DAO/Repository,各司其職 |
| SQL 維護 | SQL 散落在 Java 字串中,修改欄位需全域搜尋,易遺漏 | 集中管理於 Mapper XML 或 @Query 註解,版控清晰 |
| 連線管理 | 手動獲取/關閉連線,易洩漏;無連線池效能差 | DataSource (HikariCP) + 宣告式事務 |
| 事務控制 | 手動 conn.setAutoCommit(false)、commit/rollback,跨方法極難協調 | @Transactional 自動邊界,支援傳播行為 |
| 防注入 | 需自律用 PreparedStatement,拼接字串極易植入漏洞 | 框架強制參數化,原生支援動態 SQL 安全構建 |
| 分頁查詢 | 各資料庫語法不同,手寫 LIMIT/OFFSET/ROWNUM 移植痛苦 | PageHelper / Spring Data Pageable 自動適配 |
| 團隊協作 | 合併衝突頻發(同一 Servlet 改不同功能),Code Review 難聚焦 | 分層+介面隔離,並行開發衝突極低 |
// JDBC + Servlet (約 80 行)
protected void doGet(...) {
String sql = "SELECT * FROM user WHERE name LIKE ?";
try (Connection c = ds.getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
ps.setString(1, "%" + name + "%");
try (ResultSet rs = ps.executeQuery()) {
List<User> list = new ArrayList<>();
while (rs.next()) list.add(mapRow(rs));
req.setAttribute("users", list);
req.getRequestDispatcher("/user.jsp").forward(req, resp);
}
} catch (SQLException e) { throw new ServletException(e); }
}
private User mapRow(ResultSet rs) throws SQLException { ... }
// Spring Boot + MyBatis (約 15 行)
// UserMapper.xml
// UserService.java
@Transactional(readOnly = true)
public List<User> search(String name) {
return userMapper.selectByName("%" + name + "%");
}
// UserController.java
@GetMapping("/users")
public String list(@RequestParam String name, Model model) {
model.addAttribute("users", userService.search(name));
return "user";
}
JDBC + Servlet **能跑、但不可維護**。只要專案超過「玩具級」,請直接上 **Spring Boot + MyBatis/JPA**,節省 80% 樣板碼,獲得事務、連線池、分頁、安全、測試等企業級能力。