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

MVC 架構中控制器與模型層數據傳遞實戰:ParamBean 設計模式詳解

     編輯:J2EE

場景痛點:Controller ↔ Model 數據如何優雅流動?

MVC 三層架構中,控制器接收前端請求參數,需傳遞給模型層執行業務邏輯與資料庫操作;模型層處理完畢,需將結果(查詢數據、狀態碼、錯誤訊息)回傳控制器,再由控制器決定視圖渲染或轉向。若直接用 HttpServletRequest、Map 或零散參數傳遞,會導致:

  • 控制器與模型強耦合 Servlet API,難以單元測試。
  • 參數名稱硬編碼散落各處,重構易出錯。
  • 缺乏統一錯誤碼/訊息/狀態封裝,異常處理碎片化。
  • 大數據集(ResultSet)傳遞不當導致連線洩漏或序列化問題。

本文介紹經典解法:ParamBean(命令/傳輸物件模式),封裝請求參數、業務狀態、查詢結果,實現層間解耦與類型安全。

ParamBean 完整實作

package com.framework.core;

import java.io.Serializable;
import java.sql.ResultSet;
import java.util.Hashtable;
import javax.sql.rowset.CachedRowSet;
import com.sun.rowset.CachedRowSetImpl; // 具體實現,生產環境建議用標準 RowSetProvider

public class ParamBean implements Serializable {
    // === 審計/日誌欄位 ===
    private String oprCode = "";      // 操作員代碼
    private String depCode = "";      // 部門代碼
    private String menuCode = "";     // 選單代碼
    private String opDesc = "";       // 操作描述

    // === 統一回應狀態 ===
    private int retCode = 0;          // 0=成功, -1=失敗, 其他=業務錯誤碼
    private String errMsg = "";       // 錯誤訊息

    // === 動態參數載體 ===
    private final Hashtable params = new Hashtable<>();
    
    // === 斷開連線的結果集(可序列化、跨層傳遞) ===
    private CachedRowSet crs;

    // === 建構子 ===
    public ParamBean() {}

    // === 審計資訊一鍵設定 ===
    public void setInfo(String oprCode, String depCode, String menuCode, String opDesc) {
        this.oprCode = oprCode;
        this.depCode = depCode;
        this.menuCode = menuCode;
        this.opDesc = opDesc;
    }

    // === 動態參數操作(核心) ===
    public void addParam(String name, Object value) {
        if (name == null || value == null) return;
        name = name.trim();
        if (name.isEmpty()) return;
        params.put(name, value);
    }

    public Object getParam(String name) {
        if (name == null) return null;
        return params.get(name.trim());
    }

    public void removeParam(String name) {
        if (name != null) params.remove(name.trim());
    }

    public int getParamCount() { return params.size(); }
    public void clearParams() { params.clear(); }
    public Hashtable getParams() { return params; }

