JDBC(Java Database Connectivity)

🔍 一、为什么必须学 JDBC?------不是"过时",而是"根基"

在 Spring Boot 时代,我们用 `@Mapper`、`JdbcTemplate`、`MyBatis-Plus` 三行代码就完成增删改查。那为什么还要啃 JDBC?

> 💡 答案很朴素:它是所有 Java 数据库框架的底层基石!

> - MyBatis 的 `SqlSession` 是对 `Connection + PreparedStatement` 的封装;

> - Spring 的 `JdbcTemplate` 是对 `PreparedStatement` 和 `ResultSet` 的模板化抽象;

> - 连接池(Druid/Hikari)、事务管理器(`DataSourceTransactionManager`)全基于 JDBC 接口构建。

📌 一句话总结:

> ❗️不学 JDBC,永远只能调 API;学透 JDBC,才能真正看懂框架、写出高性能 SQL、解决事务死锁、排查连接泄漏!

📘 二、JDBC 是什么?------一套标准,不是某个工具!

✅ 官方定义

JDBC(Java Database Connectivity)是由 Sun 公司制定的一套访问关系型数据库的标准接口(API 规范),属于 Java SE 的一部分。

角色 说明
JDBC(接口) java.sql.* 包下的 Connection, Statement, ResultSet 等------你写的代码依赖它
JDBC Driver(驱动实现) MySQL 的 com.mysql.cj.jdbc.Driver、Oracle 的 oracle.jdbc.driver.OracleDriver ------厂商提供,你引入 jar 包

> ⚠️ 类比理解:

> JDBC = USB 接口标准(Type-C / USB-A)

> 驱动 = 手机厂商(华为/小米)为自家手机生产的 Type-C 数据线

> ❌ 没有驱动 → 插上 USB 线也传不了数据!

🧩 三、JDBC 核心架构图解(4 大核心接口)

