☕ 第十一章 JDBC
📄 一句话概括

每次都 getConnection 新建连接太浪费(TCP 握手 + 账号验证,用完就销毁)——连接池 = 连接的租车行:程序启动时预建好一池子连接,用的时候取、用完 close 还回去循环使用。JDK 定了标准接口 DataSource,最常用实现是阿里的 Druid。

直连的账,算一下就心疼

11.3 的 Demo13 每跑一次都要 DriverManager.getConnection(...)——这一行背后发生了什么?

  1. 和 MySQL 服务器 TCP 三次握手建立网络连接
  2. 传输账号密码,验证身份
  3. 服务器分配资源,才算"接通了"

一次几百毫秒,看着不多。但真实程序里数据库操作每秒成百上千次——每个请求都现造一辆车、用完报废,服务器自己先被自己累死。

连接池:租车行的生意经

程序启动时:预建 N 条连接,停在"车位"上待命
        │
请求来了 → getConnection() 取一条 ← 车位空了
        │
用完了 → conn.close() ← 注意:不是销毁,是开回车位!
        │
下个请求 → 取的还是这条(或另一条闲置的)

三大好处:

  1. 快:省掉每次握手的开销,取连接是纯内存操作,微秒级
  2. 省:连接数有上限(比如最多 20 条),再猛的请求也只会排队,数据库不会被挤爆
  3. 好管理:url、账号、密码、池子大小集中在一处配置,不用散落在每个方法里
⚠️ 本节最关键的一句

连接池版代码里,conn.close() 的意思变了:不是"关闭连接",是"把连接还回池子"。忘写 close = 只借不还,池子很快被借空,后面的请求全部卡死等车。规矩反而更严了:用完必须还。

DataSource:JDK 又一份"总合同"

连接池这么好用,各大厂商都造——JDK 照 11.1 的思路又定了一个标准接口:

又是 4.3 接口 + 4.4 多态的老戏码:你面向 DataSource 接口编程,哪天想换 HikariCP,只改一行 new 的地方。

Demo14:Druid 版查询(和 11.3 只差一行)

准备:下载 druid(GitHub 阿里 Druid releases,拿 druid-1.2.x.jar),按 11.3 保姆四步导入(libs → 粘贴 → Add as Library)。

import com.alibaba.druid.pool.DruidDataSource;
import javax.sql.DataSource;
import java.sql.*;

public class Demo14 {
    public static void main(String[] args) throws Exception {
        // ① 造一辆"租车行",配上参数
        DataSource ds = new DruidDataSource();
        ((DruidDataSource) ds).setUrl("jdbc:mysql://localhost:3306/javaweb?serverTimezone=Asia/Shanghai");
        ((DruidDataSource) ds).setUsername("root");
        ((DruidDataSource) ds).setPassword("你的密码");
        ((DruidDataSource) ds).setInitialSize(5);      // 启动先备 5 条连接

        // ② 取连接(从池里拿,不再现造)
        try (Connection conn = ds.getConnection();
             Statement st = conn.createStatement();
             ResultSet rs = st.executeQuery("SELECT * FROM student")) {

            while (rs.next()) {
                System.out.println(rs.getInt("id") + " "
                        + rs.getString("name") + " " + rs.getInt("age"));
            }
        }   // close = 还车回池,连接没死,下个请求接着用
    }
}

对比 Demo13:只有"取连接"那一行换了写法(DriverManager.getConnection → ds.getConnection),Statement、ResultSet、try-with-resources 全套照旧——11.2 的方法地图一个字不用重学。

真实项目里,这个"租车行"会在工具类里造一次(static),全程序共用——这就是后面要学的 JDBCUtils 工具类套路。

💡 30 秒实验一:数数还车

System.out.println(ds.getConnection() != null); 连取三次都成功;把三个连接都 close 再取——照样成功。车是循环用的,不还才怪。

💡 30 秒实验二:只借不还的下场

把 setInitialSize(2)、setMaxWait(1000)(等车最多等 1 秒)设上,取两条连接不 close,再取第三条 → 超时报错拿不到连接。池子是有限的,借了要还。

💡 30 秒实验三:换合同另一家

如果项目里同时有 HikariCP 的 jar,把 new DruidDataSource() 换成 new HikariDataSource(),配同样的 url 账号密码——其余代码一行不动照样跑。这就是面向接口编程的含金量。

常见误区

  1. 「连接池的 close 真的关连接」——是还回池子,不是销毁。这也是为什么池子版代码更不能漏 close:只借不还,池子空了全卡死。
  2. 「用了连接池就不用学 JDBC 了」——恰恰相反,池子只负责"发连接",拿到 Connection 之后 Statement、ResultSet 那套原封不动。池子是 11.2 五步套路的第一步的升级版。
  3. 「池子越大越好」——错。每条连接在 MySQL 那边都占资源,设几百条反而拖垮数据库。一般 10~50 条按业务定。
  4. 「Druid 是 JDK 自带的」——不是。JDK 自带的只有 DataSource 接口;Druid、HikariCP 都要另导 jar(11.3 保姆步骤)。
  5. 「每次 getConnection 都是同一条连接」——不一定,池子给你的是任意一条闲置的。所以千万别把连接存到成员变量里全局共用——多线程会打架(多线程是后面的大章)。

自测一下

❓ Q1:没有连接池时,每获取一次连接的开销有哪些?

TCP 三次握手建立网络连接 → 传输账号密码做身份验证 → 服务器分配资源。每次几百毫秒,高频请求下服务器吃不消。

❓ Q2:连接池版代码里 conn.close() 是什么意思?

把连接还回池子(开回车位),不是销毁。下个请求可复用。忘 close = 只借不还,池子被借空后所有请求卡死。

❓ Q3:DataSource 和 Druid 是什么关系?

DataSource 是 JDK 自带的标准接口(合同);Druid 是阿里写的实现类(第三方 jar)。面向接口编程,Druid 换 HikariCP 只改 new 的那一行(4.3 + 4.4 的思想)。

❓ Q4:用了连接池,11.2 的五步套路还成立吗?

成立。只有第一步"获取连接"从 DriverManager 换成 ds.getConnection(),注册驱动由池子代劳;createStatement、执行、取结果、释放资源全套照旧。

❓ Q5(毕业题):把 Demo13 的 CRUD 四连改造成 Druid 连接池版。哪些行要改、哪些行一个字不动?

改动:①导 druid jar;②造一个 static 的 DruidDataSource 并配置四参(url/user/password/initialSize),全程序共用这一家租车行;③每处 DriverManager.getConnection(url, user, pass) 换成 ds.getConnection()。不动:Class.forName 可留可删(池子代劳);Statement/ResultSet/try-with-resources/CRUD 语句全部原样。核心认知:close 从"销毁"变"归还"。

下一步学什么

相关笔记