JDBC详解

1. JDBC 核心概念

1.1 什么是 JDBC?

JDBC(Java Database Connectivity,Java 数据库连接)是 Java 官方提供的一套用于操作关系型数据库的标准 API,它定义了 Java 程序与数据库交互的统一接口,屏蔽了不同数据库(如 MySQL、Oracle、SQL Server)的底层实现差异,让开发者无需针对特定数据库编写不同代码。

简单来说:JDBC 是 Java 程序和数据库之间的 “桥梁”—— 通过 JDBC,Java 可以发送 SQL 语句到数据库,接收执行结果,并处理数据。

1.2 JDBC 的核心作用

  • 统一接口:无论操作 MySQL 还是 Oracle,都使用相同的 JDBC 方法(如 ConnectionStatement),降低跨数据库开发的成本。
  • 解耦:Java 程序无需依赖具体数据库的实现,只需引入对应数据库的 “驱动”(Driver)即可。
  • 原生交互:直接操作 SQL,保留数据库的原生功能,适合需要精细控制 SQL 的场景。

1.3 JDBC 依赖的核心组件

JDBC 的使用依赖 “Java 标准 API + 数据库驱动” 两部分:

  • Java 标准 API:JDK 自带的 java.sqljavax.sql 包下的接口 / 类(如 ConnectionStatement),定义了规范。
  • 数据库驱动:由数据库厂商提供的实现类(如 MySQL 的 com.mysql.cj.jdbc.Driver),负责将 JDBC 接口与数据库底层协议关联,是 JDBC 能操作具体数据库的关键。

2. JDBC 核心组件详解

JDBC 的核心操作围绕以下 5 个核心接口 / 类展开,需掌握每个组件的作用和常用方法:

