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

Java 數據庫編程基礎:JDBC 核心 API、PreparedStatement、事務控制、連接池與最佳實踐

     編輯:Java 相關知識

JDBC (Java Database Connectivity) 是 Java 訪問關係型數據庫的標準 API。本文系統講解 DriverManager、Connection、Statement/PreparedStatement/CallableStatement、ResultSet、事務控制、連接池(HikariCP/Druid)六大核心模塊,附完整 CRUD 示例、SQL 注入防範、資源管理模式與生產級配置建議。

一、JDBC 核心四步驟

// 1. 加載驅動 (JDK 6+ SPI 機制自動加載,可省略)
Class.forName("com.mysql.cj.jdbc.Driver");

// 2. 建立連接
String url = "jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC";
String user = "root", pwd = "123456";
Connection conn = DriverManager.getConnection(url, user, pwd);

// 3. 執行 SQL
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users");

// 4. 處理結果 / 釋放資源
while (rs.next()) { System.out.println(rs.getString("name")); }
rs.close(); stmt.close(); conn.close();

二、PreparedStatement:預編譯、參數化、防注入

String sql = "INSERT INTO users(name, email, age) VALUES(?, ?, ?)";
try (Connection conn = getConnection();
     PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {

    ps.setString(1, "張三");      // 索引從 1 開始
    ps.setString(2, "zhang@example.com");
    ps.setInt(3, 25);

    int affected = ps.executeUpdate();
    System.out.println("插入行數: " + affected);

    // 獲取自增主鍵
    try (ResultSet keys = ps.getGeneratedKeys()) {
        if (keys.next()) System.out.println("新 ID: " + keys.getLong(1));
    }
}

核心優勢:

  • SQL 預編譯:數據庫解析/優化一次,多次執行復用計劃。
  • 參數化綁定:徹底隔離代碼與數據,**杜絕 SQL 注入**。
  • 類型安全:setInt/setString/setTimestamp/setNull 等強類型方法。
  • 批量執行:addBatch()/executeBatch() 大幅提升批量寫入性能。

三、ResultSet:結果集導航與數據提取

方法說明
next()移動到下一行,返回 boolean
getXxx(int colIndex) / getXxx(String colName)提取列值:getString/getInt/getLong/getDouble/getTimestamp/getBoolean
wasNull()判斷上次讀取是否為 SQL NULL
findColumn(String name)列名轉索引(避免重複調用)
absolute(row)/relative(rows)/beforeFirst()/afterLast()可滾動結果集導航(需 TYPE_SCROLL_INSENSITIVE)
updateXxx()/insertRow()/deleteRow()/refreshRow()可更新結果集操作(需 CONCUR_UPDATABLE)

四、事務控制:ACID 保證

Connection conn = null;
try {
    conn = getConnection();
    conn.setAutoCommit(false); // 開啟事務

    // 業務邏輯:多條 SQL 原子執行
    transfer(conn, fromId, toId, amount); // 扣款
    transfer(conn, toId, fromId, amount); // 入款

    conn.commit(); // 提交事務
} catch (Exception e) {
    if (conn != null) conn.rollback(); // 回滾
    throw new RuntimeException("轉賬失敗", e);
} finally {
    if (conn != null) {
        try { conn.setAutoCommit(true); } catch (SQLException ignored) {}
        close(conn);
    }
}

隔離級別:conn.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED) — 解決臟讀/不可重複讀/幻讀權衡。

五、連接池:生產環境必備

為什麼需要:創建連接耗時 (建立 TCP、握手、認證),頻繁開關導致性能差、數據庫連接數耗盡。

HikariCP (Spring Boot 2+ 默認、性能王者)

HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb?useSSL=false");
config.setUsername("root");
config.setPassword("123456");
config.setMaximumPoolSize(20);           // 最大連接數
config.setMinimumIdle(5);                // 最小空閒
config.setIdleTimeout(300000);           // 空閒超時 5 分
config.setMaxLifetime(1800000);          // 連接最大壽命 30 分
config.setConnectionTimeout(30000);      // 獲取連接超時 30 秒
config.setPoolName("MyApp-Pool");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");

DataSource ds = new HikariDataSource(config);
// 使用
try (Connection conn = ds.getConnection()) { ... }

Druid (阿里巴巴、監控強、擴展性好)

