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

Java JDBC 通用資料庫操作基礎類封裝:連線管理、CRUD 模板與資源自動釋放

     編輯:Java 相關知識

前言:重複造輪子 vs 封裝 BaseDao

每個專案都要寫「載入驅動 → 獲取連線 → PreparedStatement → 綁參 → 執行 → 關閉資源」的樣板碼,不僅枯燥易錯,還難以統一異常處理、事務控制、SQL 日誌。本文封裝一個可配置多種資料庫、支援參數化 SQL、自動資源管理、提供增刪改查模板方法的 BaseDao,作為專案資料存取層的基石。

完整 BaseDao 實現


package dao;

import java.sql.*;
import java.util.*;

public class BaseDao {
    /* ========== 資料庫配置 (建議外部化到 properties/yaml) ========== */
    // MySQL
    private static final String DRIVER = "com.mysql.cj.jdbc.Driver";
    private static final String URL    = "jdbc:mysql://127.0.0.1:3306/school?useSSL=false&serverTimezone=UTC";
    private static final String USER   = "root";
    private static final String PASS   = "123456";

    // Oracle / SQL Server 只需切換上述四個常數即可
    // private static final String DRIVER = "oracle.jdbc.driver.OracleDriver";
    // private static final String URL    = "jdbc:oracle:thin:@localhost:1521:XE";
    // ...

    static {
        try { Class.forName(DRIVER); }
        catch (ClassNotFoundException e) { throw new ExceptionInInitializerError("JDBC 驅動載入失敗: " + DRIVER); }
    }

    /* ========== 連線獲取 ========== */
    public Connection getConnection() throws SQLException {
        return DriverManager.getConnection(URL, USER, PASS);
    }

    /* ========== 統一資源關閉 (逆序、逐一 try-catch) ========== */
    public void closeAll(Connection conn, PreparedStatement ps, ResultSet rs) {
        closeQuietly(rs);
        closeQuietly(ps);
        closeQuietly(conn);
    }
    private void closeQuietly(AutoCloseable c) {
        if (c != null) try { c.close(); } catch (Exception ignored) {}
    }

    /* ========== 通用增刪改 (DML) ========== */
    /**
     * @param sql    參數化 SQL,如 "INSERT INTO t(a,b) VALUES(?,?)"
     * @param params 參數陣列,順序對應 ?, 自動 setObject
     * @return 影響行數
     */
    public int executeUpdate(String sql, Object... params) {
        Connection conn = null;
        PreparedStatement ps = null;
        try {
            conn = getConnection();
            ps = conn.prepareStatement(sql);
            bindParams(ps, params);
            return ps.executeUpdate();
        } catch (SQLException e) {
            throw new RuntimeException("SQL 執行失敗: " + sql, e);
        } finally {
            closeAll(conn, ps, null);
        }
    }

    /* ========== 通用單筆查詢 ========== */
    public  T queryOne(String sql, RowMapper mapper, Object... params) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            conn = getConnection();
            ps = conn.prepareStatement(sql);
            bindParams(ps, params);
            rs = ps.executeQuery();
            return rs.next() ? mapper.mapRow(rs) : null;
        } catch (SQLException e) {
            throw new RuntimeException("查詢失敗: " + sql, e);
        } finally {
            closeAll(conn, ps, rs);
        }
    }

    /* ========== 通用多筆查詢 ========== */
    public  List queryList(String sql, RowMapper mapper, Object... params) {
        Connection conn = null;
        PreparedStatement ps = null;
        ResultSet rs = null;
        List list = new ArrayList<>();
        try {
            conn = getConnection();
            ps = conn.prepareStatement(sql);
            bindParams(ps, params);
            rs = ps.executeQuery();
            while (rs.next()) list.add(mapper.mapRow(rs));
            return list;
        } catch (SQLException e) {
            throw new RuntimeException("查詢失敗: " + sql, e);
        } finally {
            closeAll(conn, ps, rs);
        }
    }

    /* ========== 參數綁定輔助 ========== */
    private void bindParams(PreparedStatement ps, Object[] params) throws SQLException {
        if (params != null) {
            for (int i = 0; i < params.length; i++) {
                ps.setObject(i + 1, params[i]); // JDBC 4.2+ 支援自動類型推導
            }
        }
    }

    /* ========== RowMapper 函數式介面 (Java 8+) ========== */
    @FunctionalInterface
    public interface RowMapper {
        T mapRow(ResultSet rs) throws SQLException;
    }

    /* ========== 測試主程式 ========== */
    public static void main(String[] args) {
        BaseDao dao = new BaseDao();
        try (Connection c = dao.getConnection()) {
            System.out.println("連線成功: " + !c.isClosed());
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

使用範例:UserDao 繼承 BaseDao


public class UserDao extends BaseDao {
    public User findById(int id) {
        return queryOne("SELECT * FROM user WHERE id=?", rs -> {
            User u = new User();
            u.setId(rs.getInt("id"));
            u.setName(rs.getString("name"));
            u.setAge(rs.getInt("age"));
            return u;
        }, id);
    }

    public List findAll() {
        return queryList("SELECT * FROM user", rs -> {
            User u = new User();
            u.setId(rs.getInt("id"));
            u.setName(rs.getString("name"));
            u.setAge(rs.getInt("age"));
            return u;
        });
    }

    public boolean insert(User u) {
        return executeUpdate("INSERT INTO user(name,age) VALUES(?,?)", u.getName(), u.getAge()) == 1;
    }

    public boolean update(User u) {
        return executeUpdate("UPDATE user SET name=?, age=? WHERE id=?", u.getName(), u.getAge(), u.getId()) == 1;
    }

    public boolean delete(int id) {
        return executeUpdate("DELETE FROM user WHERE id=?", id) == 1;
    }
}

設計亮點總結

特性實現方式收益
多資料庫切換四個常數 (DRIVER/URL/USER/PASS)零程式碼修改遷移 MySQL/Oracle/SQL Server
SQL 注入防護全程 PreparedStatement + setObject參數化查詢,徹底杜絕拼接注入
資源零洩漏finally + closeQuietly 逆序關閉異常/正常路徑均安全釋放
函數式映射RowMapper Lambda映射邏輯內聯,無需匿名類/繼承
異常豐富化RuntimeException 包裝 SQL + 原始異常上層無需捕獲 SQLException,日誌含完整 SQL
可變參數Object... params呼叫端極簡潔,支援任意參數數量

生產環境擴展建議

  1. 連線池:將 getConnection() 改為從 HikariCP/DataSource 獲取,移除 DriverManager。
  2. 事務支援:新增 executeInTransaction(ConnectionCallback cb),控制 conn.setAutoCommit(false)、commit/rollback。
  3. SQL 日誌:整合 P6Spy 或自定義 PreparedStatement 代理,記錄慢 SQL、綁定參數。
  4. 分頁抽象:新增 queryPage(sql, mapper, pageNum, pageSize, params),內部拼接 dialect 特定 LIMIT/OFFSET。
  5. 配置外部化:四個常數改為 @Value("${spring.datasource.xxx}") 或獨立 db.properties。

結語

BaseDao 不是框架,而是**團隊共識的起點**。它用最純粹的 JDBC API 解決了 80% 的重複勞動,同時保留了完全的 SQL 掌控權。理解其設計權衡(如為什麼不用 ORM、為什麼拋 RuntimeException),能讓你在面對 MyBatis、JPA、JdbcTemplate 等高階工具時,知道它們在幫你做什麼、哪裡仍需自己把關。

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