0%

JDBC基本操作

JDBC 详解:Java 数据库连接的核心操作

JDBC(Java Database Connectivity)是 Java 访问数据库的标准接口,它定义了一套独立于特定数据库管理系统(DBMS)的 API,使 Java 程序能够通过统一的方式操作各种关系型数据库(如 MySQL、Oracle、SQL Server 等)。本文将详细讲解 JDBC 的核心概念、连接步骤及常用操作,帮助你掌握 Java 与数据库交互的基础。

JDBC 核心组件

JDBC 主要通过以下接口和类实现数据库交互:

组件 作用描述
Driver 数据库驱动接口,由各数据库厂商实现(如 MySQL 的 com.mysql.jdbc.Driver)。
DriverManager 驱动管理类,负责注册驱动、创建数据库连接。
Connection 数据库连接接口,代表与数据库的会话,用于创建执行 SQL 的对象。
Statement 执行静态 SQL 语句的接口,存在 SQL 注入风险。
PreparedStatement 预编译 SQL 语句的接口,可防止 SQL 注入,性能更优。
ResultSet 结果集接口,存储查询返回的数据,提供遍历和获取数据的方法。

JDBC 连接数据库的步骤

使用 JDBC 操作数据库的核心步骤可概括为:加载驱动 → 建立连接 → 执行 SQL → 处理结果 → 释放资源

准备工作

  • 引入数据库驱动:根据数据库类型添加对应的驱动 JAR 包(如 MySQL 驱动 mysql-connector-java)。

  • 创建数据库和表:以 MySQL 为例,创建测试表user:

    1
    2
    3
    4
    CREATE TABLE user (
    id INT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) NOT NULL
    ) ENGINE = InnoDB DEFAULT CHARSET = utf8;

加载驱动与建立连接

(1)核心参数定义
1
2
3
4
5
6
7
8
// 数据库驱动类名(MySQL 8.0+ 需使用 com.mysql.cj.jdbc.Driver)
private static final String DRIVER = "com.mysql.jdbc.Driver";
// 数据库连接 URL(格式:jdbc:数据库类型://主机:端口/数据库名)
private static final String URL = "jdbc:mysql://localhost:3306/test?useSSL=false";
// 数据库用户名
private static final String USER = "root";
// 数据库密码
private static final String PASSWORD = "123456";
(2)加载驱动

通过 Class.forName(DRIVER) 加载驱动类,其原理是触发驱动类中的静态代码块,向 DriverManager 注册驱动实例:

1
2
3
4
5
6
7
8
// MySQL 驱动类的静态代码块(自动注册驱动)
static {
try {
DriverManager.registerDriver(new Driver());
} catch (SQLException e) {
throw new RuntimeException("驱动注册失败");
}
}
(3)建立连接

通过 DriverManager.getConnection(URL, USER, PASSWORD) 获取 Connection 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* 获取数据库连接
*/
public static Connection getConnection() {
Connection conn = null;
try {
// 加载驱动(MySQL 5.1+ 可省略此步,DriverManager 会自动扫描并加载驱动)
Class.forName(DRIVER);
// 建立连接
conn = DriverManager.getConnection(URL, USER, PASSWORD);
System.out.println("数据库连接成功");
} catch (ClassNotFoundException e) {
throw new RuntimeException("驱动加载失败:" + e.getMessage());
} catch (SQLException e) {
throw new RuntimeException("连接建立失败:" + e.getMessage());
}
return conn;
}

注意:MySQL 5.1 之后的驱动可省略 Class.forName(DRIVER),因为 JDBC 4.0 引入了自动加载机制(通过 META-INF/services/java.sql.Driver 文件)。

释放资源

数据库连接、Statement、ResultSet 都是稀缺资源,使用后必须关闭,建议在 finally 块中处理:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 关闭资源(Connection、PreparedStatement、ResultSet)
*/
public static void close(Connection conn, PreparedStatement pstmt, ResultSet rs) {
try {
if (rs != null) rs.close(); // 先关闭结果集
if (pstmt != null) pstmt.close(); // 再关闭语句对象
if (conn != null) conn.close(); // 最后关闭连接
} catch (SQLException e) {
e.printStackTrace();
}
}

执行 SQL 语句:Statement 与 PreparedStatement

Statement:执行静态 SQL(不推荐)

Statement 用于执行静态 SQL 语句(SQL 字符串固定),但存在 SQL 注入风险,且每次执行都需重新编译 SQL,性能较差。

示例:插入数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 使用 Statement 插入数据(存在 SQL 注入风险)
*/
public static void insertWithStatement(String name) {
Connection conn = null;
Statement stmt = null;
try {
conn = getConnection();
stmt = conn.createStatement();
// 拼接 SQL(危险!若 name 包含恶意代码,如 "张三'; DROP TABLE user; --",将导致数据泄露或破坏)
String sql = "INSERT INTO user (name) VALUES ('" + name + "')";
int rows = stmt.executeUpdate(sql); // 执行 INSERT/UPDATE/DELETE,返回受影响行数
System.out.println("插入成功,影响行数:" + rows);
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(conn, stmt, null); // ResultSet 为 null
}
}

