JDBC 基础
约 1089 字大约 4 分钟
布欧-Lewyon
2026-05-16
首页 › Java › JDBC 与数据访问 › JDBC 基础
JDBC 是 Java 连接数据库的最低层 API。无论你将来用 JdbcTemplate、JPA 还是 MyBatis,底层走的都是 JDBC。本节通过一个完整的 CRUD 示例,带你手写一遍"注册驱动 → 获取连接 → 执行 SQL → 处理结果 → 关闭资源"的完整流程。
Maven 依赖
JDBC 4.0+ 支持自动驱动加载——只要驱动 JAR 在 classpath 上,DriverManager 会自动发现。以 H2 内存数据库(无需安装)和 MySQL 为例:
<!-- H2(开发/测试用) -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.2.224</version>
<scope>test</scope>
</dependency>
<!-- MySQL 驱动(生产) -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<version>8.1.0</version>
</dependency>建立连接
Connection 代表与数据库的一个物理连接,通过 DriverManager.getConnection 获取:
// H2 内存数据库 URL
String url = "jdbc:h2:mem:testdb";
String user = "sa";
String password = "";
try (Connection conn = DriverManager.getConnection(url, user, password)) {
System.out.println("连接成功: " + conn.getMetaData().getDatabaseProductName());
}JDBC URL 格式:jdbc:<数据库厂商名>://<主机>:<端口>/<数据库名>,H2 内存库特殊格式 jdbc:h2:mem:testdb。
Statement vs PreparedStatement
永远使用 PreparedStatement,原因只有一个:防止 SQL 注入。Statement 通过字符串拼接构建 SQL,恶意输入可以改写语义:
// 危险!Statement 拼接
String input = "' OR '1'='1";
Statement stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery("SELECT * FROM users WHERE name = '" + input + "'");
// 实际执行:SELECT * FROM users WHERE name = '' OR '1'='1' → 查出所有用户PreparedStatement 正确用法
PreparedStatement 使用 ? 占位符预编译 SQL,参数值由 JDBC 驱动安全转义:
String sql = "INSERT INTO users (name, email, age) VALUES (?, ?, ?)";
try (Connection conn = DriverManager.getConnection(url, user, password);
PreparedStatement ps = conn.prepareStatement(sql)) {
// 设置参数(索引从 1 开始)
ps.setString(1, "Alice");
ps.setString(2, "alice@example.com");
ps.setInt(3, 30);
int rows = ps.executeUpdate(); // 返回影响行数
System.out.println("插入 " + rows + " 行");
}查询与 ResultSet
executeQuery() 返回 ResultSet,通过游标逐行读取:
String sql = "SELECT id, name, email, age FROM users WHERE age > ?";
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, 18);
ResultSet rs = ps.executeQuery();
while (rs.next()) { // 游标移到下一行
long id = rs.getLong("id");
String name = rs.getString("name");
String email = rs.getString("email");
int age = rs.getInt("age");
System.out.printf("%d | %s | %s | %d%n", id, name, email, age);
}
}rs.next()判断是否还有下一行,首次调用前游标指向第一行之前。- 列读取可以通过列名(推荐,可读性好)或列索引(从 1 开始)。
- 读取顺序:先遍历完
ResultSet,再关闭——ResultSet依赖其Statement。
完整的 CRUD 示例
public class UserDao {
private static final String URL = "jdbc:h2:mem:testdb";
private static final String SQL_CREATE = """
CREATE TABLE users (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) NOT NULL,
age INT
)""";
public static void main(String[] args) throws SQLException {
try (Connection conn = DriverManager.getConnection(URL, "sa", "")) {
// 建表
try (Statement st = conn.createStatement()) {
st.execute(SQL_CREATE);
}
// 插入
try (PreparedStatement ps = conn.prepareStatement(
"INSERT INTO users (name, email, age) VALUES (?, ?, ?)")) {
ps.setString(1, "Alice");
ps.setString(2, "alice@example.com");
ps.setInt(3, 30);
ps.executeUpdate();
}
// 查询
try (PreparedStatement ps = conn.prepareStatement("SELECT * FROM users");
ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getString("name"));
}
}
}
}
}资源关闭:务必 try-with-resources
Connection、Statement、ResultSet 都是资源,必须在 try-with-resources 中关闭。关闭顺序与创建顺序相反:ResultSet → Statement → Connection,但 try-with-resources 自动处理(声明逆序关闭)。
// 正确:全部自动关闭
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
while (rs.next()) { ... }
}连接池概念
每次请求都 DriverManager.getConnection() 开销很大(TCP 握手、认证)。连接池(如 HikariCP)维护一组复用连接:
// HikariCP 配置(概念示例)
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb");
config.setUsername("root");
config.setPassword("password");
config.setMaximumPoolSize(10); // 池中最大连接数
DataSource ds = new HikariDataSource(config);
Connection conn = ds.getConnection(); // 从池中获取,用完 close() 归还- 生产环境永远不要直接
DriverManager.getConnection()。 - Spring Boot 自动配置 HikariCP(
spring.datasource.hikari.*)。 - 连接池的关闭与数据库连接断开由池管理,
conn.close()归还而非真正关闭。
小结
- JDBC 核心:
DriverManager(或DataSource)→Connection→PreparedStatement→ResultSet→ 关闭。 - 永远使用
PreparedStatement而不是Statement来防止 SQL 注入。 - 三个资源(
Connection、Statement、ResultSet)都必须用try-with-resources自动关闭。 - 生产环境使用连接池(HikariCP)复用连接,避免频繁创建。
- 易错:
ResultSet的列索引从 1 开始(不是 0);用完ResultSet再关闭Statement——如果先关闭Statement,ResultSet会失效。try-with-resources按声明逆序关闭,所以把Connection放第一个、ResultSet放最后一个即可。 - 思考任务:使用 H2 内存数据库创建一个
products表,包含id、name、price、stock四列,用PreparedStatement完成插入两条数据和查询全部数据的完整代码。
上一节:NIO.2 与文件操作
下一节:多线程基础
