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 连接的管理机制

管理链:ConnectionImplMySQLConnectionNativeSocketConnectionSocket

  • ① 层级封装关系

    // 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,又无法管理数量,易导致数据库连接耗尽。

结尾

posted @ 2025-12-04 17:46  那就改变世界吧  阅读(143)  评论(0)    收藏  举报