Java 数据库编程 (JDBC)

几乎所有的企业级应用都需要与数据库打交道。Java 通过 JDBC (Java Database Connectivity) API 提供了一套标准的、独立于具体数据库厂商的接口,用于执行 SQL 语句并与关系型数据库进行交互。

什么是 JDBC?

JDBC 是一套由 java.sql 和 javax.sql 包组成的 Java API。它定义了 Java 应用程序如何连接和操作数据库。要连接到一个特定的数据库(如 MySQL, PostgreSQL, Oracle),您需要该数据库厂商提供的 JDBC 驱动程序。这个驱动程序是一个实现了 JDBC 接口的库,它负责将标准的 JDBC 调用转换为特定数据库的协议。

JDBC 核心组件

  1. DriverManager: 管理一组 JDBC 驱动程序。它的主要作用是使用 getConnection() 方法,根据数据库 URL 创建一个数据库连接。
  2. Connection: 代表与数据库的一个物理连接。所有与数据库的通信都在 Connection 的上下文中进行。
  3. Statement: 用于执行静态的 SQL 语句并返回结果。它有几个子接口,其中最常用的是 PreparedStatement。
  4. PreparedStatement: Statement 的子接口。它代表一个预编译的 SQL 语句,可以高效地多次执行。更重要的是,它能有效防止 SQL 注入攻击。
  5. ResultSet: 代表 SQL 查询的结果集。它维护一个指向其数据行的游标,您可以通过移动游标来逐行读取数据。

JDBC 操作步骤

使用 JDBC 连接数据库通常遵循以下六个步骤:

  1. 加载 JDBC 驱动:在现代 JDBC (4.0+) 中,驱动程序通常会自动加载,此步骤可省略。
  2. 建立连接:使用 DriverManager.getConnection() 获取 Connection 对象。
  3. 创建语句对象:通过 Connection 对象创建 Statement 或 PreparedStatement。
  4. 执行 SQL 语句:使用 executeQuery() (用于 SELECT) 或 executeUpdate() (用于 INSERT, UPDATE, DELETE) 执行 SQL。
  5. 处理结果集:如果执行的是查询,则遍历 ResultSet 对象获取数据。
  6. 关闭资源:按相反的顺序(ResultSet -> Statement -> Connection)关闭所有资源,以释放数据库连接。

示例:使用 JDBC 查询数据

假设我们有一个 MySQL 数据库,并且已经在项目的依赖中添加了 MySQL JDBC 驱动。

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class JdbcExample {
    // 数据库连接信息
    static final String DB_URL = "jdbc:mysql://localhost:3306/mydatabase?useSSL=false&serverTimezone=UTC";
    static final String USER = "root";
    static final String PASS = "password";

    public static void main(String[] args) {
        // 使用 try-with-resources 语句自动关闭资源
        try (Connection conn = DriverManager.getConnection(DB_URL, USER, PASS)) {
            System.out.println("数据库连接成功!");

            String sql = "SELECT id, name, email FROM users WHERE id = ?";

            try (PreparedStatement pstmt = conn.prepareStatement(sql)) {
                // 设置 SQL 语句中的参数 (第一个 ? 的索引是 1)
                pstmt.setInt(1, 101);

                // 执行查询
                try (ResultSet rs = pstmt.executeQuery()) {
                    // 遍历结果集
                    while (rs.next()) {
                        // 通过列名或索引获取数据
                        int id = rs.getInt("id");
                        String name = rs.getString("name");
                        String email = rs.getString("email");

                        System.out.printf("ID: %d, Name: %s, Email: %s%n", id, name, email);
                    }
                }
            }
        } catch (SQLException e) {
            System.err.println("数据库操作失败: " + e.getMessage());
            e.printStackTrace();
        }
    }
}

Statement vs. PreparedStatement

  • Statement: 每次执行都会重新编译 SQL 语句。如果 SQL 中包含用户输入,容易引发 SQL 注入。
    // 不安全的示例
    String userId = "101 OR 1=1"; // 恶意输入
    statement.executeQuery("SELECT * FROM users WHERE id = " + userId);
  • PreparedStatement: SQL 语句只在创建时编译一次。它将 SQL 结构和参数分开,参数值会经过转义处理,从而杜绝了 SQL 注入的风险。性能更好,代码更清晰,是首选方式。

事务管理

事务是一组必须作为一个整体成功或失败的 SQL 操作。默认情况下,JDBC 连接处于自动提交模式 (auto-commit),即每条 SQL 语句都是一个独立的事务。

可以手动管理事务:

Connection conn = null;
try {
    conn = DriverManager.getConnection(...);
    // 1. 关闭自动提交
    conn.setAutoCommit(false);

    // 2. 执行多个 SQL 操作...
    // statement.executeUpdate(...);
    // statement.executeUpdate(...);

    // 3. 如果所有操作都成功,提交事务
    conn.commit();
} catch (SQLException e) {
    // 4. 如果发生任何错误,回滚事务
    if (conn != null) {
        try {
            conn.rollback();
        } catch (SQLException ex) {
            ex.printStackTrace();
        }
    }
} finally {
    // 恢复自动提交模式并关闭连接
    if (conn != null) {
        conn.setAutoCommit(true);
        conn.close();
    }
}

评论