首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> JSP 程式設計 >> 關於JSP

JSP 頁面查詢顯示常用模式:Value Object、Map、JSON 三種數據傳遞方案深度解析

     編輯:關於JSP

核心問題:ResultSet 不能離開 DAO 層

JDBC ResultSet 綁定底層資料庫連接與網路游標,一旦 Statement 或 Connection 關閉,ResultSet 即失效。因此**絕不能將 ResultSet 傳遞至 JSP 視圖層**。必須在 DAO/Service 層將查詢結果轉換為獨立於連接的數據結構,再傳給 JSP 渲染。本文對比三種主流傳遞模式。

模式一:Value Object (VO) / JavaBean 列表 —— 經典強類型方案

定義領域模型

public class EmployeeVO implements Serializable {
    private Long empId;
    private String empCode;
    private String realName;
    private String deptName;
    private Date hireDate;
    // 標準 Getter/Setter、toString、equals/hashCode
}

DAO 封裝邏輯

public List<EmployeeVO> findByOrgId(Long orgId) {
    String sql = "SELECT e.emp_id, e.emp_code, e.real_name, d.dept_name, e.hire_date " +
                 "FROM t_employee e JOIN t_dept d ON e.dept_id = d.dept_id " +
                 "WHERE e.organ_id = ?";
    try (Connection conn = dataSource.getConnection();
         PreparedStatement pstmt = conn.prepareStatement(sql)) {
        pstmt.setLong(1, orgId);
        try (ResultSet rs = pstmt.executeQuery()) {
            List<EmployeeVO> list = new ArrayList<>();
            while (rs.next()) {
                EmployeeVO vo = new EmployeeVO();
                vo.setEmpId(rs.getLong("emp_id"));
                vo.setEmpCode(rs.getString("emp_code"));
                vo.setRealName(rs.getString("real_name"));
                vo.setDeptName(rs.getString("dept_name"));
                vo.setHireDate(rs.getDate("hire_date"));
                list.add(vo);
            }
            return list;
        }
    }
}

JSP 渲染(配合 JSTL/EL)

<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="fmt" uri="https://java.sun.com/jsp/jstl/fmt" %>
<table>
    <thead><tr><th>工號</th><th>姓名</th><th>部門</th><th>入職日期</th></tr></thead>
    <tbody>
    <c:forEach items="${employeeList}" var="emp">
        <tr>
            <td>${emp.empCode}</td>
            <td>${emp.realName}</td>
            <td>${emp.deptName}</td>
            <td><fmt:formatDate value="${emp.hireDate}" pattern="yyyy-MM-dd"/></td>
        </tr>
    </c:forEach>
    </tbody>
</table>

優缺點分析

優點缺點
類型安全,IDE 自動提示、重構友好
業務語義清晰,便於驗證、格式化邏輯封裝
適合穩定、長期維護的核心業務實體
每個查詢需定義專屬 VO 類,類爆炸
手動映射代碼繁瑣(可用 BeanUtils/MapStruct 自動化)
彈性差:SELECT 列變動需同步修改 VO

模式二:Map<String, Object> 列表 —— 動態彈性方案

DAO 返回 List<Map>

public List<Map<String, Object>> findDynamic(String sql, Object... params) {
    try (Connection conn = dataSource.getConnection();
         PreparedStatement pstmt = conn.prepareStatement(sql)) {
        for (int i = 0; i < params.length; i++) pstmt.setObject(i+1, params[i]);
        try (ResultSet rs = pstmt.executeQuery()) {
            ResultSetMetaData meta = rs.getMetaData();
            int colCount = meta.getColumnCount();
            List<Map<String, Object>> list = new ArrayList<>();
            while (rs.next()) {
                Map<String, Object> row = new LinkedHashMap<>(); // 保持列順序
                for (int i = 1; i <= colCount; i++) {
                    String colName = meta.getColumnLabel(i); // 別名優先
                    row.put(colName, rs.getObject(i));
                }
                list.add(row);
            }
            return list;
        }
    }
}

JSP 渲染