组件全类名核心作用
DriverManagerjava.sql.DriverManager1. 注册数据库驱动;2. 管理驱动列表;3. 获取数据库连接(Connection
Connectionjava.sql.Connection代表 Java 与数据库的 “一次连接”,是操作数据库的基础,可创建执行 SQL 的对象
Statement 家族java.sql.Statement用于向数据库发送 SQL 语句并执行
ResultSetjava.sql.ResultSet存储 SQL 查询(SELECT)的结果集,提供遍历和获取数据的方法
SQLExceptionjava.sql.SQLException处理 JDBC 操作中所有与数据库相关的异常(如连接失败、SQL 语法错误)

2.1 DriverManager(驱动管理类)

核心功能:获取数据库连接

获取连接的关键是 数据库 URL、用户名、密码,不同数据库的 URL 格式不同:

  • MySQL 8.0+:jdbc:mysql://localhost:3306/数据库名?useSSL=false&serverTimezone=UTC
    • localhost:数据库服务器地址(本地为 localhost,远程为 IP)
    • 3306:MySQL 默认端口
    • useSSL=false:禁用 SSL(开发环境常用)
    • serverTimezone=UTC:指定时区(MySQL 8.0+ 必须配置)
  • Oraclejdbc:oracle:thin:@localhost:1521:数据库服务名
  • SQL Serverjdbc:sqlserver://localhost:1433;databaseName=数据库名
常用方法:
// 1. 注册驱动(Java 6+ 后可省略,驱动会自动加载)
Class.forName("com.mysql.cj.jdbc.Driver"); 

// 2. 获取数据库连接(核心方法)
Connection conn = DriverManager.getConnection(
    "jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC",
    "root",  // 数据库用户名
    "123456" // 数据库密码
);

2.2 Connection(数据库连接接口)

核心功能:
  • 创建执行 SQL 的 Statement/PreparedStatement 对象;
  • 控制事务(提交、回滚);
  • 关闭连接(释放资源)。
常用方法:
方法作用
createStatement()创建普通 Statement 对象(执行静态 SQL)
prepareStatement(String sql)创建 PreparedStatement 对象(执行预编译 SQL,防注入)
setAutoCommit(boolean autoCommit)设置事务是否自动提交(默认 true,手动控制事务需设为 false
commit()提交事务
rollback()回滚事务(发生异常时调用)
close()关闭连接(必须执行,避免资源泄露)

2.3 Statement 家族(SQL 执行接口)

JDBC 提供 3 种执行 SQL 的接口,适用于不同场景:

接口适用场景优点缺点
Statement执行静态 SQL(SQL 语句固定不变)简单直接1. SQL 硬编码;2. 存在 SQL 注入风险;3. 性能低(每次执行都编译)
PreparedStatement执行动态 SQL(SQL 含参数,如 ?1. 预编译 SQL(性能高);2. 防 SQL 注入;3. 代码可复用仅支持参数化 SQL,不支持存储过程
CallableStatement调用数据库存储过程(如 call proc(?)支持存储过程的调用和输出参数获取语法复杂,依赖数据库存储过程
常用方法(以 PreparedStatement 为例):
// 1. 预编译 SQL(? 是参数占位符,不能加引号)
String sql = "INSERT INTO user(name, age) VALUES (?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);

// 2. 给 ? 赋值(索引从 1 开始!)
pstmt.setString(1, "张三"); // 第一个 ? 赋值为字符串
pstmt.setInt(2, 20);      // 第二个 ? 赋值为整数

// 3. 执行 SQL(根据 SQL 类型选择方法)
int rows = pstmt.executeUpdate(); // 执行增删改(返回受影响行数)
ResultSet rs = pstmt.executeQuery(); // 执行查询(返回结果集)

2.4 ResultSet(结果集接口)

核心功能:存储 SELECT 查询的结果,提供遍历和获取数据的方法。
常用方法:
方法作用
next()游标向下移动一行,返回 true 表示有数据,false 表示无数据(遍历核心)
getString(int columnIndex)获取当前行指定索引列的字符串(索引从 1 开始)
getString(String columnName)获取当前行指定列名的字符串(更推荐,避免列顺序变化)
getInt(String columnName)获取当前行指定列名的整数
close()关闭结果集(释放资源)
遍历结果集示例:
// 假设查询 user 表,有 id(int)、name(varchar)、age(int) 列
while (rs.next()) { // 游标移动,有数据则进入循环
    int id = rs.getInt("id");       // 通过列名获取整数
    String name = rs.getString("name"); // 通过列名获取字符串
    int age = rs.getInt("age");
    System.out.println("id: " + id + ", name: " + name + ", age: " + age);
}

2.5 SQLException(异常类)

JDBC 操作中所有与数据库相关的异常(如连接失败、SQL 语法错误、参数类型不匹配)都会抛出 SQLException,需强制捕获或声明抛出。

常用方法(获取异常信息):
  • getMessage():获取异常描述信息;
  • getSQLState():获取 SQL 状态码(如 42S22 表示列不存在);
  • getErrorCode():获取数据库厂商自定义的错误码(如 MySQL 的 1045 表示用户名密码错误)。

3. JDBC 标准操作流程(必背)

JDBC 操作数据库的流程固定,无论增删改查,都遵循以下 6 个步骤(以查询为例):

步骤 1:准备环境(导入驱动包)

使用 JDBC 需引入对应数据库的驱动包:

  • Maven 项目(推荐):在pom.xml中添加依赖(以 MySQL 8.0 为例):

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.32</version> <!-- 版本需与数据库匹配 -->
    </dependency>
    
  • 非 Maven 项目:下载驱动 Jar 包(如 mysql-connector-java-8.0.32.jar),手动添加到项目依赖中。

步骤 2:加载驱动(Java 6+ 可省略)

Java 6 及以上版本会通过 SPI 机制自动加载驱动,无需手动调用 Class.forName(),但为了兼容旧代码,了解即可。

步骤 3:获取数据库连接(Connection)

通过 DriverManager.getConnection() 获取连接,需传入 URL、用户名、密码。

步骤 4:创建 Statement/PreparedStatement 对象

根据 SQL 类型选择合适的对象(查询用 PreparedStatement,静态 SQL 用 Statement)。

步骤 5:执行 SQL 并处理结果

  • 增删改操作:调用 executeUpdate(),返回受影响的行数;
  • 查询操作:调用 executeQuery(),返回 ResultSet,需遍历处理。

步骤 6:释放资源(关键)

JDBC 资源(ResultSetStatementConnection)属于 “非内存资源”,必须手动关闭,否则会导致数据库连接泄露,耗尽数据库连接池。

释放顺序:ResultSet → Statement → Connection(先创建的后关闭)
推荐使用 try-with-resources(Java 7+ 特性):

自动关闭实现了 AutoCloseable 接口的资源(JDBC 核心组件均实现了该接口),无需手动写 finally,代码更简洁。

4. JDBC 事务管理

JDBC 事务默认是 自动提交autoCommit = true),即每执行一条 INSERT/UPDATE/DELETE 语句就自动提交到数据库。但实际开发中,需保证 “一组操作要么全成功,要么全失败”(如转账:扣钱和加钱必须同时成功),此时需手动控制事务。

事务控制步骤:

  1. 获取 Connection 后,设置 autoCommit = false(关闭自动提交);
  2. 执行一组 SQL 操作;
  3. 若所有操作成功,调用 conn.commit() 提交事务;
  4. 若发生异常,调用 conn.rollback() 回滚事务(恢复到操作前状态);
  5. 最终在 finally 中关闭资源。
事务示例(转账):
try (Connection conn = DriverManager.getConnection(URL, USER, PWD)) {
    conn.setAutoCommit(false); // 关闭自动提交,开启手动事务
    
    // 1. 扣减用户 A 的钱
    String sql1 = "UPDATE account SET balance = balance - 100 WHERE name = ?";
    try (PreparedStatement pstmt1 = conn.prepareStatement(sql1)) {
        pstmt1.setString(1, "A");
        pstmt1.executeUpdate();
    }
    
    // 2. 增加用户 B 的钱
    String sql2 = "UPDATE account SET balance = balance + 100 WHERE name = ?";
    try (PreparedStatement pstmt2 = conn.prepareStatement(sql2)) {
        pstmt2.setString(1, "B");
        pstmt2.executeUpdate();
    }
    
    conn.commit(); // 所有操作成功,提交事务
    System.out.println("转账成功!");
} catch (SQLException e) {
    // 若有异常,回滚事务(注意:需在 catch 中获取 Connection,需调整代码结构)
    System.out.println("转账失败,回滚事务!");
    e.printStackTrace();
}

事务保存点(Savepoint)

若需要 “部分回滚”(如一组操作中,前 2 步成功,第 3 步失败,仅回滚第 3 步),可使用 Savepoint

try (Connection conn = DriverManager.getConnection(URL, USER, PWD)) {
    conn.setAutoCommit(false);
    PreparedStatement pstmt = null;
    
    // 操作 1:成功
    pstmt = conn.prepareStatement("UPDATE account SET balance = balance - 50 WHERE name = 'A'");
    pstmt.executeUpdate();
    
    // 设置保存点(标记操作 1 后的状态)
    Savepoint sp = conn.setSavepoint();
    
    // 操作 2:失败(假设 SQL 错误)
    try {
        pstmt = conn.prepareStatement("UPDATE account SET balance = balance + 50 WHERE name = 'C'"); // C 不存在
        pstmt.executeUpdate();
    } catch (SQLException e) {
        // 仅回滚到保存点(操作 1 不回滚,操作 2 回滚)
        conn.rollback(sp);
        System.out.println("操作 2 失败,回滚到保存点!");
    }
    
    conn.commit(); // 提交操作 1 的结果
}

5. JDBC 优缺点

优点:

  1. 跨数据库兼容性:统一接口,切换数据库只需更换驱动和 URL;
  2. 原生 SQL 支持:直接操作 SQL,保留数据库的所有功能(如存储过程、函数);
  3. 轻量级:无需依赖额外框架,仅需驱动包;
  4. 性能较高:底层直接与数据库交互,无中间层开销。

缺点:

  1. 代码冗余:每次操作都需重复写 “获取连接→创建 Statement→释放资源”;
  2. SQL 硬编码:SQL 语句写在 Java 代码中,修改需重新编译;
  3. 手动管理资源:容易遗漏关闭资源,导致连接泄露;
  4. 结果集处理繁琐:需手动将 ResultSet 映射为 Java 对象(如 User、Order)。

注意:JDBC 的缺点催生了 ORM 框架(如 MyBatis、Hibernate),这些框架基于 JDBC 封装,简化了代码编写,但 JDBC 是底层基础,必须掌握。

6. 完整示例代码(查询 + 增删改)

6.1 查询示例(获取用户列表)

import java.sql.*;

public class JdbcQueryExample {
    // 数据库连接信息(建议配置在配置文件中,此处为演示)
    private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC";
    private static final String USER = "root";
    private static final String PWD = "123456";

    public static void main(String[] args) {
        // try-with-resources 自动关闭 ResultSet、PreparedStatement、Connection
        try (Connection conn = DriverManager.getConnection(URL, USER, PWD);
             // 预编译查询 SQL(? 是参数占位符)
             PreparedStatement pstmt = conn.prepareStatement("SELECT id, name, age FROM user WHERE age > ?");
        ) {
            // 给 ? 赋值(索引从 1 开始)
            pstmt.setInt(1, 18);
            
            // 执行查询,获取结果集
            try (ResultSet rs = pstmt.executeQuery()) {
                // 遍历结果集
                while (rs.next()) {
                    int id = rs.getInt("id");       // 通过列名获取
                    String name = rs.getString("name");
                    int age = rs.getInt("age");
                    System.out.printf("id: %d, name: %s, age: %d%n", id, name, age);
                }
            }
        } catch (SQLException e) {
            System.out.println("查询失败!");
            e.printStackTrace();
        }
    }
}

6.2 增删改示例(插入用户)

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

public class JdbcUpdateExample {
    private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC";
    private static final String USER = "root";
    private static final String PWD = "123456";

    public static void main(String[] args) {
        // 插入用户的 SQL
        String sql = "INSERT INTO user(name, age) VALUES (?, ?)";
        
        try (Connection conn = DriverManager.getConnection(URL, USER, PWD);
             PreparedStatement pstmt = conn.prepareStatement(sql)
        ) {
            // 赋值参数
            pstmt.setString(1, "李四");
            pstmt.setInt(2, 22);
            
            // 执行增删改,返回受影响行数
            int rows = pstmt.executeUpdate();
            if (rows > 0) {
                System.out.println("插入成功!受影响行数:" + rows);
            } else {
                System.out.println("插入失败!");
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

7. 关键注意事项

  1. URL 格式正确性:不同数据库的 URL 格式不同,MySQL 8.0+ 必须配置 serverTimezone
  2. 参数索引从 1 开始PreparedStatement? 占位符索引从 1 开始,而非 0;
  3. 资源必须关闭:优先使用 try-with-resources,避免资源泄露;
  4. 防 SQL 注入:严禁使用 Statement 拼接 SQL(如 sql = "SELECT * FROM user WHERE name = '" + name + "'"),必须用 PreparedStatement 的参数化查询;
  5. 事务手动控制:涉及多步操作时,必须关闭自动提交(setAutoCommit(false)),并在异常时回滚。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值