    // === CachedRowSet 封裝(關鍵:解決 ResultSet 不可序列化/連線綁定問題) ===
    public boolean setCrs(ResultSet rs) {
        try {
            this.crs = new CachedRowSetImpl();
            this.crs.populate(rs); // 將 ResultSet 數據完整複製到記憶體
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public CachedRowSet getCrs() { return crs; }
    public void setCrs(CachedRowSet crs) { this.crs = crs; }

    // === 狀態輔助方法 ===
    public void setError(String msg) { this.retCode = -1; this.errMsg = msg; }
    public void setSuccess() { this.retCode = 0; this.errMsg = ""; }
    public boolean isSuccess() { return retCode == 0; }

    // === Getters/Setters 省略 ===
    // oprCode, depCode, menuCode, opDesc, retCode, errMsg 標準存取子
}

為什麼用 CachedRowSet 而非直接傳 ResultSet?

特性ResultSetCachedRowSet
序列化❌ 不可序列化(遠程 EJB/RMI/Session 複製失效)✅ 實作 Serializable
連線綁定✅ 需保持 Connection 開啟❌ 斷開連線,數據在記憶體
可捲動/可更新視 JDBC 驅動支援度✅ 保證支援 TYPE_SCROLL_INSENSITIVE, CONCUR_UPDATABLE
跨層傳遞❌ 危險(DAO 關閉連線後失效)✅ 安全,控制器可直接綁定 JSP/前端

結論:DAO 層查詢後立即 paramBean.setCrs(rs) 並關閉連線,控制器拿到的是純記憶體數據結構,零資源洩漏風險。

三層協作流程實戰代碼

1. 控制器:接收請求 → 封裝 ParamBean → 呼叫模型

public class LoginController extends BaseController {
    public void doPost(HttpServletRequest req, HttpServletResponse resp) {
        ParamBean param = new ParamBean();
        
        // 從 Session 取得登入上下文
        String depCode = (String) req.getSession().getAttribute("LOGIN_USER_DEP");
        param.addParam("DEP_CODE", depCode);
        
        // 從請求參數綁定
        param.addParam("USER_CODE", req.getParameter("userCode"));
        param.addParam("PASSWORD", req.getParameter("password"));
        
        // 設定審計資訊
        param.setInfo(
            (String) req.getSession().getAttribute("LOGIN_USER_ID"),
            depCode,
            "LOGIN_MENU",
            "使用者登入驗證"
        );
        
        // 呼叫模型層業務邏輯
        AuthService authService = new AuthService();
        ParamBean result = authService.loginAuth(param);
        
        // 統一結果判斷與視圖分發
        if (result.isSuccess()) {
            req.setAttribute("PARAM_CRS", result.getCrs()); // 綁定查詢結果給視圖
            forward("loginSuccess.jsp", req, resp);
        } else {
            req.setAttribute("ERROR_MSG", result.getErrMsg());
            forward("loginFail.jsp", req, resp);
        }
    }
}

2. 模型層服務:業務邏輯 + DAO 協作

public class AuthService {
    public ParamBean loginAuth(ParamBean param) throws Exception {
        String userCode = (String) param.getParam("USER_CODE");
        String password = (String) param.getParam("PASSWORD");
        
        // 清空輸入參數,準備裝載查詢結果
        param.clearParams();
        
        String sql = "SELECT user_id, user_name, role_id FROM sys_user " +
                     "WHERE user_code = ? AND password = ? AND status = '1'";
        
        // 執行查詢(DAO 封裝 JDBC 樣板代碼)
        sqlExecQuery(param, sql, userCode, password);
        return param;
    }

    private void sqlExecQuery(ParamBean param, String sql, Object... args) {
        DatabaseBean db = getDatabase(); // 連線池封裝
        param.setError("查詢失敗"); // 預設失敗
        
        try (Connection conn = db.getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            
            // 綁定參數
            for (int i = 0; i < args.length; i++) {
                ps.setObject(i + 1, args[i]);
            }
            
            try (ResultSet rs = ps.executeQuery()) {
                param.setCrs(rs); // 關鍵:複製到 CachedRowSet
                param.setSuccess();
            }
        } catch (Exception e) {
            param.setError(e.getMessage());
            e.printStackTrace();
        } finally {
            db.close(); // 歸還連線
        }
    }
}

3. 視圖層(JSP)消費 CachedRowSet

<%@ taglib prefix="c" uri="https://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="sql" uri="https://java.sun.com/jsp/jstl/sql" %>


    
            
用戶ID用戶名角色

進階擴展建議

方向說明
泛型化參數 Map將 Hashtable 升級為 Map,支援型別安全取值 getParam(name, String.class)。
驗證框架整合加入 Bean Validation (JSR-380) 註解於 ParamBean 子類,Controller 統一驗證。
分頁/排序封裝內建 pageNo, pageSize, sortField, sortOrder 標準欄位,DAO 自動解析生成 LIMIT/OFFSET。
異常轉換器全域異常處理器捕獲 Service 異常,自動填充 ParamBean 錯誤碼/訊息,Controller 無需 try-catch。
替換為 DTO/VO現代架構傾向定義具體 LoginRequest, LoginResponse 類別,替代通用 ParamBean,獲得編譯期型別安全與 IDE 提示。

總結

ParamBean 模式以單一可序列化物件封裝「輸入參數、業務狀態、查詢結果、審計資訊」,實現:

  1. 層間解耦:Controller/Model 不直接依賴 Servlet API/JDBC API。
  2. 類型安全:編譯期檢查參數名稱(配合常數/Enum),避免拼寫錯誤。
  3. 資源安全:CachedRowSet 斷開連線,消除連線洩漏與序列化障礙。
  4. 統一協定:retCode/errMsg/isSuccess() 標準化錯誤處理流程。

雖然現代框架(Spring MVC、MyBatis、JPA)已提供更優雅的 @RequestBody/@ModelAttribute/ResultHandler 機制,但理解 ParamBean 設計思想,有助於在無框架環境、舊系統維護、或自定義框架時做出正確架構決策。

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