<c:forEach items="${rowList}" var="row">
    <tr>
        <c:forEach items="${row}" var="entry">
            <td>${entry.value}</td>
        </c:forEach>
    </tr>
</c:forEach>

優缺點分析

優點缺點
零領域類定義,極致彈性
通用 DAO 方法支援任意 SQL
適合報表、臨時查詢、動態列表
無類型安全,鍵名拼寫錯誤運行時才發現
EL 訪問需用 ${row['COL_NAME']},大小寫敏感
日期/數字格式化需在 JSP 中處理,邏輯分散
序列化體積大(含鍵名重複)

模式三:JSON 字串 / 物件 —— 前後端分離/單頁應用首選

Service 層直接序列化

// 使用 Jackson / Gson
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule()); // 支援 Java 8 日期
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

String json = mapper.writeValueAsString(rowList); // List 或 List
request.setAttribute("jsonData", json);

JSP 嵌入前端框架渲染

<script>
    const data = ${jsonData}; // 直接注入 JS 變量
    // Vue/React/原生 JS 渲染
    new Vue({
        el: '#app',
        data: { rows: data },
        template: '<table><tr v-for="r in rows"><td>{{r.EMP_CODE}}</td>...</tr></table>'
    });
</script>

優缺點分析

優點缺點
前後端契約標準化,支援異構客戶端
天然支援巢狀結構、複雜類型
前端渲染分擔伺服器壓力,首屏可直出 SEO
傳統 JSP 專案引入前端框架增加複雜度
雙模板維護成本高(JSP + JS 模板)
需處理 XSS 風險(JSON 注入 script 標籤)

綜合對比與選型建議

| 維度 | VO 列表 | Map 列表 | JSON | |------|---------|----------|------| | **類型安全** | ⭐⭐⭐⭐⭐ | ⭐ | ⭐⭐⭐ (配合 TS) | | **開發效率** | ⭐⭐ (初期) | ⭐⭐⭐⭐ | ⭐⭐⭐ | | **維護性** | ⭐⭐⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐ | | **彈性/動態** | ⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | | **序列化體積** | 中 | 大 (鍵重複) | 小 (壓縮後) | | **適用場景** | 核心業務 CRUD、表單回顯 | 動態報表、臨時查詢、導出 | SPA、API、微服務、異步載入 |

現代化最佳實踐:分層封裝與自動化映射

1. 統一分頁結果封裝

public class PageResult<T> {
    private List<T> records;
    private long total;
    private int pageNum, pageSize;
    private int totalPages;
    // 構造器、Getter、工廠方法 of(list, total, pageNum, pageSize)
}

2. 使用 MapStruct 自動生成 VO 映射代碼

@Mapper(componentModel = "spring")
public interface EmployeeMapper {
    @Mapping(source = "empId", target = "empId")
    EmployeeVO toVO(EmployeePO po); // PO 為持久層實體
    List<EmployeeVO> toVOList(List<EmployeePO> pos);
}

3. 通用 BaseDao 模板方法

public abstract class BaseDao {
    protected <T> List<T> queryForList(String sql, RowMapper<T> mapper, Object... params) {
        // 統一連接、預編譯、參數綁定、結果映射、資源關閉、異常轉換
    }
}

@FunctionalInterface
public interface RowMapper<T> {
    T mapRow(ResultSet rs, int rowNum) throws SQLException;
}

總結

JSP 頁面查詢顯示的本質是**「如何將資料庫元組安全、高效地轉換為視圖可消費的數據結構」**。

  • 傳統 MVC 專案:核心業務用 VO 列表,配合 MapStruct 自動映射;動態報表用 Map 列表 通用 DAO。
  • 轉型前後端分離:服務層直接返回 JSON,JSP 退化為單頁殼或模板引擎直出。
  • 無論哪種模式:嚴禁 ResultSet 流出 DAO、統一異常處理、連接池資源管理、SQL 參數化防注入、分頁必須下推資料庫。

選擇適合團隊技術棧與業務階段的模式,並建立統一規範與基礎設施,才是長久之計。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved