Java JDBC 从入门到进阶:一篇搞定数据库操作
前言:JDBC操作速览
在深入JDBC原理之前,我们先通过一个完整的代码示例,快速建立对JDBC操作的直观认识。
数据库基础SQL回顾
sql
-- 添加数据
INSERT INTO t_user(id, name) VALUES(?, ?);
-- 删除数据
DELETE FROM t_user WHERE id = ?;
-- 修改数据
UPDATE t_user SET name = ? WHERE id = ?;
-- 查询所有数据
SELECT * FROM t_user;
-- 查询指定数据
SELECT * FROM t_user WHERE id = ?;
JDBC操作七步法
java
// 1. 加载驱动
Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 获取连接
String url = "jdbc:mysql://localhost:3306/testjdbc?useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8";
String username = "root";
String password = "root";
Connection connection = DriverManager.getConnection(url, username, password);
// 3. 准备SQL语句
String sql = "SELECT * FROM t_user WHERE id = ?";
// 4. 获取PreparedStatement对象
PreparedStatement ps = connection.prepareStatement(sql);
// 5. 设置参数
ps.setInt(1, 10);
// 6. 执行查询,获取结果集
ResultSet rs = ps.executeQuery();
// 7. 处理结果集
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
System.out.println(id + "-----" + name);
}
// 8. 释放资源(按创建顺序反向关闭)
rs.close();
ps.close();
connection.close();
1、什么是JDBC
1.1、概念解析
JDBC (Java DataBase Connectivity,Java数据库连接)是Java提供的一套用于执行SQL语句的API 。它最大的价值在于:为Java开发者提供了一套统一的、与具体数据库无关的编程接口。
简单理解:JDBC定义了"如何与数据库对话"的标准,无论底层是MySQL、Oracle还是PostgreSQL,开发者都使用同一套Java代码进行交互。
1.2、数据库驱动与JDBC架构
JDBC本身是一组接口 ,真正完成数据库操作的是各个数据库厂商提供的驱动实现。这种设计遵循了面向接口编程的思想,实现了Java程序与数据库的解耦。
JDBC核心组件
| 组件 | 作用 |
|---|---|
| DriverManager | 管理数据库驱动列表,负责建立数据库连接 |
| Connection | 代表与特定数据库的连接会话 |
| Statement / PreparedStatement | 用于执行SQL语句并返回结果 |
| ResultSet | 代表查询结果集,提供数据访问方法 |
JDBC架构图
数据库
JDBC驱动层
JDBC API
Java应用程序
Java Application
DriverManager
Connection
Statement
ResultSet
MySQL驱动
Oracle驱动
PostgreSQL驱动
MySQL
Oracle
PostgreSQL
2、JDBC核心API详解
2.1、Properties配置文件管理
将数据库连接信息写在代码中不利于维护和部署。最佳实践是使用properties文件进行配置管理。
配置文件 db.properties
properties
driver_name = com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/jdbc?serverTimezone=UTC
username = root
password = root
Java读取配置代码
java
// 使用类加载器读取配置文件
Properties props = new Properties();
InputStream is = getClass().getClassLoader().getResourceAsStream("db.properties");
props.load(is);
// 获取配置参数
String driver = props.getProperty("driver_name");
String url = props.getProperty("url");
String username = props.getProperty("username");
String password = props.getProperty("password");
// 加载驱动并建立连接
Class.forName(driver);
Connection conn = DriverManager.getConnection(url, username, password);
2.2、Statement与PreparedStatement对比
| 对比项 | Statement | PreparedStatement |
|---|---|---|
| 继承关系 | 基础接口 | 继承自Statement |
| SQL执行 | 每次执行时编译SQL | 预编译SQL,可重复使用 |
| 参数设置 | 字符串拼接 | 使用占位符?,通过setXxx方法设置 |
| SQL注入 | 存在风险 | 有效防止SQL注入 |
| 性能 | 较低 | 较高,尤其适合批量操作 |
| 适用场景 | 动态SQL结构 | 参数化SQL,推荐使用 |
⚠️ 重要提示 :在实际开发中,应始终优先使用PreparedStatement,它不仅是性能考虑,更是安全底线。
2.3、核心方法详解
java
// 执行查询(SELECT),返回ResultSet对象
ResultSet executeQuery() throws SQLException;
// 执行任意SQL,返回true表示有结果集(查询),false表示无结果集(增删改)
boolean execute() throws SQLException;
// 执行增删改操作,返回受影响的行数
int executeUpdate() throws SQLException;
2.4、资源管理最佳实践
数据库连接、PreparedStatement、ResultSet都持有系统资源,必须在用完后显式关闭。不关闭资源会导致连接池耗尽,程序无法获取新连接。
java
// 正确的资源关闭方式(使用try-with-resources,Java 7+)
String sql = "SELECT * FROM t_user WHERE id = ?";
try (Connection conn = DriverManager.getConnection(url, username, password);
PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, 10);
try (ResultSet rs = ps.executeQuery()) {
while (rs.next()) {
// 处理结果
}
}
} catch (SQLException e) {
e.printStackTrace();
}
// 资源自动关闭,无需手动处理
2.5、元数据(Metadata)
元数据是描述数据的数据。JDBC提供了两种元数据:
- DatabaseMetaData:描述数据库整体信息,如版本、支持的SQL语法等
- ResultSetMetaData:描述结果集的结构,如列名、列类型、列数量等
java
// 使用ResultSetMetaData动态获取结果集信息
ResultSet rs = ps.executeQuery();
ResultSetMetaData metaData = rs.getMetaData();
int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
String columnName = metaData.getColumnName(i);
String columnType = metaData.getColumnTypeName(i);
System.out.println(columnName + " : " + columnType);
}
元数据配合反射使用,可以实现自动将查询结果映射为Java对象,这是许多ORM框架(如MyBatis、Hibernate)的核心原理。
3、连接池技术
3.1、为什么需要连接池
传统模式下,每次数据库操作都需要经历"创建连接 → 使用 → 关闭连接"的过程。而创建数据库连接是非常昂贵的操作,涉及网络通信、身份认证、资源分配等开销。
连接池的核心思想是:复用连接。在应用启动时初始化一批连接放入池中,使用时从池中获取,使用完毕后归还而非关闭,从而显著提升性能。
3.2、连接池工作原理
数据库 连接池 应用程序 数据库 连接池 应用程序 初始化创建N个连接 alt [池中有空闲连接] [无空闲连接且未达上限- ] [连接数已达上限] 连接重置并放回池中 请求连接 返回空闲连接 创建新连接 返回新连接 返回新连接 等待或抛出异常 使用完毕,归还连接
3.3、主流连接池对比
| 连接池 | 特点 | 适用场景 |
|---|---|---|
| HikariCP | 性能极佳,Spring Boot 2.x默认 | 高性能要求的应用 |
| Druid | 功能丰富,提供监控、SQL防火墙等 | 需要监控和扩展功能的场景 |
| C3P0 | 老牌连接池,配置灵活 | 传统项目 |
| Tomcat JDBC Pool | Tomcat内置,与容器集成好 | 运行在Tomcat下的应用 |
Druid配置示例
java
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("root");
dataSource.setInitialSize(5); // 初始连接数
dataSource.setMaxActive(20); // 最大连接数
dataSource.setMinIdle(5); // 最小空闲连接数
dataSource.setMaxWait(60000); // 获取连接超时时间(毫秒)
// 使用连接池获取连接
Connection conn = dataSource.getConnection();
// 使用完毕后调用close(),实际是归还到池中
conn.close();
4、ThreadLocal与连接管理
4.1、ThreadLocal原理
ThreadLocal 是Java提供的线程局部变量。每个线程都拥有自己独立的变量副本,线程之间互不干扰。
Thread-3
Thread-2
Thread-1
ThreadLocal Map
Connection对象
ThreadLocal Map
Connection对象
ThreadLocal Map
Connection对象
ThreadLocal实例
4.2、结合ThreadLocal管理数据库连接
在多线程环境下,ThreadLocal可以保证同一个线程中的多个数据库操作使用同一个连接,这对于实现事务管理至关重要。
java
public class ConnectionManager {
private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
private static DataSource dataSource; // 连接池
static {
// 初始化数据源
DruidDataSource ds = new DruidDataSource();
ds.setUrl("jdbc:mysql://localhost:3306/test");
ds.setUsername("root");
ds.setPassword("root");
dataSource = ds;
}
// 获取当前线程的Connection
public static Connection getConnection() {
Connection conn = threadLocal.get();
try {
if (conn == null || conn.isClosed()) {
conn = dataSource.getConnection();
threadLocal.set(conn);
}
} catch (SQLException e) {
e.printStackTrace();
}
return conn;
}
// 关闭当前线程的连接
public static void closeConnection() {
Connection conn = threadLocal.get();
try {
if (conn != null && !conn.isClosed()) {
conn.close();
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
threadLocal.remove(); // 重要:防止内存泄漏
}
}
}
5、Apache DBUtils与QueryRunner
5.1、DBUtils简介
Apache Commons DBUtils 是对JDBC的轻量级封装,它简化了资源管理和结果集处理,让JDBC代码更加简洁。
核心组件:
- QueryRunner:执行SQL语句的核心类
- ResultSetHandler:结果集处理器接口,定义了如何将ResultSet转换为Java对象
- DbUtils:工具类,提供关闭资源等静态方法
5.2、QueryRunner使用示例
java
// 创建QueryRunner(需要传入数据源)
QueryRunner runner = new QueryRunner(dataSource);
// 1. 查询单个对象
String sql1 = "SELECT * FROM t_user WHERE id = ?";
User user = runner.query(sql1, new BeanHandler<>(User.class), 1);
// 2. 查询对象列表
String sql2 = "SELECT * FROM t_user";
List<User> userList = runner.query(sql2, new BeanListHandler<>(User.class));
// 3. 查询单行单列
String sql3 = "SELECT COUNT(*) FROM t_user";
Long count = runner.query(sql3, new ScalarHandler<>());
// 4. 插入操作(返回自增主键)
String sql4 = "INSERT INTO t_user(name, age) VALUES(?, ?)";
GeneratedKeyHolder keyHolder = new GeneratedKeyHolder();
runner.insert(sql4, new BeanHandler<>(User.class), "张三", 20);
Long id = keyHolder.getKey().longValue();
// 5. 更新操作
String sql5 = "UPDATE t_user SET name = ? WHERE id = ?";
int affectedRows = runner.update(sql5, "李四", 1);
5.3、自定义ResultSetHandler
当默认的处理器不满足需求时,可以实现自定义处理器:
java
// 自定义Map处理器
ResultSetHandler<Map<String, Object>> mapHandler = new ResultSetHandler<Map<String, Object>>() {
@Override
public Map<String, Object> handle(ResultSet rs) throws SQLException {
Map<String, Object> map = new HashMap<>();
if (rs.next()) {
ResultSetMetaData meta = rs.getMetaData();
for (int i = 1; i <= meta.getColumnCount(); i++) {
map.put(meta.getColumnName(i), rs.getObject(i));
}
}
return map;
}
};
Map<String, Object> result = runner.query(sql, mapHandler);
6、最佳实践与总结
6.1、JDBC开发最佳实践
JDBC最佳实践
连接管理
使用连接池管理连接
合理设置初始连接和最大连接数
确保连接及时释放
安全防护
必须使用PreparedStatement防止SQL注入
敏感信息不硬编码
使用配置文件管理数据库凭证
性能优化
合理使用批处理Batch操作
控制单次查询返回数据量
使用连接池减少连接创建开销
资源释放
try-with-resources自动释放
释放顺序与创建顺序相反
避免在循环中创建连接
异常处理
记录完整异常堆栈
事务中的异常要及时回滚
区分业务异常和系统异常
6.2、核心知识点总结
| 知识点 | 核心要点 |
|---|---|
| JDBC本质 | 一套Java访问数据库的标准接口 |
| 核心接口 | DriverManager, Connection, PreparedStatement, ResultSet |
| PreparedStatement | 预编译,防SQL注入,性能优于Statement |
| 连接池 | 复用连接,提升性能,推荐使用HikariCP或Druid |
| ThreadLocal | 保证同一线程共享连接,是实现事务的关键 |
| DBUtils | 轻量级封装,简化JDBC代码,推荐使用QueryRunner |
6.3、进阶学习路径
- 掌握基础:熟练使用JDBC完成CRUD操作
- 理解原理:研究连接池实现、事务隔离级别、数据库锁机制
- 封装工具类:基于ThreadLocal和DBUtils封装自己的持久层工具
- 学习ORM框架:理解MyBatis、Hibernate等框架如何封装JDBC
- 关注性能:学习SQL优化、索引原理、批量处理技巧