Java JDBC 从入门到进阶

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、进阶学习路径

  1. 掌握基础:熟练使用JDBC完成CRUD操作
  2. 理解原理:研究连接池实现、事务隔离级别、数据库锁机制
  3. 封装工具类:基于ThreadLocal和DBUtils封装自己的持久层工具
  4. 学习ORM框架:理解MyBatis、Hibernate等框架如何封装JDBC
  5. 关注性能:学习SQL优化、索引原理、批量处理技巧
相关推荐
Joker`s smile2 小时前
Spring Cloud Alibaba 基础入门实践
java·spring boot·后端·spring cloud
nbsaas-boot2 小时前
AI编程的现实困境与未来路径:从“可用”到“可靠”的跃迁
java·运维·开发语言·架构
东离与糖宝2 小时前
Java 26 Vector API 第十一轮孵化:AI 推理性能提升 80% 实战
java·人工智能
廖圣平2 小时前
从零开始,福袋直播间脚本研究【八】《策略模式》
开发语言·python·bash·策略模式
灰子学技术2 小时前
C++ 代码质量检测工具集合技术文档
开发语言·c++
散峰而望2 小时前
【数据结构】单调栈与单调队列深度解析:从模板到实战,一网打尽
开发语言·数据结构·c++·后端·算法·github·推荐算法
qwehjk20082 小时前
内存泄漏自动检测系统
开发语言·c++·算法
华科大胡子2 小时前
91行代码创意赛
开发语言
星如雨グッ!(๑•̀ㅂ•́)و✧2 小时前
WebClient请求样例
java