13.jdbc第二步Connection 对象
1.定位:Connection 是 JDBC 中代表 Java 程序与数据库物理连接的核心接口(位于 java.sql.Connection),是所有数据库操作的基础 —— 所有 Statement/PreparedStatement、事务控制、数据库元数据查询都依赖 Connection 实例。它的底层是 “TCP Socket 连接 + 数据库协议解析 + 连接状态管理” 的封装。
2.Socket 连接的创建流程
代码调用链:DriverManager.getConnection() → Driver.connect() → NativeMySQLConnection.connect() → NativeSocketConnection.connect() → Socket.connect()
- ① 核心入口:Driver.connect ()
// com.mysql.cj.jdbc.Driver @Override public Connection connect(String url, Properties info) throws SQLException { ConnectionUrl connUrl = ConnectionUrl.parseURL(url, info); if (!connUrl.getSchema().equals(ConnectionUrl.Type.MYSQL)) { return null; } // 1. 构建连接配置(包含主机、端口、超时等) MySQLConnectionConfiguration config = new MySQLConnectionConfiguration(connUrl); // 2. 创建 MySQL 底层连接实例,持有socketConnection的引用 MySQLConnection mysqlConn = new NativeMySQLConnection(config); // 3. 触发 Socket 连接创建 + 协议握手 mysqlConn.connect(); // 4. 封装为 Connection 实现类返回 return new ConnectionImpl(mysqlConn); } - ②NativeMySQLConnection 直接持有 socketConnection 的引用,且该引用是其核心成员变量
// com.mysql.cj.NativeMySQLConnection public class NativeMySQLConnection implements MySQLConnection { // 核心:直接持有 SocketConnection 类型的引用(NativeSocketConnection 是其实现类) private SocketConnection socketConnection; // 其他核心属性 private NativeProtocol protocol; private MySQLConnectionConfiguration connectionConfig; private boolean isClosed = false; // ... 其他属性省略 } - ③关键步骤:NativeMySQLConnection.connect ()
// com.mysql.cj.NativeMySQLConnection @Override public void connect() throws SQLException { // 1. 初始化 Socket 连接载体 this.socketConnection = new NativeSocketConnection(); // 2. 核心:创建 Socket 物理连接 this.socketConnection.connect(this.connectionConfig); // 3. 协议握手(认证+配置协商,依赖已创建的 Socket) handshake(); } - ④NativeSocketConnection 核心属性
// com.mysql.cj.protocol.NativeSocketConnection public class NativeSocketConnection implements SocketConnection { // 1. 底层物理 Socket 实例(TCP 连接载体) private Socket socket; // 2. Socket 输入流:读取数据库返回的字节数据(如 SQL 响应、握手包) private InputStream inputStream; // 3. Socket 输出流:向数据库发送字节数据(如 SQL 指令、认证包) private OutputStream outputStream; } - ⑤最终实现:NativeSocketConnection.connect ()
// com.mysql.cj.protocol.NativeSocketConnection @Override public void connect(MySQLConnectionConfiguration config) throws CJException { try { // 1. 构建 Socket 地址 InetSocketAddress socketAddress = new InetSocketAddress(config.getHost(), config.getPort()); // 2. 创建 Socket 实例(未建立连接) this.socket = new Socket(); // 3. 设置 Socket 选项(超时、禁用 Nagle 算法) this.socket.setTcpNoDelay(true); this.socket.setSoTimeout(config.getSocketTimeout()); // 4. 核心动作:触发 TCP 三次握手,建立物理连接 this.socket.connect(socketAddress, config.getConnectTimeout()); // 5. 获取 Socket 输入/输出流(用于协议通信) this.inputStream = new BufferedInputStream(this.socket.getInputStream()); this.outputStream = new BufferedOutputStream(this.socket.getOutputStream()); } catch (IOException e) { throw new CJCommunicationsException("Socket 连接失败", e); } }
3.Connection 对 Socket 连接的管理机制
管理链:ConnectionImpl → MySQLConnection → NativeSocketConnection → Socket
-
① 层级封装关系
// 1. ConnectionImpl 持有 MySQLConnection 引用 public class ConnectionImpl extends AbstractConnectionImpl { private MySQLConnection mysqlConnection; // 核心依赖 } // 2. MySQLConnection 持有 NativeSocketConnection 引用 public class NativeMySQLConnection implements MySQLConnection { private NativeSocketConnection socketConnection; // Socket 连接载体 } // 3. NativeSocketConnection 直接持有 Socket 实例 public class NativeSocketConnection implements SocketConnection { private Socket socket; // 物理 Socket 连接 private InputStream inputStream; // Socket 输入流 private OutputStream outputStream; // Socket 输出流 } -
② 生命周期管理(核心方法)
-
使用阶段:Socket 通信的代理
Connection 所有数据库操作(执行 SQL、事务控制)最终都会代理到 Socket 层 , 预编译阶段 ,
NativeProtocol.prepareStatement(sql),核心作用是:让数据库解析 SQL 语法、生成并缓存执行计划,返回唯一的 stmt_id(预编译 ID)// ConnectionImpl 执行 SQL 的底层代理逻辑 public class ConnectionImpl { public PreparedStatement prepareStatement(String sql) throws SQLException { // 校验连接状态(未关闭) checkClosed(); // 代理到 MySQLConnection,最终通过 Socket 发送预编译指令 long stmtId = this.mysqlConnection.getProtocol().prepareStatement(sql); return new NativePreparedStatement(this, sql, stmtId); } } // NativeProtocol 最终通过 Socket 发送数据 public class NativeProtocol { private SocketConnection socketConnection; // 核心引用 // 构造方法强制注入 socketConnection public NativeProtocol(SocketConnection socketConnection, MySQLConnectionConfiguration config) { this.socketConnection = socketConnection; // 绑定引用 this.config = config; // 初始化协议编解码的核心组件(如数据包解析器) this.packetReader = new PacketReader(this.socketConnection.getInputStream()); this.packetWriter = new PacketWriter(this.socketConnection.getOutputStream()); } public long prepareStatement(String sql) throws CJException { // 构建预编译指令数据包 byte[] preparePacket = buildPreparePacket(sql); // 通过 Socket 输出流发送 this.socketConnection.getOutputStream().write(preparePacket); this.socketConnection.getOutputStream().flush(); // 读取 Socket 输入流的响应 return parsePrepareResponse(this.socketConnection.getInputStream()); } } -
ConnectionImpl.prepareStatement(String sql)返回的PreparedStatement对象有什么用呢?
核心属性// NativePreparedStatement 核心属性(简化) public class NativePreparedStatement extends ClientPreparedStatement { // 1. 关联的 Connection(提供 Socket 通信通道) private ConnectionImpl connection; // 2. 原始 SQL 字符串(带?占位符,仅用于调试/日志) private String originalSql; // 3. 数据库返回的预编译 ID(核心,绑定执行计划) private long stmtId; // 核心:参数绑定管理器(而非 List<Object>) private ParameterBindings parameterBindings; } // 构造方法(绑定核心依赖) public NativePreparedStatement(ConnectionImpl connection, String sql, long stmtId) { this.connection = connection; this.originalSql = sql; this.stmtId = stmtId; // 解析 SQL 中的?个数,初始化 ParameterBindings int paramCount = countParameterMarkers(sql); this.parameterBindings = new ParameterBindings(paramCount); }- 预编译 ID 持有者 : 绑定数据库返回的 stmtId,后续执行 SQL 时无需重复预编译;
- 参数管理器 : 接收开发者设置的参数(如 setString() / setInt() ),编码为 MySQL 协议格式;
- SQL 执行器 : 封装 executeUpdate()/executeQuery() 等方法,触发底层 Socket 发送执行指令
- 结果处理器 : 解析数据库返回的执行结果,封装为 ResultSet/ 影响行数返回给开发者;
-
SQL语句形状 :
String sql = "INSERT INTO user(name, age) VALUES (?, ?)";赋值:通过
setXXX()方法为 ? 占位符赋值,驱动底层会将参数存入 NativePreparedStatement 的参数容器,并自动转义特殊字符(防注入)。@Override public void setString(int parameterIndex, String value) throws SQLException { // 1. 校验连接未关闭、参数索引有效(不超过?的个数) checkClosed(); checkParameterIndex(parameterIndex); // 2. 转义特殊字符(如'转\',防 SQL 注入) String escapedValue = this.connection.getEscapeProcessor().escapeString(value); // 3. 存入参数容器(按索引存储) this.parameterBindings.setBinding(parameterIndex - 1, escapedValue, Types.VARCHAR); }参数设置
PreparedStatement pstmt = conn.prepareStatement(sql); // 实际返回 NativePreparedStatement 实例s // 设置参数(索引从 1 开始,对应 SQL 中的第 N 个?) pstmt.setString(1, "张三"); // 第一个?赋值为字符串 pstmt.setInt(2, 25); // 第二个?赋值为整数可以直接使用无占位符的SQL语句,但是用 NativePreparedStatement 执行无占位符 SQL,相当于 “开跑车只走人行道”—— 语法合法,但完全浪费预编译的设计初衷。
-
4.手动实现复用connection对象,使用单例模式得到connection对象
public class DBUtil {
// 1. 私有静态 Connection:全局唯一,仅本类可直接访问
private static Connection conn;
public static Connection getConnection() {
//双重校验锁(DCL)
if (singleton == null) {
synchronized (DBUtil.class) {
if (singleton == null) {
try {
conn = DriverManager.getConnection(URL, USER, PWD);
}
} catch (SQLException e) {
throw new RuntimeException("获取数据库连接失败", e);
}
}
}
}
return conn;
}
}
但是这样做在实际开发中是致命的
-
线程不安全:多个线程共用同一个 Connection,执行 SQL 时会出现语句错乱、结果集异常(比如线程 A 的查询被线程 B 的更新覆盖);
-
连接泄漏:应用程序从数据库获取了 Connection,但未正常归还 / 关闭,导致数据库端认为该连接仍在使用,长期占用数据库的连接资源,最终耗尽数据库的最大连接数,使新请求无法建立连接。;
-
无连接数控制:全局仅一个 Connection,高并发场景下所有请求排队,性能极差;若改为多个静态 Connection,又无法管理数量,易导致数据库连接耗尽。

浙公网安备 33010602011771号