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

JavaWeb 基礎工具類 BaseDao 重構:從菜鳥代碼到生產可用的資料存取模板

     編輯:Java 綜合教學

歷史背景與演進

早期學 Servlet 時手寫的簡易 JDBC 包裝,畢業後重構改進。雖技術棧已轉向 Spring Data JPA / MyBatis,但理解底層 Connection/PreparedStatement/ResultSet 生命週期管理,對排查連線洩漏、SQL 注入、事務邊界仍有核心價值。

BaseDao 完整原始碼(已修正資源關閉順序與異常處理)

package dao;

import java.sql.*;

public class BaseDao {
    // 建議改為從配置檔/環境變數讀取
    private static final String URL  = "jdbc:sqlserver://localhost:1433;DatabaseName=StudentManageSys";
    private static final String USER = "sa";
    private static final String PWD  = "sa";

    protected Connection conn = null;
    protected PreparedStatement pstmt = null;

    static {
        try {
            Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
        } catch (ClassNotFoundException e) {
            throw new ExceptionInInitializerError("JDBC Driver 載入失敗", e);
        }
    }

    /** 取得連線(支援外部傳入連線以實作事務) */
    protected Connection getConnection() throws SQLException {
        if (conn == null || conn.isClosed()) {
            conn = DriverManager.getConnection(URL, USER, PWD);
        }
        return conn;
    }

    /** 統一關閉資源:順序 ResultSet → Statement → Connection */
    protected void closeAll(ResultSet rs, Statement stmt, Connection conn) {
        try { if (rs != null) rs.close(); } catch (SQLException ignore) {}
        try { if (stmt != null) stmt.close(); } catch (SQLException ignore) {}
        try { if (conn != null) conn.close(); } catch (SQLException ignore) {}
    }

    /** 通用執行入口:回傳 ResultSet 或 update count */
    protected Object execute(String sql, Object... params) throws SQLException {
        prepareStatement(sql, params);
        boolean hasResultSet = pstmt.execute();
        return hasResultSet ? pstmt.getResultSet() : pstmt.getUpdateCount();
    }

    private void prepareStatement(String sql, Object[] params) throws SQLException {
        conn = getConnection();
        pstmt = conn.prepareStatement(sql);
        if (params != null) {
            for (int i = 0; i < params.length; i++) {
                pstmt.setObject(i + 1, params[i]); // 防 SQL 注入
            }
        }
    }
}

關鍵改進點解析

原版問題改進方案
靜態區塊吞噬異常,導致後續 NPE拋出 ExceptionInInitializerError 快速失敗
closeAll 參數順序錯誤,連線過早關閉導致 RS/Stmt 無法關閉順序:RS → Stmt → Conn,各自獨立 try-catch
無事務支援(conn 為實例變數,無法外部控制)保留 getConnection() 可覆寫,子類可注入共用連線
硬編碼資料庫連線資訊建議整合 DataSource (DBCP/HikariCP) + 外部配置
未使用 try-with-resources (Java 7+)簡化版保留手動關閉以教學;生產建議全面遷移至 JdbcTemplate/MyBatis

子類使用範例:StudentDao

public class StudentDao extends BaseDao {
    public List findAll() {
        String sql = "SELECT id, name, age FROM student";
        List list = new ArrayList<>();
        ResultSet rs = null;
        try {
            rs = (ResultSet) execute(sql);
            while (rs.next()) {
                Student s = new Student();
                s.setId(rs.getInt("id"));
                s.setName(rs.getString("name"));
                s.setAge(rs.getInt("age"));
                list.add(s);
            }
        } catch (SQLException e) {
            throw new RuntimeException("查詢學生失敗", e);
        } finally {
            closeAll(rs, pstmt, conn);
        }
        return list;
    }
}

現代化替代建議

  1. Spring JdbcTemplate:模板方法模式自動管理資源,支援 RowMapper、NamedParameter
  2. MyBatis:SQL 與代碼分離,動態 SQL、結果映射、緩存、分頁外掛生態豐富
  3. Spring Data JPA:Repository 抽象,方法名自動生成查詢,適合領域驅動設計
  4. jOOQ:型別安全 SQL 建構器,適合複雜報表與多資料庫專案

總結

BaseDao 承載了 JDBC 原生 API 的精華與痛點。理解它,是通往成熟 ORM 框架的階梯;超越它,才是現代 JavaWeb 開發的常態。祝各位天天進步,早日脫離「手寫 Dao」苦海!

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