PreparedStatement:预编译 SQL(推荐)

PreparedStatementStatement 的子接口,支持 带占位符(?)的 SQL,执行前会预编译 SQL 模板,后续只需填充参数,可:

  • 防止 SQL 注入(参数自动转义);
  • 重复执行时无需重新编译,性能更优。
示例:插入、更新、删除数据
(1)插入数据(含自增主键获取)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
* 使用 PreparedStatement 插入数据,并获取自增主键
*/
public static int insertWithPreparedStatement(String name) {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
int generatedId = -1; // 存储自增主键
try {
conn = getConnection();
// SQL 模板(? 为占位符,索引从 1 开始)
String sql = "INSERT INTO user (name) VALUES (?)";
// 第二个参数指定返回自增主键
pstmt = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS);
// 设置参数(1 表示第一个占位符,String 类型)
pstmt.setString(1, name);
// 执行插入,返回受影响行数
int rows = pstmt.executeUpdate();
System.out.println("插入成功,影响行数:" + rows);

// 获取自增主键
rs = pstmt.getGeneratedKeys();
if (rs.next()) {
generatedId = rs.getInt(1); // 主键列索引为 1
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(conn, pstmt, rs);
}
return generatedId;
}
(2)更新数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* 使用 PreparedStatement 更新数据
*/
public static void updateWithPreparedStatement(int id, String newName) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = getConnection();
String sql = "UPDATE user SET name = ? WHERE id = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, newName); // 第一个占位符:新名称
pstmt.setInt(2, id); // 第二个占位符:ID
int rows = pstmt.executeUpdate();
System.out.println("更新成功,影响行数:" + rows);
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(conn, pstmt, null);
}
}
(3)删除数据
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/**
* 使用 PreparedStatement 删除数据
*/
public static void deleteWithPreparedStatement(int id) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = getConnection();
String sql = "DELETE FROM user WHERE id = ?";
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, id);
int rows = pstmt.executeUpdate();
System.out.println("删除成功,影响行数:" + rows);
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(conn, pstmt, null);
}
}

查询数据:处理 ResultSet

executeQuery() 用于执行 SELECT 语句,返回 ResultSet 结果集,需通过 next() 方法遍历数据行,通过列名或索引获取字段值。

示例:查询所有用户
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 查询所有用户并打印结果
*/
public static void queryAllUsers() {
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
conn = getConnection();
String sql = "SELECT id, name FROM user";
pstmt = conn.prepareStatement(sql);
rs = pstmt.executeQuery(); // 执行查询,返回结果集

// 遍历结果集(rs.next() 移动到下一行,初始在第一行之前)
while (rs.next()) {
int id = rs.getInt("id"); // 通过列名获取
String name = rs.getString(2); // 通过列索引获取(索引从 1 开始)
System.out.println("ID: " + id + ", Name: " + name);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
close(conn, pstmt, rs); // 务必关闭结果集
}
}

批量操作:高效处理大量数据

当需要插入、更新多条记录时,使用批量操作可大幅提升性能(减少网络交互次数)。

示例:批量插入数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* 批量插入用户数据
*/
public static void batchInsertUsers(List<String> names) {
Connection conn = null;
PreparedStatement pstmt = null;
try {
conn = getConnection();
// 关闭自动提交(开启事务)
conn.setAutoCommit(false);
String sql = "INSERT INTO user (name) VALUES (?)";
pstmt = conn.prepareStatement(sql);

// 添加批量任务
for (String name : names) {
pstmt.setString(1, name);
pstmt.addBatch(); // 将 SQL 加入批处理队列
}

// 执行批量操作(返回每个 SQL 影响的行数数组)
int[] rows = pstmt.executeBatch();
System.out.println("批量插入完成,总影响行数:" + Arrays.stream(rows).sum());

// 提交事务
conn.commit();
} catch (SQLException e) {
// 事务回滚
if (conn != null) {
try {
conn.rollback();
} catch (SQLException ex) {
ex.printStackTrace();
}
}
e.printStackTrace();
} finally {
// 恢复自动提交
if (conn != null) {
try {
conn.setAutoCommit(true);
} catch (SQLException e) {
e.printStackTrace();
}
}
close(conn, pstmt, null);
}
}

优化点

  • 关闭自动提交(conn.setAutoCommit(false)),批量执行后手动提交,减少事务开销。
  • 批量大小适中(如每次 1000 条),避免内存溢出。

JDBC 最佳实践

  1. 优先使用 PreparedStatement:防止 SQL 注入,提升重复执行性能。
  2. 及时释放资源:在 finally 块中关闭 ConnectionPreparedStatementResultSet,避免资源泄露。
  3. 使用连接池:生产环境中,通过连接池(如 HikariCP、C3P0)管理连接,避免频繁创建和关闭连接的开销。
  4. 处理事务:对批量操作或多步 SQL,使用事务(commit()/rollback())保证数据一致性。
  5. 避免硬编码:数据库参数(URL、用户名、密码)应配置在外部文件(如 properties)中,便于维护

欢迎关注我的其它发布渠道