复制代码
graph LR
A[DriverManager] -->|获取| B[Connection]
B -->|创建| C[Statement]
B -->|预编译| D[PreparedStatement]
B -->|调用存储过程| E[CallableStatement]
C & D & E -->|执行| F[ResultSet]
接口 职责 关键方法 使用场景
DriverManager 驱动注册中心 & 连接工厂 getConnection(url, user, pwd) 加载驱动、获取连接(只用一次
Connection 物理数据库会话 createStatement(), prepareStatement(), setAutoCommit(false) 控制事务、创建执行器
Statement 普通 SQL 执行器 executeQuery(), executeUpdate() 简单、无参数、一次性 SQL(不推荐生产使用
PreparedStatement 预编译 SQL 执行器 setString(1,"xxx"), executeQuery() ✅ 防 SQL 注入、✅ 性能高(缓存执行计划)、✅ 支持批量操作
ResultSet 查询结果游标 next(), getString("name"), getInt(1) 遍历、取值、关闭(必须 close!

> ✅ 最佳实践口诀:

> "先连库,再预编,设参查,游标遍,最后关!"

> (Connection → PreparedStatement → setXxx → executeQuery → next + getXxx → close)

🛠 四、JDBC 操作六步法(含完整代码)

> ✅ 务必按顺序执行,资源关闭顺序必须「反向」!

> `ResultSet` → `Statement` → `Connection`(否则可能报 `Closed Connection` 异常)

✅ Step 1:加载驱动(MySQL 8+)

复制代码
// ✅ MySQL 8.x 推荐写法(自动注册,可省略)
Class.forName("com.mysql.cj.jdbc.Driver");

// ⚠️ MySQL 5.x 写法(已过时,仅兼容)
// Class.forName("com.mysql.jdbc.Driver");

✅ Step 2:获取连接(URL 参数很重要!)

复制代码
String url = "jdbc:mysql://localhost:3306/jdbc_db?" +
        "useSSL=false&" +           // 关闭 SSL(本地开发)
        "serverTimezone=Asia/Shanghai&" + // 时区(避免时间错乱)
        "characterEncoding=utf8";   // 字符集(防中文乱码)

Connection conn = DriverManager.getConnection(url, "root", "1111");

✅ Step 3:创建 PreparedStatement(强烈推荐!)

复制代码
String sql = "INSERT INTO student (id, name, age, sex, address) VALUES (?, ?, ?, ?, ?)";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, 101);
pstmt.setString(2, "张三");
pstmt.setInt(3, 25);
pstmt.setString(4, "男");
pstmt.setString(5, "北京市朝阳区");

✅ Step 4:执行操作

操作类型 方法 返回值 示例
查询(SELECT) pstmt.executeQuery() ResultSet rs = pstmt.executeQuery();
增/删/改(DML) pstmt.executeUpdate() int(影响行数) int rows = pstmt.executeUpdate();

✅ Step 5:处理 ResultSet(注意游标初始位置!)

复制代码
ResultSet rs = pstmt.executeQuery();
// ⚠️ 游标初始在第 0 行(第一行前),必须 next() 才能取值!
while (rs.next()) {
    int id = rs.getInt("id");          // ✅ 推荐:用列名(更可读)
    String name = rs.getString("name");
    // 或用索引(从 1 开始!)→ rs.getString(2)
}

✅ Step 6:关闭资源(必须 try-finally 或 try-with-resources!)

复制代码
finally {
    if (rs != null) rs.close();
    if (pstmt != null) pstmt.close();
    if (conn != null) conn.close(); // 归还连接池(若使用)
}

> 💡 进阶技巧:使用 try-with-resources(JDK 7+)自动关闭

复制代码
try (Connection conn = DBUtils.getConn();
     PreparedStatement pstmt = conn.prepareStatement(sql);
     ResultSet rs = pstmt.executeQuery()) {
    
    while (rs.next()) { /* 处理结果 */ }
} catch (SQLException e) {
    e.printStackTrace();
} // 自动 close()!

🚫 五、致命风险:SQL 注入攻击与防御(必考!)

❌ 危险写法(字符串拼接)→ 账户密码全裸奔!

复制代码
// 用户输入:username = "admin' -- ",password = "123"
String sql = "SELECT * FROM sys_user WHERE username='" + username + "' AND password='" + password + "'";
// 实际执行:
// SELECT * FROM sys_user WHERE username='admin' -- ' AND password='123'
// → 注释掉密码校验!直接登录成功!

✅ 正确方案:PreparedStatement 占位符(唯一可靠方案!)

复制代码
String sql = "SELECT * FROM sys_user WHERE username = ? AND password = ?";
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.setString(1, username); // ✅ 参数化,数据库当「值」而非「SQL 片段」
pstmt.setString(2, password);
ResultSet rs = pstmt.executeQuery();

> ✅ 原理:PreparedStatement 将 SQL 与参数分离,数据库先编译 SQL 模板,再安全绑定参数值,彻底杜绝注入!

💰 六、事务控制:转账案例(ACID 四大特性落地)

📌 需求

A 向 B 转账 1000 元,要求:

✅ A 扣款成功 & B 加款成功 → 全部提交

❌ A 扣款成功 & B 加款失败 → 全部回滚(钱不能丢!)

✅ 核心代码(手动事务)

复制代码
Connection conn = null;
try {
    conn = DBUtils.getConn();
    conn.setAutoCommit(false); // 🔑 关闭自动提交,开启事务

    String sql1 = "UPDATE account SET amount = amount - 1000 WHERE aid = 1";
    String sql2 = "UPDATE account SET amount = amount + 1000 WHERE aid = 2";

    PreparedStatement p1 = conn.prepareStatement(sql1);
    PreparedStatement p2 = conn.prepareStatement(sql2);

    p1.executeUpdate(); // 扣款
    // int i = 1 / 0; // ✅ 模拟异常:此处抛异常则触发回滚
    p2.executeUpdate(); // 加款

    conn.commit(); // ✅ 全部成功,提交事务
    System.out.println("转账成功!");
} catch (Exception e) {
    e.printStackTrace();
    try {
        if (conn != null) conn.rollback(); // 🔑 回滚!
        System.out.println("转账失败,已回滚!");
    } catch (SQLException ex) {
        ex.printStackTrace();
    }
} finally {
    DBUtils.close(conn);
}

✅ ACID 特性对应实现:

特性 JDBC 实现方式
原子性(Atomicity) commit() / rollback() 控制整体成败
一致性(Consistency) 事务内约束(如外键、唯一键)自动校验
隔离性(Isolation) conn.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE)
持久性(Durability) 提交后数据写入磁盘(数据库保证)

🐬 七、性能瓶颈:为什么需要数据库连接池?

❌ 传统模式痛点

复制代码
每次请求 → 创建 Connection(TCP 握手 + 认证)→ 执行 SQL → close()(四次挥手)  
→ 高并发下:连接创建/销毁耗时 > SQL 执行耗时 → 系统雪崩!

✅ 连接池原理(复用连接)

复制代码
graph LR
A[应用] -->|借| B[连接池]
B --> C[空闲连接1]
B --> D[空闲连接2]
B --> E[空闲连接3]
C & D & E -->|归还| B
方案 特点 现状
c3p0 / DBCP 老牌开源池,配置复杂 ❌ 已基本淘汰
Druid(阿里) 监控强大(Web 控制台)、中文文档好、Spring Boot 默认集成 国内主流选择
HikariCP(日本) 性能最强(号称"光速")、Spring Boot 2.0+ 官方默认 国际主流 & 推荐首选

🧱 八、企业级封装:BaseDAO ------ 手写你的第一个"轻量 ORM"

> 💡 目标:用一个 `BaseDao<T>`,支持任意实体类的通用增删改查 + 分页,零 SQL 硬编码!

✅ Step 1:定义通用分页对象 `PageInfo<T>`

复制代码
public class PageInfo<T> {
    private List<T> data;   // 当前页数据
    private Long total;     // 总条数
    private Integer pageNum; // 当前页码
    private Integer pageSize;// 每页条数

    // getter/setter...
}

✅ Step 2:BaseDao 核心封装(反射 + 泛型 + PreparedStatement)

复制代码
public class BaseDao {

    // ✅ 通用查询列表(支持 ? 占位符)
    public <T> List<T> selectList(String sql, Class<T> cls, Object... params) {
        List<T> list = new ArrayList<>();
        try (Connection conn = DBUtils.getConn();
             PreparedStatement ps = conn.prepareStatement(sql)) {

            // 设置参数
            for (int i = 0; i < params.length; i++) {
                ps.setObject(i + 1, params[i]);
            }

            try (ResultSet rs = ps.executeQuery()) {
                ResultSetMetaData meta = rs.getMetaData();
                int columnCount = meta.getColumnCount();

                while (rs.next()) {
                    T obj = cls.getDeclaredConstructor().newInstance();
                    for (int i = 1; i <= columnCount; i++) {
                        String colName = meta.getColumnLabel(i); // 获取列别名(如 createTime)
                        Object value = rs.getObject(colName);
                        Field field = cls.getDeclaredField(colName);
                        field.setAccessible(true);
                        field.set(obj, value);
                    }
                    list.add(obj);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return list;
    }

    // ✅ 通用分页查询(自动计算 count + limit)
    public <T> PageInfo<T> selectPage(String sql, Class<T> cls, int pageNum, int pageSize) {
        PageInfo<T> page = new PageInfo<>();

        // 1. 查询当前页数据
        String pageSql = sql + " LIMIT " + (pageNum - 1) * pageSize + "," + pageSize;
        List<T> dataList = selectList(pageSql, cls);

        // 2. 查询总条数(子查询包装)
        String countSql = "SELECT COUNT(*) FROM (" + sql + ") AS tmp";
        long total = 0;
        try (Connection conn = DBUtils.getConn();
             PreparedStatement ps = conn.prepareStatement(countSql);
             ResultSet rs = ps.executeQuery()) {
            if (rs.next()) total = rs.getLong(1);
        } catch (Exception e) {
            e.printStackTrace();
        }

        page.setData(dataList);
        page.setTotal(total);
        page.setPageNum(pageNum);
        page.setPageSize(pageSize);
        return page;
    }

    // ✅ 通用更新(INSERT/UPDATE/DELETE)
    public boolean update(String sql, Object... params) {
        try (Connection conn = DBUtils.getConn();
             PreparedStatement ps = conn.prepareStatement(sql)) {
            for (int i = 0; i < params.length; i++) {
                ps.setObject(i + 1, params[i]);
            }
            return ps.executeUpdate() > 0;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }
}

✅ Step 3:业务 DAO 继承使用(极简!)

复制代码
public class UserDao extends BaseDao {
    // ✅ 不用写 SQL!复用父类方法
    public User findByUsernameAndPassword(String username, String password) {
        String sql = "SELECT * FROM sys_user WHERE username = ? AND password = ?";
        return selectOne(sql, User.class, username, password);
    }

    public void updateUserState(Long id, Integer deleted) {
        String sql = "UPDATE sys_user SET deleted = ?, deleted_time = NOW() WHERE id = ?";
        update(sql, deleted, id);
    }

    public PageInfo<User> findPage(int pageNum, int pageSize) {
        String sql = "SELECT * FROM sys_user WHERE deleted = 0";
        return selectPage(sql, User.class, pageNum, pageSize);
    }
}

> ✅ 优势总结:

> - ✅ 零 SQL 硬编码(SQL 写在方法里,非 XML)

> - ✅ 强类型安全(泛型 `<T>` 编译期检查)

> - ✅ 自动映射(字段名 ↔ 列名一致即可)

> - ✅ 开箱即用分页(count + limit 一键搞定)

> - ✅ 为后续接入 MyBatis 奠定认知基础

🌈 九、总结:JDBC 学习路线图(附学习建议)

阶段 掌握目标 推荐练习
入门 能手写 6 步完成 CRUD、理解 PreparedStatement 防注入 学生管理系统增删改查
进阶 掌握事务控制、连接池配置(Druid/Hikari)、批处理 addBatch() 银行转账 + 并发压力测试
高手 封装 BaseDAO、理解 JDBC 四大接口
相关推荐
陌上丨2 小时前
如何保证Redis缓存和数据库数据的一致性?
数据库·redis·缓存
qq_12498707532 小时前
基于springboot的尿毒症健康管理系统的设计与实现(源码+论文+部署+安装)
java·spring boot·spring·毕业设计·计算机毕业设计
l1t2 小时前
一个用postgresql的自定义函数求解数独的程序
数据库·postgresql·数独
IvorySQL2 小时前
改变工作方式的 PostgreSQL 实用模式
数据库·postgresql
Anarkh_Lee2 小时前
在VSCode中使用MCP实现智能问数
数据库·ide·vscode·ai·编辑器·ai编程·数据库开发
黎子越2 小时前
python相关练习
java·前端·python
晓13132 小时前
第八章:Redis底层原理深度详细解析
数据库·redis·缓存
电商API&Tina2 小时前
电商数据采集 API 接口 全维度解析(技术 + 商业 + 合规)
java·大数据·开发语言·数据库·人工智能·json
liwulin05062 小时前
【JSON】使用com.fasterxml.jackson解析json字符串
java·数据库·json