一、JDBC 核心概念详解

JDBC(Java Database Connectivity)是 Java 提供的一套标准接口,用于实现 Java 程序与各类关系型数据库(MySQL、Oracle、PostgreSQL 等)的交互。简单来说:

  • JDBC 本身不是具体实现,而是定义了 “怎么操作数据库” 的规范;
  • 各大数据库厂商(如 MySQL)会提供符合 JDBC 规范的驱动包(如 mysql-connector-java),这才是真正实现数据库连接、SQL 执行的底层代码;
  • 开发者只需调用 JDBC 接口,无需关心不同数据库的底层差异(这也是 “面向接口编程” 的典型应用)。
1. JDBC 核心组件(5 个关键接口 / 类)
组件 作用
DriverManager 管理数据库驱动,创建数据库连接(Connection
Connection 代表 Java 程序与数据库的物理连接,是所有数据库操作的基础
Statement/PreparedStatement 执行 SQL 语句的对象(PreparedStatement 更安全,推荐使用)
ResultSet 存储 SQL 查询(SELECT)的结果集,可遍历获取数据
SQLException 处理 JDBC 操作中所有数据库相关的异常
2. JDBC 核心优势
  • 跨数据库兼容:一套代码适配不同数据库(只需更换驱动和连接串);
  • 底层可控:相比 ORM 框架(如 MyBatis),更贴近数据库底层,适合简单场景或性能优化;
  • 标准统一:Java 官方定义,无需依赖第三方框架。

二、JDBC 完整执行流程(8 步)

  1. 加载 / 注册数据库驱动(MySQL 8.0+ 可省略,自动加载);
  2. 通过 DriverManager 获取 Connection 连接;
  3. 创建 PreparedStatement(推荐)/Statement 对象,封装 SQL 语句;
  4. 执行 SQL 语句(executeQuery 查 /executeUpdate 增删改);
  5. 处理执行结果(遍历 ResultSet 或获取受影响行数);
  6. 关闭 ResultSet(查询时);
  7. 关闭 PreparedStatement/Statement
  8. 关闭 Connection(必须关闭,否则占用数据库连接资源)。

三、完整可运行代码示例

以 MySQL 为例,实现 “查询用户 + 新增用户” 的完整流程,包含异常处理、资源关闭、参数防注入(用 PreparedStatement 避免 SQL 注入)。

前置准备
  1. 引入 MySQL 驱动依赖(Maven):
<!-- pom.xml 中添加 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.33</version> <!-- 适配 MySQL 8.0+,5.x 用 5.1.49 -->
    <scope>runtime</scope>
</dependency>
  1. 数据库准备(创建测试表):
CREATE DATABASE IF NOT EXISTS jdbc_demo;
USE jdbc_demo;
CREATE TABLE IF NOT EXISTS user (
    id INT PRIMARY KEY AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL,
    age INT
);
完整 Java 代码
import java.sql.*;

/**
 * JDBC 完整执行流程示例:查询+新增用户
 */
public class JdbcCompleteDemo {
    // 数据库连接信息(根据自己的环境修改)
    private static final String DB_URL = "jdbc:mysql://localhost:3306/jdbc_demo?useSSL=false&serverTimezone=UTC&characterEncoding=utf8";
    private static final String DB_USER = "root"; // 你的数据库用户名
    private static final String DB_PASSWORD = "123456"; // 你的数据库密码

    public static void main(String[] args) {
        // 1. 新增用户
        insertUser("张三", 25);
        insertUser("李四", 30);

        // 2. 查询所有用户
        queryAllUsers();
    }

    /**
     * 新增用户(增删改用 executeUpdate)
     * @param username 用户名
     * @param age 年龄
     */
    public static void insertUser(String username, int age) {
        Connection conn = null;
        PreparedStatement pstmt = null;

        try {
            // 步骤1:获取数据库连接(MySQL 8.0+ 无需手动加载驱动)
            conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);

            // 步骤2:创建 PreparedStatement(? 是参数占位符,防 SQL 注入)
            String sql = "INSERT INTO user (username, age) VALUES (?, ?)";
            pstmt = conn.prepareStatement(sql);
            // 给占位符赋值(索引从 1 开始)
            pstmt.setString(1, username);
            pstmt.setInt(2, age);

            // 步骤3:执行 SQL(executeUpdate 返回受影响行数)
            int affectedRows = pstmt.executeUpdate();
            System.out.println("新增用户成功,受影响行数:" + affectedRows);

        } catch (SQLException e) {
            // 异常处理(实际开发中可封装为自定义异常)
            e.printStackTrace();
        } finally {
            // 步骤4:关闭资源(逆序关闭,先关 Statement,再关 Connection)
            closeResource(conn, pstmt, null);
        }
    }

    /**
     * 查询所有用户(查询用 executeQuery,返回 ResultSet)
     */
    public static void queryAllUsers() {
        Connection conn = null;
        PreparedStatement pstmt = null;
        ResultSet rs = null;

        try {
            // 步骤1:获取连接
            conn = DriverManager.getConnection(DB_URL, DB_USER, DB_PASSWORD);

            // 步骤2:创建 PreparedStatement
            String sql = "SELECT id, username, age FROM user";
            pstmt = conn.prepareStatement(sql);

            // 步骤3:执行查询,获取结果集
            rs = pstmt.executeQuery();

            // 步骤4:遍历结果集
            System.out.println("\n===== 用户列表 =====");
            while (rs.next()) { // rs.next() 移动到下一行,无数据时返回 false
                int id = rs.getInt("id"); // 通过列名获取值
                String username = rs.getString("username");
                int age = rs.getInt("age");
                System.out.printf("ID:%d,用户名:%s,年龄:%d%n", id, username, age);
            }

        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            // 步骤5:关闭资源(包含 ResultSet)
            closeResource(conn, pstmt, rs);
        }
    }

    /**
     * 统一关闭 JDBC 资源的工具方法(避免重复代码)
     * @param conn 连接对象
     * @param stmt 执行 SQL 的对象
     * @param rs 结果集(查询时才用)
     */
    private static void closeResource(Connection conn, Statement stmt, ResultSet rs) {
        try {
            if (rs != null) {
                rs.close(); // 关闭结果集
            }
            if (stmt != null) {
                stmt.close(); // 关闭 Statement
            }
            if (conn != null) {
                conn.close(); // 关闭连接
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}
代码关键说明
  1. PreparedStatement 为什么推荐用?
    • 防 SQL 注入:参数通过占位符 ? 赋值,而非拼接字符串(比如 username='张三' OR 1=1 这类注入语句会失效);
    • 性能优化:可预编译 SQL,重复执行时无需重新解析。
  2. 资源关闭的重要性
    • 数据库连接是稀缺资源,必须在 finally 中关闭(无论是否异常);
    • 关闭顺序:ResultSetStatementConnection(创建顺序的逆序)。
  3. MySQL 8.0+ 注意点
    • 驱动类路径从 com.mysql.jdbc.Driver 改为 com.mysql.cj.jdbc.Driver
    • 连接串必须指定 serverTimezone(如 UTC、Asia/Shanghai),否则报错。
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