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

Java 數據庫操作最佳實踐:連接池、預處理防注入、批處理、自增鍵獲取與事務模板全解析

     編輯:MySQL 綜合教學

Java 數據庫操作最佳實踐:連接池、預處理防注入、批處理、自增鍵獲取與事務模板全解析

從硬編碼連接到生產級數據訪問層,本文遞進式講解:配置文件解耦連接、連接工廠模式、Statement 三大執行方法、預處理防注入原理與實戰、自增主鍵回填、批處理優化、事務控制模板,附完整可運行代碼片段。

一、 連接解耦:配置文件 + 工廠模式

jdbc.properties

## MySQL
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/hncu?useUnicode=true&characterEncoding=UTF-8
username=root
password=1234

## Oracle (備選)
#driver=oracle.jdbc.driver.OracleDriver
#url=jdbc:oracle:thin:@192.168.31.12:1521:orcl
#username=scott
#password=tiger

ConnFactory.java

public class ConnFactory {
    private static Connection conn;
    static {
        try {
            Properties p = new Properties();
            p.load(ConnFactory.class.getClassLoader().getResourceAsStream("jdbc.properties"));
            Class.forName(p.getProperty("driver"));
            conn = DriverManager.getConnection(p.getProperty("url"), p.getProperty("username"), p.getProperty("password"));
            System.out.println("已連接到數據庫..." + conn);
        } catch (Exception e) {
            throw new RuntimeException("讀取配置文件失敗", e);
        }
    }
    public static Connection getConn() { return conn; }
}

優勢:切換數據庫只改配置,代碼零修改;生產環境請替換為成熟連接池。

二、 ResultSet getXXX 方法正確用法

@Test
public void getXXXDemo() throws Exception {
    Statement st = ConnFactory.getConn().createStatement();
    ResultSet rs = st.executeQuery("SELECT * FROM book");
    while (rs.next()) {
        Integer id = rs.getInt(1);           // 列索引從 1 開始
        String name = rs.getString(2);
        Double price = rs.getDouble(3);      // DECIMAL 取 getDouble,getObject 返回 BigDecimal
        Object dateTime = rs.getObject(4);   // DATETIME 取 Object 為 java.sql.Timestamp
        System.out.println(id + "," + name + "," + price + "," + dateTime);
        // 日期時間拆分
        System.out.println(rs.getDate(4) + "--" + rs.getTime(4));
    }
}

注意:自增列刪除後重新插入,序號不回退,會出現空號。

三、 Statement 三大執行方法選擇

方法適用 SQL返回值備註
executeQuerySELECTResultSet只能查詢
executeUpdateINSERT/UPDATE/DELETEint (影響行數)不能查詢
execute任意boolean (true=有結果集)通用,查詢時需 getResultSet() 取結果
boolean hasRs = st.execute(sql);
if (hasRs) {
    ResultSet rs = st.getResultSet();
    // 處理結果
}

四、 預處理:防注入的核心武器

危險場景:用戶輸入拼接 SQL

String sql = "SELECT COUNT(*) FROM stud WHERE id=" + id + " AND sname='" + name + "'";
// 惡意輸入:id=1002, name=1' OR '1'='1  → 條件恆真,繞過驗證

安全方案:PreparedStatement 佔位符

@Test
public void loginSafe() throws Exception {
    Scanner sc = new Scanner(System.in);
    Connection con = ConnFactory.getConn();
    String sql = "SELECT COUNT(*) FROM stud WHERE id=? AND sname=?";
    PreparedStatement pst = con.prepareStatement(sql);
    pst.setInt(1, sc.nextInt());
    pst.setString(2, sc.nextLine());
    ResultSet rs = pst.executeQuery();
    rs.next();
    System.out.println(rs.getInt(1) > 0 ? "登錄成功" : "登錄失敗");
    con.close();
}

原理:SQL 模板預編譯,參數作為數據傳遞,不參與語法解析,徹底杜絕注入。

五、 獲取自增主鍵(JDBC 3.0 標準)

@Test
public void saveAuto() throws Exception {
    Connection con = ConnFactory.getConn();
    // Statement 方式
    Statement st = con.createStatement();
    st.executeUpdate("INSERT INTO book(name,price,pub) VALUES('JavaEE',100.8,'2013-06-12 08:30:30')", Statement.RETURN_GENERATED_KEYS);
    ResultSet rs = st.getGeneratedKeys();
    if (rs.next()) System.out.println("自增ID: " + rs.getInt(1));

    // PreparedStatement 方式
    PreparedStatement pst = con.prepareStatement("INSERT INTO book(name,price) VALUES(?,?)", Statement.RETURN_GENERATED_KEYS);
    pst.setString(1, "計算機基礎");
    pst.setDouble(2, 28);
    pst.executeUpdate();
    ResultSet rs2 = pst.getGeneratedKeys();
    if (rs2.next()) System.out.println("自增ID: " + rs2.getInt(1));
}

六、 批處理:大幅提升寫入吞吐

Statement 批處理

public void batch() throws Exception {
    Connection con = ConnFactory.getConn();
    Statement st = con.createStatement();
    for (int i=0; i<10; i++) st.addBatch("INSERT INTO book(name,price,pub) VALUES('JavaEE',100.8,'2015-06-12 08:30:30')");
    st.addBatch("UPDATE book SET price=price*1.1 WHERE price>100");
    int[] counts = st.executeBatch();
    for (int c : counts) System.out.println(c); // 每條影響行數
}

PreparedStatement 批處理(推薦)

public void preparedBatch() throws Exception {
    Connection con = ConnFactory.getConn();
    PreparedStatement pst = con.prepareStatement("INSERT INTO book(name,price,pub) VALUES(?,?,?)");
    for (int i=0; i<5; i++) {
        pst.setString(1, "Java"+i);
        pst.setDouble(2, 55.85+i);
        pst.setString(3, "2016-12-10 07:07:08");
        pst.addBatch(); // 不帶參數
    }
    int[] counts = pst.executeBatch();
    for (int c : counts) System.out.println(c);
}

關鍵差異:PreparedStatement.addBatch() 無參數,參數已通過 setXxx 綁定;Statement.addBatch(sql) 需帶 SQL 字符串。

七、 事務控制模板(生產級)

public void transactionTemplate() {
    Connection con = null;
    try {
        con = ConnFactory.getConn();
        con.setAutoCommit(false);
        // 業務操作...
        con.commit();
    } catch (Exception e) {
        if (con != null) con.rollback();
        throw new RuntimeException("事務失敗", e);
    } finally {
        if (con != null) {
            try { con.setAutoCommit(true); con.close(); } catch (SQLException ignore) {}
        }
    }
}

八、 總結:從入門到生產的進階路徑

  1. 硬編碼連接 → 配置文件 + 工廠 → 連接池。
  2. Statement 拼接 → PreparedStatement 佔位符(必須)。
  3. 單條執行 → 批處理 → 異步攢批入庫。
  4. 自動提交 → 顯式事務模板(try-catch-finally 標準化)。
  5. 單機 → 讀寫分離/分庫分表 → 分布式事務。

代碼規範落地,數據安全與性能才有保障。

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