JDBC速成
一、JDBC 简介
什么是 JDBC?
-
JDBC(Java Database Connectivity)是 Java 官方提供的一套 API,集成在
java.sql和javax.sql包中 -
JDBC 允许 Java 程序连接和操作各种关系型数据库,如 MySQL、Oracle、PostgreSQL、SQL Server 等
JDBC 是基于驱动的架构:
Java 应用程序
↓
JDBC API(Connection、Statement 等)
↓
JDBC Driver(由数据库厂商提供)
↓
数据库(MySQL、Oracle、PostgreSQL等)
二、JDBC 核心组件
| 类/接口 | 说明 |
|---|---|
DriverManager |
管理 JDBC 驱动和数据库连接 |
Connection |
表示数据库连接 |
Statement |
执行静态 SQL 语句 |
PreparedStatement |
执行预编译 SQL,防止 SQL 注入 |
CallableStatement |
执行数据库存储过程 |
ResultSet |
表示查询结果集 |
ResultSetMetaData |
获取结果集列信息 |
DatabaseMetaData |
获取数据库结构信息 |
SQLException |
数据库操作异常 |
三、JDBC 使用六步法(详解)
第一步:加载数据库驱动(可选)
Class.forName("com.mysql.cj.jdbc.Driver");
- MySQL 8.x:
com.mysql.cj.jdbc.Driver - Oracle:
oracle.jdbc.driver.OracleDriver
注意:JDBC 4.0+ 不强制要求手动加载驱动,JDBC 4.0 引入了 SPI(Service Provider Interface) 机制,DriverManager 会自动加载并注册
第二步:获取数据库连接
String url = "jdbc:mysql://localhost:3306/database?serverTimezone=Asia/Shanghai";
Connection conn = DriverManager.getConnection(url, "username", "password");
URL 格式:
jdbc:mysql://主机名:端口/数据库名?参数=值&参数2=值2
第三步:创建执行对象
Statement(容易 SQL 注入)
// 不安全的写法(字符串拼接会有 SQL 注入风险)
String name = "flying.";
String sql = "SELECT * FROM user WHERE name = '" + name + "'";
Statement stmt = conn.createStatement();
stmt.executeQuery(sql)
PreparedStatement(推荐使用)
问号 ? 是参数占位符,用于给 setInt 或 setString 指定参数
String sql = "SELECT * FROM user WHERE id = ? and name = ?";
PreparedStatement ps = conn.prepareStatement(sql);
ps.setInt(1, 1001);
ps.setString(2, "flying.");
第四步:执行 SQL 语句
查询(返回 ResultSet)
ResultSet rs = ps.executeQuery();
如果 SQL 语句是增删改(只返回影响的行数)
int count = ps.executeUpdate();
第五步:处理结果集
while (rs.next()) {
int id = rs.getInt("id"); // 获取id字段,int类型
String name = rs.getString("name"); // 获取name字段,string类型(varchar、char)
}
第六步:关闭资源(从小到大)
rs.close();
ps.close();
conn.close();
也可以用 Java 7+ 的 try-with-resources:
try (Connection conn = ...; PreparedStatement ps = ...) {
// todo
}
四、PreparedStatement 与 Statement 对比
| 特性 | Statement | PreparedStatement |
|---|---|---|
| SQL 拼接 | 手动拼接 | 使用参数占位符 ? |
| 安全性 | 易被 SQL 注入攻击 | 安全 |
| 性能 | SQL 每次重新编译 | 支持预编译,效率更高 |
| 可读性 | 差 | 好 |
五、常用 JDBC 方法解释
Connection 接口常用方法
| 方法 | 功能 |
|---|---|
createStatement() |
创建 Statement |
prepareStatement(String sql) |
创建预编译语句 |
setAutoCommit(boolean) |
设置是否自动提交事务 |
commit() |
提交事务 |
rollback() |
回滚事务 |
close() |
关闭连接 |
Statement 常用方法
| 方法 | 功能 |
|---|---|
executeQuery(String sql) |
执行查询语句,返回 ResultSet |
executeUpdate(String sql) |
执行更新语句,返回影响行数 |
execute(String sql) |
可执行任意语句 |
addBatch() |
添加批处理 |
executeBatch() |
执行批处理 |
clearBatch() |
清除批处理 |
PreparedStatement 常用方法(其他方法都继承自 Statement)
注意:
-
继承的
executeQuery、executeUpdate和execute这三个方法是不用参数的 -
且
setInt、setString和setDate等setXxx方法的第一个参数是指定 SQL 语句中的哪一个?占位符(从 1 开始),第二个参数是给?占位符赋的值
| 方法 | 功能 |
|---|---|
executeQuery() |
执行查询语句,返回 ResultSet |
executeUpdate() |
执行更新语句,返回影响行数 |
execute() |
可执行任意语句 |
setInt(int, int) |
设置整型参数 |
setString(int, String) |
设置字符串参数 |
setFloat(int, double) |
设置浮点型参数 |
setDouble(int, double) |
设置双精度浮点型参数 |
setBoolean(int, boolean) |
设置布尔型参数 |
setDate(int, Date) |
设置日期参数 |
CallableStatement 常用方法(继承 PreparedStatement 的方法)
CallableStatement 不常用,不记也行
| 方法 | 功能 |
|---|---|
prepareCall(String sql) |
创建 CallableStatement |
registerOutParameter(int index, SQLType) |
注册 OUT 参数 |
getXxx(int index) |
获取 OUT 参数结果 |
ResultSet 常用方法
注意:
-
ResultSet对象是executeQuery()方法的返回值 -
getXxx和 上面PreparedStatement的各种set方法是对应的,代入即可,如既然有setInt那就肯定有setInt
| 方法 | 功能 |
|---|---|
next() |
移动到下一行数据,有下一行数据则返回 True |
privious()() |
移动到上一行数据,作用同上 |
getString("列名") |
获取字符串数据 |
getInt("列名") |
获取整数数据 |
wasNull() |
判断上一个读取值是否为 null |
close() |
关闭结果集 |
六、事务管理
JDBC 默认自动提交。可以通过以下代码实现手动提交:
示例:
conn.setAutoCommit(false);
try {
// SQL 操作
conn.commit();
} catch (Exception e) {
conn.rollback(); // 回滚是指本次的批量 SQL 操作都撤消掉
}
设置隔离级别
conn.setTransactionIsolation(Connection.TRANSACTION_REPEATABLE_READ);
隔离级别说明:
| 隔离级别 | 常见问题 | 描述 |
|---|---|---|
| READ_UNCOMMITTED | 脏读 | 最低级别 |
| READ_COMMITTED | 不可重复读 | Oracle 默认 |
| REPEATABLE_READ | 幻读 | MySQL 默认 |
| SERIALIZABLE | 性能最低,最安全 | 完全串行执行 |
七、批处理执行(Batch Processing)
优点:
- 提高执行效率
- 减少网络通信次数
使用场景:
- 大量重复执行相同结构的 SQL(如插入、更新)
示例:
String sql = "INSERT INTO users(name, age) VALUES (?, ?)";
PreparedStatement ps = conn.prepareStatement(sql);
for (int i = 0; i < 1000; i++) {
ps.setString(1, "user" + i);
ps.setInt(2, 20 + i % 10);
ps.addBatch(); // 添加到批处理队列
if (i % 100 == 0) {
ps.executeBatch(); // 每 100 条执行一次
ps.clearBatch(); // 清空批处理队列
}
}
ps.executeBatch(); // 执行队列剩余语句,如:不足 100 条的情况出现
ps.close();
八、分页查询(以 MySQL 为例)
用于查询数据库大表时分批显示数据。
语法:
SELECT * FROM 表名 LIMIT ?, ?
- 第一个
?:起始位置(从 0 开始) - 第二个
?:每页数量
示例:
String sql = "SELECT * FROM user LIMIT ?, ?";
PreparedStatement ps = conn.prepareStatement(sql);
int page = 2;
int pageSize = 10;
ps.setInt(1, (page - 1) * pageSize);
ps.setInt(2, pageSize);
ResultSet rs = ps.executeQuery();
九、ResultSetMetaData / DatabaseMetaData 使用
这两不常用,不记也行
ResultSetMetaData:用于获取结果集的列信息
ResultSetMetaData meta = rs.getMetaData();
int columnCount = meta.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
System.out.println(meta.getColumnName(i));
}
DatabaseMetaData:获取数据库结构信息
DatabaseMetaData dbMeta = conn.getMetaData();
ResultSet rs = dbMeta.getTables(null, null, "%", new String[]{"TABLE"});
十、常见异常与解决方案
| 报错信息 | 原因 | 解决办法 |
|---|---|---|
ClassNotFoundException |
未加载驱动 | 添加驱动类/依赖 |
SQLException: Access denied |
数据库用户名或密码错误 | 检查登录信息 |
Communications link failure |
数据库未启动或 IP 错误 | 启动数据库/检查 URL |
No suitable driver |
URL 或驱动不兼容 | 检查驱动版本和 JDBC URL |
总结重点
- JDBC 是数据库编程的基础
PreparedStatement安全又高效- 事务管理需掌握:手动控制、异常回滚
- 批处理与分页能显著提升性能
完整 JDBC 查询示例代码
面向过程风格
import java.sql.*;
public class Example {
public static void main(String[] args) {
// 1. JDBC连接参数
String url = "jdbc:mysql://localhost:3306/database?serverTimezone=Asia/Shanghai";
String user = "root"; // 数据库用户名
String password = "123456"; // 数据库密码
// 2. JDBC资源
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
try {
// 3. 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 4. 建立数据库连接
conn = DriverManager.getConnection(url, user, password);
// 5. 编写 SQL 查询语句
String sql = "SELECT id, name, age FROM user WHERE age > ?";
// 6. 创建 PreparedStatement
ps = conn.prepareStatement(sql);
ps.setInt(1, 18); // 查询年龄大于 18 的用户
// 7. 执行查询,返回结果集
rs = ps.executeQuery();
// 8. 处理结果集
System.out.println("查询结果:");
while (rs.next()) {
int id = rs.getInt("id"); // 或 rs.getInt(1)
String name = rs.getString("name"); // 或 rs.getString(2)
int age = rs.getInt("age"); // 或 rs.getInt(3)
System.out.printf("ID: %d, 姓名: %s, 年龄: %d%n", id, name, age);
}
} catch (ClassNotFoundException e) {
System.out.println("数据库驱动未找到!");
} catch (SQLException e) {
System.out.println("数据库操作异常!");
} finally {
// 9. 释放资源(从小到大)
try { if (rs != null) rs.close(); } catch (SQLException ignored) {}
try { if (ps != null) ps.close(); } catch (SQLException ignored) {}
try { if (conn != null) conn.close(); } catch (SQLException ignored) {}
}
}
}
面向对象风格
import java.sql.*;
public class Example {
public static void main(String[] args) {
// JDBC 连接参数
String url = "jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "123456";
// 查询语句
String sql = "SELECT id, name, age FROM user WHERE age > ?";
// try-with-resources 会自动关闭资源,无需手动 close()
try {
// 1. 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
try (
// 2. 建立连接
Connection conn = DriverManager.getConnection(url, user, password);
// 3. 创建 PreparedStatement
PreparedStatement ps = conn.prepareStatement(sql);
) {
// 4. 设置参数
ps.setInt(1, 18);
// 5. 执行查询
try (ResultSet rs = ps.executeQuery()) {
System.out.println("查询结果:");
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
int age = rs.getInt("age");
System.out.printf("ID: %d, 姓名: %s, 年龄: %d\n", id, name, age);
}
}
}
} catch (ClassNotFoundException e) {
System.out.println("数据库驱动未找到!");
} catch (SQLException e) {
System.out.println("数据库操作异常!");
}
}
}





