將 JDBC 代碼直接寫在 JSP 中 (Model 1) 會導致業務邏輯與視圖耦合、難以復用、事務管理混亂。本文介紹基於 **JSP 標簽庫 + JavaBean + DataSource** 的組件化方案,實現 SQL 邏輯與視圖分離、連接池復用、聲明式調用,顯著提升開發效率與運行性能。
| 模式 | 結構 | 優點 | 缺點 |
|---|---|---|---|
| Model 1 (Page-centric) | JSP 直連 JDBC | 簡單、直觀 | 耦合度高、無復用、事務難控、性能差 |
| Model 2 (MVC) | Servlet(Controller) + JavaBean(Model) + JSP(View) | 分層清晰、可測試、事務集中 | 需寫 Controller、轉發代碼 |
| Taglib 組件化 (本文方案) | 自定義標簽 + Bean + DataSource | **JSP 內聲明式調用、零 Java 代碼、標簽復用、連接池透明** | 標簽類開發門檻稍高 |
package com.myactions;
import javax.servlet.jsp.tagext.*;
import javax.servlet.jsp.*;
import javax.sql.DataSource;
import javax.naming.*;
import java.sql.*;
import java.util.*;
public class QueryTag extends BodyTagSupport {
private String sql; // SQL 語句,支持參數占位符 ?
private String dataSource; // JNDI 名稱
private String var; // 導出變量名 (List
myactions.tld<?xml version="1.0" encoding="UTF-8"?>
<taglib xmlns="https://java.sun.com/xml/ns/javaee" version="2.1">
<tlib-version>1.0</tlib-version>
<short-name>myactions</short-name>
<uri>https://example.com/myactions</uri>
<tag>
<name>query</name>
<tag-class>com.myactions.QueryTag</tag-class>
<body-content>empty</body-content>
<attribute>
<name>sql</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>dataSource</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
<attribute>
<name>var</name>
<required>true</required>
<rtexprvalue>true</rtexprvalue>
</attribute>
</tag>
</taglib>
<%@ taglib prefix="act" uri="https://example.com/myactions" %>
<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core" %>
<act:query dataSource="jdbc/MyDS"
sql="SELECT id, name, email FROM users WHERE status = ?"
var="userList"/>
<table>
<c:forEach items="${userList}" var="u">
<tr>
<td>${u.id}</td>
<td>${u.name}</td>
<td>${u.email}</td>
</tr>
</c:forEach>
</table>
<resource-ref>
<res-ref-name>jdbc/MyDS</res-ref-name>
<res-type>javax.sql.DataSource</res-type>
<res-auth>Container</res-auth>
</resource-ref>
Tomcat context.xml 見前文「技巧 1」。
<act:query ... var="list">
<act:param value="${param.keyword}"/> <!-- 第 1 個 ? -->
<act:param value="1"/> <-- 第 2 個 ? -->
</act:query>
標簽類中解析 getBodyContent() 或實現 Tag 介面的子標簽處理器。
<act:update>public class UpdateTag extends TagSupport {
// 類似 QueryTag,執行 ps.executeUpdate(),返回影響行數
// 支援批量:addBatch()/executeBatch()
}
<act:transaction dataSource="jdbc/MyDS">
<act:update sql="INSERT INTO orders ..."/>
<act:update sql="UPDATE inventory SET qty = qty - ? WHERE id = ?"/>
</act:transaction>
實現需在 doStartTag 獲取連接關閉自動提交,doEndTag 提交/回滾,連接存入 pageContext 供子標簽共用。
poolPreparedStatements=true (Druid/HikariCP 支援)。ResultSet 迭代輸出 (需自定義迭代標簽)。release() 重置字段。標簽庫封裝 JDBC 將 **SQL 邏輯內聚到標簽實現類**,JSP 頁面回歸純視圖模板,**零 Java 代碼、聲明式數據綁定、連接池透明復用**。這是從 Model 1 邁向組件化架構的關鍵一步,也是早期 JSP 專案重構的低成本高收益路徑。