1. 概述
本文将带你了解 JDBC(Java Database Connectivity),这是用于连接数据库并执行查询的 API。JDBC 的优势在于其通用性——只要提供合适的驱动程序,它就能与任何数据库协同工作。
2. JDBC 驱动
JDBC 驱动是 JDBC API 的具体实现,用于连接特定类型的数据库。驱动主要分为四类:
- Type 1:映射到其他数据访问 API(如 JDBC-ODBC 桥接驱动)
- Type 2:使用目标数据库的客户端库(原生 API 驱动)
- Type 3:通过中间件转换 JDBC 调用(网络协议驱动)
- Type 4:直接将 JDBC 调用转换为数据库协议(瘦驱动)
⚠️ 最常用的是 Type 4 驱动,优势在于:
- ✅ 跨平台性:不依赖本地库
- ✅ 性能更优:直接连接数据库服务器
- ❌ 数据库特定:每种数据库需要专用驱动
3. 连接数据库
连接数据库只需两步:初始化驱动和打开连接。
3.1 注册驱动
以 Type 4 驱动为例(MySQL 数据库)。首先添加依赖:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.32</version>
</dependency>
通过 Class.forName()
动态加载驱动类:
Class.forName("com.mysql.cj.jdbc.Driver");
踩坑提示:在 JDBC 4.0+ 版本中,classpath 中的驱动会自动加载,无需手动注册!现代开发环境可省略此步骤。
3.2 创建连接
使用 DriverManager.getConnection()
打开连接:
try (Connection con = DriverManager
.getConnection("jdbc:mysql://localhost:3306/myDb", "user1", "pass")) {
// 使用连接执行操作
}
资源管理:
Connection
实现了AutoCloseable
,务必放在 try-with-resources 块中!
不同数据库的 URL 格式示例:
- MySQL:
jdbc:mysql://localhost:3306/myDb?user=user1&password=pass
- PostgreSQL:
jdbc:postgresql://localhost/myDb
- HSQLDB:
jdbc:hsqldb:mem:myDb
创建数据库和用户的 SQL:
CREATE DATABASE myDb;
CREATE USER 'user1' IDENTIFIED BY 'pass';
GRANT ALL on myDb.* TO 'user1';
4. 执行 SQL 语句
通过 Connection
创建 Statement
、PreparedStatement
或 CallableStatement
执行 SQL。
4.1 Statement
Statement
提供基础 SQL 执行功能。创建方式:
try (Statement stmt = con.createStatement()) {
// 执行 SQL 操作
}
执行方法:
executeQuery()
:用于 SELECT 查询executeUpdate()
:用于更新数据或结构execute()
:通用方法(返回结果未知时)
创建表示例:
String tableSql = "CREATE TABLE IF NOT EXISTS employees"
+ "(emp_id int PRIMARY KEY AUTO_INCREMENT, name varchar(30),"
+ "position varchar(30), salary double)";
stmt.execute(tableSql);
结果判断:
stmt.getUpdateCount()
返回受影响行数(0 表示结构更新,-1 表示查询)- 查询结果通过
stmt.getResultSet()
获取
插入数据示例:
String insertSql = "INSERT INTO employees(name, position, salary)"
+ " VALUES('john', 'developer', 2000)";
stmt.executeUpdate(insertSql);
查询数据示例:
String selectSql = "SELECT * FROM employees";
try (ResultSet resultSet = stmt.executeQuery(selectSql)) {
// 处理结果集
}
资源管理:
ResultSet
必须关闭!推荐使用 try-with-resources。
4.2 PreparedStatement
预编译 SQL 语句,用 ?
表示参数。创建方式:
String updatePositionSql = "UPDATE employees SET position=? WHERE emp_id=?";
try (PreparedStatement pstmt = con.prepareStatement(updatePositionSql)) {
// 设置参数并执行
}
参数设置示例:
pstmt.setString(1, "lead developer"); // 第一个参数
pstmt.setInt(2, 1); // 第二个参数
int rowsAffected = pstmt.executeUpdate();
4.3 CallableStatement
用于调用存储过程。创建方式:
String preparedSql = "{call insertEmployee(?,?,?,?)}";
try (CallableStatement cstmt = con.prepareCall(preparedSql)) {
// 设置参数并执行
}
参数处理:
- 输入参数:
setX()
方法(同PreparedStatement
) - 输出参数:需先注册类型
// 注册输出参数
cstmt.registerOutParameter(1, Types.INTEGER);
// 设置输入参数
cstmt.setString(2, "ana");
cstmt.setString(3, "tester");
cstmt.setDouble(4, 2000);
// 执行并获取结果
cstmt.execute();
int new_id = cstmt.getInt(1);
存储过程示例(MySQL):
delimiter // CREATE PROCEDURE insertEmployee(OUT emp_id int, IN emp_name varchar(30), IN position varchar(30), IN salary double) BEGIN INSERT INTO employees(name, position,salary) VALUES (emp_name,position,salary); SET emp_id = LAST_INSERT_ID(); END // delimiter ;
权限问题:执行存储过程需授权:
GRANT ALL ON mysql.proc TO 'user1';
或连接时添加参数:
con = DriverManager.getConnection( "jdbc:mysql://localhost:3306/myDb?noAccessToProcedureBodies=true", "user1", "pass");
5. 解析查询结果
ResultSet
表示查询结果,具有类似表格的行列结构。
5.1 ResultSet 接口
先定义数据模型:
public class Employee {
private int id;
private String name;
private String position;
private double salary;
// 标准构造器、getter/setter
}
遍历结果集示例:
String selectSql = "SELECT * FROM employees";
try (ResultSet resultSet = stmt.executeQuery(selectSql)) {
List<Employee> employees = new ArrayList<>();
while (resultSet.next()) {
Employee emp = new Employee();
emp.setId(resultSet.getInt("emp_id"));
emp.setName(resultSet.getString("name"));
emp.setPosition(resultSet.getString("position"));
emp.setSalary(resultSet.getDouble("salary"));
employees.add(emp);
}
}
取值技巧:
- 使用列名(
"emp_id"
)而非列序号,避免查询结构调整导致错误- 通过
getX()
方法获取数据(X 为数据类型)
5.2 可更新的 ResultSet
默认 ResultSet
只能前向遍历且不可修改。创建可更新版本:
stmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE,
ResultSet.CONCUR_UPDATABLE
);
导航方法:
first()
/last()
:移动到首行/末行next()
/previous()
:前进/后退absolute(int row)
:跳转到指定行relative(int nrRows)
:相对移动
更新方法:
updateX()
:更新当前行数据(仅内存)updateRow()
:将修改持久化到数据库insertRow()
:插入新行deleteRow()
:删除当前行
插入数据示例:
try (Statement updatableStmt = con.createStatement(
ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE)) {
try (ResultSet updatableResultSet = updatableStmt.executeQuery(selectSql)) {
updatableResultSet.moveToInsertRow();
updatableResultSet.updateString("name", "mark");
updatableResultSet.updateString("position", "analyst");
updatableResultSet.updateDouble("salary", 2000);
updatableResultSet.insertRow();
}
}
6. 解析元数据
JDBC 提供两种元数据接口:数据库元数据和结果集元数据。
6.1 DatabaseMetadata
获取数据库全局信息(表、存储过程等):
DatabaseMetaData dbmd = con.getMetaData();
ResultSet tablesResultSet = dbmd.getTables(null, null, "%", null);
while (tablesResultSet.next()) {
LOG.info(tablesResultSet.getString("TABLE_NAME"));
}
6.2 ResultSetMetadata
获取结果集结构信息(列名、数量等):
ResultSetMetaData rsmd = rs.getMetaData();
int nrColumns = rsmd.getColumnCount();
IntStream.range(1, nrColumns).forEach(i -> {
try {
LOG.info(rsmd.getColumnName(i));
} catch (SQLException e) {
e.printStackTrace();
}
});
7. 处理事务
默认情况下,每条 SQL 语句自动提交。可通过编程控制事务:
String updatePositionSql = "UPDATE employees SET position=? WHERE emp_id=?";
PreparedStatement pstmt = con.prepareStatement(updatePositionSql);
pstmt.setString(1, "lead developer");
pstmt.setInt(2, 1);
String updateSalarySql = "UPDATE employees SET salary=? WHERE emp_id=?";
PreparedStatement pstmt2 = con.prepareStatement(updateSalarySql);
pstmt.setDouble(1, 3000);
pstmt.setInt(2, 1);
boolean autoCommit = con.getAutoCommit();
try {
con.setAutoCommit(false); // 关闭自动提交
pstmt.executeUpdate();
pstmt2.executeUpdate();
con.commit(); // 手动提交
} catch (SQLException exc) {
con.rollback(); // 异常回滚
} finally {
con.setAutoCommit(autoCommit); // 恢复原设置
}
事务场景:当多个操作需保持原子性时(如转账操作),必须使用事务!
8. 关闭资源
使用完毕后必须关闭连接释放资源:
con.close();
最佳实践:所有 JDBC 资源(
Connection
/Statement
/ResultSet
)都应放在 try-with-resources 块中,避免资源泄漏!
9. 总结
本文系统介绍了 JDBC 的核心功能:
- 驱动类型与选择
- 数据库连接与配置
- SQL 语句的三种执行方式
- 结果集处理与元数据查询
- 事务控制机制
- 资源管理最佳实践
简单粗暴总结:JDBC 是 Java 数据库操作的基石,掌握它才能玩转 MyBatis、JPA 等高级框架。记住三点:用 try-with-resources 管理资源、优先使用
PreparedStatement
防注入、复杂操作用事务保一致性!
完整示例代码见 GitHub 仓库。