JDBC教程,2025版最新讲解.超详细入门教程
以下内容全面详尽地梳理了 JDBC (Java Database Connectivity)的核心知识点,并在关键环节配以示例代码。若要快速定位,可先查看下方结构:
- JDBC 概览
- 驱动加载与注册
- 获取数据库连接
- 执行 SQL(Statement、PreparedStatement、CallableStatement)
- 处理结果集(ResultSet)
- 批量操作与批处理
- 事务管理
- 元数据操作(DatabaseMetaData、ResultSetMetaData)
- 异常处理
- 连接池与 DataSource
- 最佳实践与性能优化
1. JDBC 概览
- 用途:提供 Java 程序访问各种关系型数据库(MySQL、Oracle、PostgreSQL 等)的统一 API。
- 核心接口包:
java.sql.*
- 主要角色:
- DriverManager/Driver:驱动注册、连接分发
- Connection:管理会话、事务
- Statement/PreparedStatement/CallableStatement:执行 SQL
- ResultSet:承载查询结果
- DataSource:替代 DriverManager 的连接获取方式(可集成连接池)
执行过程可总结为:
- 驱动加载(现在可以省略)
- 获取数据库连接
- 定义要执行的sql语句
- 获取执行对象
- 执行对象执行sql语句,获取返回结果
- 后续处理
2. 驱动加载与注册
2.1 自动注册(JDBC 4.0+)
只要在 classpath 中包含相应数据库的 JDBC 驱动(如 mysql-connector-java.jar
),Driver 会通过 SPI 自动注册,无需显式调用。
// 不需要 Class.forName(...),直接拿连接即可
Connection conn = DriverManager.getConnection("jdbc:mysql://localhost:3306/testdb", "root", "password");
2.2 手动加载(向后兼容)
Class.forName("com.mysql.cj.jdbc.Driver");
// 或者老版本
// Class.forName("com.mysql.jdbc.Driver");
3. 获取数据库连接
String url = "jdbc:mysql://localhost:3306/testdb?useSSL=false&serverTimezone=UTC";
String user = "root";
String password = "password";try (Connection conn = DriverManager.getConnection(url, user, password)) {// 使用 conn
} catch (SQLException e) {e.printStackTrace();
}
- 常用参数:
useSSL=false
:禁用 SSLserverTimezone=UTC
:指定时区autoReconnect=true
:自动重连
当然可以,以下是重新整理并扩充后的第4部分内容,加入了 SQL 注入的讲解,并对 Statement
的两个方法做了深入分析,保持原有结构和风格:
4. 获取执行 SQL 对象,Cnn 获取
在 JDBC 中,获取数据库连接后,我们需要通过执行 SQL 语句与数据库交互,这一过程通常通过以下三种执行对象实现:
Statement
:用于执行静态 SQL 语句(不带参数),简单但易受 SQL 注入攻击;PreparedStatement
:预编译 SQL 语句,支持参数绑定,性能更优、安全性更高;
Statement 的核心方法:
int executeUpdate(String sql)
- 执行 DML(INSERT、UPDATE、DELETE) 或 DDL(CREATE、DROP)。
- 返回值为 受影响的行数,若为 DDL,通常返回 0(不代表失败)。
ResultSet executeQuery(String sql)
- 执行 DQL(SELECT)语句,返回查询结果集
ResultSet
。
关于 SQL 注入(SQL Injection)
SQL 注入是一种严重的安全漏洞,攻击者可以通过拼接恶意 SQL 来操控数据库。例如:
String name = "' OR '1'='1";
String sql = "SELECT * FROM user WHERE name = '" + name + "'";
构造出的 SQL 实际是:
SELECT * FROM user WHERE name = '' OR '1'='1'
where后面的结果将永远是真的,所以就可以永远的查询出来.
将返回所有用户数据,甚至可用于删除表结构,极其危险!
4.1 Statement(不预编译,易受 SQL 注入)
String name = "张三";
String sql = "SELECT id, name FROM user WHERE name = '" + name + "'";
try (Statement stmt = conn.createStatement();ResultSet rs = stmt.executeQuery(sql)
) {while (rs.next()) {System.out.println(rs.getInt("id") + ": " + rs.getString("name"));}
}
优点:简单,适合一次性、非常简单的 SQL;
缺点:拼接字符串,极易受 SQL 注入攻击,不能复用或预编译,性能差。
4.2 PreparedStatement(预编译,防注入、性能更优)
String sql = "SELECT id, name FROM user WHERE name = ? AND age > ?";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {pstmt.setString(1, "张三");pstmt.setInt(2, 18);try (ResultSet rs = pstmt.executeQuery()) {while (rs.next()) {System.out.println(rs.getInt("id") + ": " + rs.getString("name"));}}
}
优点:
- 使用 占位符 ? 实现参数绑定,不拼接字符串,有效防止 SQL 注入
- SQL 会被 预编译并缓存,适合多次执行,提高效率
- 自动转义字符串,避免因特殊字符出错
缺点:相比 Statement 书写稍复杂,不适合拼接动态结构的 SQL(如条件列名)。
Statement vs PreparedStatement 详细对比
特性 | Statement | PreparedStatement |
---|---|---|
SQL 预编译 | 否 | 是(可缓存执行计划) |
防 SQL 注入能力 | 差(需要手动拼接字符串) | 强(使用参数绑定) |
性能(重复执行场景) | 差(每次都解析) | 优(执行计划可复用) |
可读性与维护性 | 差(大量拼接容易混乱) | 高(结构清晰,安全) |
支持占位符 ? | 否 | 是 |
使用场景 | 简单、临时查询 | 正式项目、复杂参数、安全性要求高场合 |
5. 处理结果集(ResultSet)
- 指针导航:
.next()
,.first()
,.last()
,.absolute(n)
(需指定 TYPE_SCROLL) - 获取列值:
getInt()
,getString()
,getDate()
等 - 按列名或列索引
- 类型转换注意:避免隐式类型转换带来的性能/精度问题
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {int id = rs.getInt(1);String name = rs.getString("name");Date dob = rs.getDate("dob");// ...
}
6. 批量操作与批处理
批量执行可显著提升大量插入/更新的性能。
String sql = "INSERT INTO user(name, age) VALUES(?, ?)";
try (PreparedStatement pstmt = conn.prepareStatement(sql)) {conn.setAutoCommit(false); // 关闭自动提交for (int i = 1; i <= 1000; i++) {pstmt.setString(1, "User" + i);pstmt.setInt(2, 20 + (i % 30));pstmt.addBatch();if (i % 200 == 0) {pstmt.executeBatch();conn.commit(); // 分批提交}}// 提交剩余pstmt.executeBatch();conn.commit();
}
7. 事务管理Cnn对象负责管理
try {conn.setAutoCommit(false); // 开启事务// 操作1// 操作2// ...conn.commit(); // 提交事务
} catch (SQLException e) {conn.rollback(); // 回滚事务
} finally {conn.setAutoCommit(true); // 恢复默认
}
- 隔离级别:
conn.setTransactionIsolation(...)
- 保存点:
Savepoint sp = conn.setSavepoint("sp1"); conn.rollback(sp);
8. 元数据操作
8.1 DatabaseMetaData
DatabaseMetaData dbMeta = conn.getMetaData();
System.out.println("Database product: " + dbMeta.getDatabaseProductName());
System.out.println("Driver version: " + dbMeta.getDriverVersion());
8.2 ResultSetMetaData
ResultSetMetaData rsMeta = rs.getMetaData();
int columnCount = rsMeta.getColumnCount();
for (int i = 1; i <= columnCount; i++) {System.out.println("Column " + i + ": " + rsMeta.getColumnName(i)+ " (" + rsMeta.getColumnTypeName(i) + ")");
}
9. 异常处理
- SQLException:
.getErrorCode()
:数据库错误码.getSQLState()
:标准 SQLState
- 资源释放:强烈推荐使用
try-with-resources
自动关闭Connection
、Statement
、ResultSet
try (Connection conn = …;PreparedStatement pstmt = conn.prepareStatement(...);ResultSet rs = pstmt.executeQuery();
) {// ...
} catch (SQLException e) {System.err.println("Error Code: " + e.getErrorCode());System.err.println("SQL State : " + e.getSQLState());e.printStackTrace();
}
10. 连接池与 DataSource
10.1 为什么使用连接池
- 连接创建开销大
- 重复使用可提升性能
10.2 常见连接池
- HikariCP(高性能)
- Apache DBCP
- C3P0
10.3 DataSource 示例(HikariCP)
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/testdb");
config.setUsername("root");
config.setPassword("password");
config.setMaximumPoolSize(10);DataSource ds = new HikariDataSource(config);// 获取连接
try (Connection conn = ds.getConnection()) {// ...
}
11. 最佳实践与性能优化
- 使用 PreparedStatement 防止 SQL 注入、提升性能。
- 合理设置批量大小,避免长事务占用资源。
- 关闭不必要的自动提交,批量操作时手动管理事务。
- 使用连接池,避免频繁创建销毁连接。
- 定期监控慢查询,并为频繁访问的字段添加索引。
- 尽量只查询所需列,避免
SELECT *
。 - 及时关闭资源,防止连接泄漏(
try-with-resources
)。
IDEA调试总结:
更通俗、贴近实际编程操作的语言来重新解释 IntelliJ IDEA 中调试时常用的快捷键,避免术语,让你一看就懂:
IDEA 调试快捷键(简单易懂版)
功能 | Windows / Linux | macOS | 说明(通俗解释) |
---|---|---|---|
启动调试 | Shift + F9 | Control + D | 像运行程序一样运行,但可以中途停下来看代码状态 |
停止调试 | Ctrl + F2 | Command + F2 | 停掉整个程序 |
一步一步执行代码(当前行) | F8 | F8 | 当前行执行完再停在下一行(如果是循环,就跳下一次) |
进入方法体中去看具体执行 | F7 | F7 | 如果当前行是一个函数调用,按它会跳进去函数里看细节 |
跳出当前方法返回上一层 | Shift + F8 | Shift + F8 | 如果你已经进了方法里,按这个会回到调用这个方法的地方 |
执行到光标处停下 | Alt + F9 | Option + F9 | 让程序一直跑到你当前鼠标点的位置再停下(省得一直按F8) |
继续往下执行程序 | F9 | Command + Option + R 或 F9 | 程序继续往下跑,直到遇到下一个断点 |
设置或取消断点 | Ctrl + F8 | Command + F8 | 在代码左边点一下,红点就是断点,程序会在这里暂停 |
常用调试辅助操作
功能 | Windows / Linux | macOS | 说明 |
---|---|---|---|
查看某个变量的值 | 鼠标悬停 | 鼠标悬停 | 把鼠标放在变量上面,就会显示它当前的值 |
手动输入表达式查看结果 | Alt + F8 | Option + F8 | 弹出一个窗口,你可以输入变量或表达式看结果,比如 a + b |
查看所有断点 | Ctrl + Shift + F8 | Command + Shift + F8 | 可以集中管理所有断点 |
快速查看变量内容 | Ctrl + Shift + I | Command + Shift + I | 不用跳转,临时查看变量内容 |
举个简单例子:
public class Demo {public static void main(String[] args) {int a = 10;int b = 20;int c = add(a, b); // 👉 在这里打断点System.out.println("结果是:" + c);}public static int add(int x, int y) {return x + y;}
}
如果你在 int c = add(a, b);
那一行打断点:
- 按
Shift + F9
开始调试; - 程序会停在
add(a, b)
那一行; - 按
F8
,它会直接执行完这一行然后到下一行; - 按
F7
,它会跳进add()
方法里面; - 在方法中可以继续按
F8
看一步步怎么返回结果; - 再按
Shift + F8
就可以返回到 main() 继续调试; - 最后按
F9
可以直接让程序跑完。
如果你还不熟练,可以先只记住这两个:
- 👉
F8
:一步一步往下执行; - 👉
F7
:进入函数体里看看里面是怎么执行的。