購物車等場景涉及多 Servlet 協作,若各自開關連接將耗盡資源。HttpSession 可存放任意對象,但直接存 Connection 存在引用竄改風險。本文提出「連接持有者」模式 + HttpSessionBindingListener 自動清理,實現安全共享。
// Servlet1
session.setAttribute("connection", conn);
// Servlet2 -- 危險!
Connection c = (Connection) session.getAttribute("connection");
c = null; // 竄改引用
session.setAttribute("connection", c); // 污染共享對象
public class ConnectionHolder implements HttpSessionBindingListener {
private Connection con; // 私有,防外部竄改
public ConnectionHolder(Connection con) {
this.con = con;
try { con.setAutoCommit(false); } // 隔離事務
catch (SQLException e) { /* handle */ }
}
public Connection getConnection() { return con; }
// Session 綁定/解綁回調
public void valueBound(HttpSessionBindingEvent e) { /* no-op */ }
public void valueUnbound(HttpSessionBindingEvent e) {
try {
if (con != null) { con.rollback(); con.close(); }
} catch (SQLException ex) { /* handle */ }
}
}
protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
HttpSession session = req.getSession();
Connection conn;
synchronized (session) { // 並發安全
ConnectionHolder holder = (ConnectionHolder) session.getAttribute("servletapp.connection");
if (holder == null) {
holder = new ConnectionHolder(DriverManager.getConnection("jdbc:dm://..."));
session.setAttribute("servletapp.connection", holder);
}
conn = holder.getConnection();
}
// 使用 conn 進行業務操作...
// 記得手動 commit/rollback
}
| 機制 | 作用 |
|---|---|
私有 con + 唯讀 getConnection() | 防止調用者竄改引用 |
setAutoCommit(false) | 隔離不同請求事務,統一由業務代碼控制提交 |
synchronized(session) | 雙重檢查鎖模式,避免並發重複創建 |
實現 HttpSessionBindingListener | Session 失效/超時自動觸發 valueUnbound 關閉連接,無需手動攔截 |
import javax.servlet.http.*;
import java.sql.*;
public class ConnectionHolder implements HttpSessionBindingListener {
private Connection con = null;
public ConnectionHolder(Connection con) {
this.con = con;
try { con.setAutoCommit(false); } catch (SQLException e) { /* log */ }
}
public Connection getConnection() { return con; }
public void valueBound(HttpSessionBindingEvent e) {}
public void valueUnbound(HttpSessionBindingEvent e) {
try { if (con != null) { con.rollback(); con.close(); } }
catch (SQLException ex) { /* log */ }
}
}
此模式適用於傳統 Servlet/JSP 架構;現代框架(Spring、JPA)建議使用成熟的連接池與聲明式事務管理,避免手動管理連接生命週期。