DruidDataSource ds = new DruidDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/mydb");
ds.setUsername("root");
ds.setPassword("123456");
ds.setInitialSize(5);
ds.setMaxActive(20);
ds.setMinIdle(5);
ds.setMaxWait(60000);
ds.setValidationQuery("SELECT 1");
ds.setTestOnBorrow(false);
ds.setTestOnReturn(false);
ds.setTestWhileIdle(true);
ds.setTimeBetweenEvictionRunsMillis(60000);
// 內建 StatViewServlet 監控頁面:/druid/login.html

六、完整 CRUD 封裝範例 (DAO 模式)

public class UserDao {
    private final DataSource ds;

    public UserDao(DataSource ds) { this.ds = ds; }

    public long insert(User u) {
        String sql = "INSERT INTO users(name,email,age) VALUES(?,?,?)";
        try (Connection conn = ds.getConnection();
             PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
            ps.setString(1, u.getName());
            ps.setString(2, u.getEmail());
            ps.setInt(3, u.getAge());
            ps.executeUpdate();
            try (ResultSet keys = ps.getGeneratedKeys()) {
                return keys.next() ? keys.getLong(1) : -1;
            }
        } catch (SQLException e) { throw new RuntimeException(e); }
    }

    public Optional findById(long id) {
        String sql = "SELECT id,name,email,age FROM users WHERE id=?";
        try (Connection conn = ds.getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setLong(1, id);
            try (ResultSet rs = ps.executeQuery()) {
                return rs.next() ? Optional.of(mapUser(rs)) : Optional.empty();
            }
        } catch (SQLException e) { throw new RuntimeException(e); }
    }

    public List findAll() {
        String sql = "SELECT id,name,email,age FROM users";
        try (Connection conn = ds.getConnection();
             PreparedStatement ps = conn.prepareStatement(sql);
             ResultSet rs = ps.executeQuery()) {
            List list = new ArrayList<>();
            while (rs.next()) list.add(mapUser(rs));
            return list;
        } catch (SQLException e) { throw new RuntimeException(e); }
    }

    public int update(User u) {
        String sql = "UPDATE users SET name=?,email=?,age=? WHERE id=?";
        try (Connection conn = ds.getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setString(1, u.getName());
            ps.setString(2, u.getEmail());
            ps.setInt(3, u.getAge());
            ps.setLong(4, u.getId());
            return ps.executeUpdate();
        } catch (SQLException e) { throw new RuntimeException(e); }
    }

    public int deleteById(long id) {
        String sql = "DELETE FROM users WHERE id=?";
        try (Connection conn = ds.getConnection();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            ps.setLong(1, id);
            return ps.executeUpdate();
        } catch (SQLException e) { throw new RuntimeException(e); }
    }

    private User mapUser(ResultSet rs) throws SQLException {
        User u = new User();
        u.setId(rs.getLong("id"));
        u.setName(rs.getString("name"));
        u.setEmail(rs.getString("email"));
        u.setAge(rs.getInt("age"));
        return u;
    }
}

七、生產級最佳實踐清單

  1. 永遠使用 PreparedStatement:拼接 SQL 字符串 = SQL 注入漏洞。
  2. try-with-resources 管理資源:Connection/Statement/ResultSet 必須關閉,否則洩漏導致連接池耗盡。
  3. 異常轉換不丟失堆棧:throw new RuntimeException("業務含義", e)。
  4. 連接池參數調優:最大連接數 ≈ CPU 核心數 * 2 + 有效盤數;監控 active/idle/usage 指標。
  5. 慢 SQL 監控:Druid StatFilter / HikariCP slowQueryThresholdMs / P6Spy。
  6. 批量操作用 addBatch:千萬級導入性能提升 10-100 倍。
  7. 只選需要的列:SELECT id,name 而非 SELECT *,減少網絡/內存/映射開銷。
  8. 大字段分表/延遲加載:BLOB/CLOB/TEXT 獨立表,按需查詢。
  9. 只讀事務優化:@Transactional(readOnly=true) 或 conn.setReadOnly(true)。

JDBC 雖底層,但掌握其核心 API、事務邊界、連接池調優與資源管理模式,是上層 ORM (MyBatis/JPA/Hibernate) 高效使用的基石。建議手寫一遍本文 UserDao,體會 try-with-resources 與 PreparedStatement 的協作之美。

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