com.mysql.cj.jdbc.Driver 解析

Driver类的文档描述:

java 复制代码
package com.mysql.cj.jdbc;

import java.sql.SQLException;

/**
 * The Java SQL framework allows for multiple database drivers. Each driver should supply a class that implements the Driver interface
 * 
 * <p>
 * The DriverManager will try to load as many drivers as it can find and then for any given connection request, it will ask each driver in turn to try to
 * connect to the target URL.
 * 
 * <p>
 * It is strongly recommended that each Driver class should be small and standalone so that the Driver class can be loaded and queried without bringing in vast
 * quantities of supporting code.
 * 
 * <p>
 * When a Driver class is loaded, it should create an instance of itself and register it with the DriverManager. This means that a user can load and register a
 * driver by doing Class.forName("foo.bah.Driver")
 */
public class Driver extends NonRegisteringDriver implements java.sql.Driver {
    //
    // Register ourselves with the DriverManager
    //
    static {
        try {
            java.sql.DriverManager.registerDriver(new Driver());
        } catch (SQLException E) {
            throw new RuntimeException("Can't register driver!");
        }
    }

    /**
     * Construct a new driver and register it with DriverManager
     * 
     * @throws SQLException
     *             if a database error occurs.
     */
    public Driver() throws SQLException {
        // Required for Class.forName().newInstance()
    }
}

这里的英文描述核心是说:

1.Driver类要保证自己被加载时就主动注册到DriverManager里

2.DriverManager会一次尝试使用每个Driver与数据库建立连接

3.每个Driver实现类需要实现java.sql.Driver接口

4.Driver实现类不应该有依赖项,自己作为一个jar包就可以完成工作;

对于描述1,可以看到Driver的static代码块中调用了

java.sql.DriverManager.registerDriver(new Driver())

来保证Class被加载时就能注册进DriverManager里,这行代码是怎么执行注册的呢?,跟踪发现调用链:

java.sql.DriverManager.registerDriver

->registerDriver(java.sql.Driver driver,DriverAction da /*注册时该变量为Null,该类的负责取消注册*/)

->registeredDrivers.addIfAbsent(new DriverInfo(driver, da));

registerDrivers的定义如下,所以注册就是加入DriverManager的属性队列里

java 复制代码
 private final static CopyOnWriteArrayList<DriverInfo> registeredDrivers = new CopyOnWriteArrayList<>();

com.mysql.cj.jdbc.Driver类通过继承NonRegisteringDriver来实现java.sql.Driver里的方法.NonRegisteringDriver类

NonRegisteringDriver 的静态代码块会开启一个清理mysql弃置连接的后台线程,核心是通过软引用实现的.

java 复制代码
    static {
        try {
            Class.forName(AbandonedConnectionCleanupThread.class.getName());
        } catch (ClassNotFoundException e) {
            // ignore
        }
    }


Reference<? extends MysqlConnection> reference是一个软引用队列,jvm进行垃圾回收时,完成对垃圾回收对象的回收后会把被回收的对象放到这个队列里,AbandonedConnectionCleanupThread对象拿到后调用方法finalizeResource方法关闭MysqlConnection的底层连接以及InputStream和OutputStream
AbandonedConnectionCleanupThread
public void run() {
        for (;;) {
            try {
                checkThreadContextClassLoader();
                Reference<? extends MysqlConnection> reference = referenceQueue.remove(5000);
                if (reference != null) {
                    finalizeResource((ConnectionFinalizerPhantomReference) reference);
                }
            } catch (InterruptedException e) {
                threadRefLock.lock();
                try {
                    threadRef = null;

                    // Finalize remaining references.
                    Reference<? extends MysqlConnection> reference;
                    while ((reference = referenceQueue.poll()) != null) {
                        finalizeResource((ConnectionFinalizerPhantomReference) reference);
                    }
                    connectionFinalizerPhantomRefs.clear();
                } finally {
                    threadRefLock.unlock();
                }
                return;
            } catch (Exception ex) {
                // Nowhere to really log this.
            }
        }
    }

ConnectionImpl

ConnectionImpl类完成参数设置,交给NativerSocketConnection完成底层连接,构造函数里的createNewIO方法完成连接

HostInfo类保存连接的信息,比如host,port,username,password, hostProperties

NativeSocketConnection

NativeSocketConnection 的connection方法完成建立连接以及读写缓冲区分配,读写缓冲区大小居为16M,

java 复制代码
    @Override
    public void connect(String hostName, int portNumber, PropertySet propSet, ExceptionInterceptor excInterceptor, Log log, int loginTimeout) {

        try {
            this.port = portNumber;
            this.host = hostName;
            this.propertySet = propSet;
            this.exceptionInterceptor = excInterceptor;

            this.socketFactory = createSocketFactory(propSet.getStringProperty(PropertyKey.socketFactory).getStringValue());
            this.mysqlSocket = this.socketFactory.connect(this.host, this.port, propSet, loginTimeout);

            int socketTimeout = propSet.getIntegerProperty(PropertyKey.socketTimeout).getValue();
            if (socketTimeout != 0) {
                try {
                    this.mysqlSocket.setSoTimeout(socketTimeout);
                } catch (Exception ex) {
                    /* Ignore if the platform does not support it */
                }
            }

            this.socketFactory.beforeHandshake();

            InputStream rawInputStream;
            if (propSet.getBooleanProperty(PropertyKey.useReadAheadInput).getValue()) {
                rawInputStream = new ReadAheadInputStream(this.mysqlSocket.getInputStream(), 16384,
                        propSet.getBooleanProperty(PropertyKey.traceProtocol).getValue(), log);
            } else if (propSet.getBooleanProperty(PropertyKey.useUnbufferedInput).getValue()) {
                rawInputStream = this.mysqlSocket.getInputStream();
            } else {
                rawInputStream = new BufferedInputStream(this.mysqlSocket.getInputStream(), 16384);
            }

            this.mysqlInput = new FullReadInputStream(rawInputStream);
            this.mysqlOutput = new BufferedOutputStream(this.mysqlSocket.getOutputStream(), 16384);
        } catch (IOException ioEx) {
            throw ExceptionFactory.createCommunicationsException(propSet, null, new PacketSentTimeHolder() {
            }, null, ioEx, getExceptionInterceptor());
        }
    }

NativeProtocol完成与mysql的交互,SimplePacketReader,SimplePacketSender完成底层socket的读写

java 复制代码
public class SimplePacketSender implements MessageSender<NativePacketPayload> {
    private BufferedOutputStream outputStream;

    public SimplePacketSender(BufferedOutputStream outputStream) {
        this.outputStream = outputStream;
    }
//send方法利用mysql交互协议完成socket写
    public void send(byte[] packet, int packetLen, byte packetSequence) throws IOException {
        PacketSplitter packetSplitter = new PacketSplitter(packetLen);
        while (packetSplitter.nextPacket()) {
            this.outputStream.write(NativeUtils.encodeMysqlThreeByteInteger(packetSplitter.getPacketLen()));
            this.outputStream.write(packetSequence++);
            this.outputStream.write(packet, packetSplitter.getOffset(), packetSplitter.getPacketLen());
        }
        this.outputStream.flush();
    }

    @Override
    public MessageSender<NativePacketPayload> undecorateAll() {
        return this;
    }

    @Override
    public MessageSender<NativePacketPayload> undecorate() {
        return this;
    }
}
java 复制代码
/*
 * Copyright (c) 2016, 2021, Oracle and/or its affiliates.
 *
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU General Public License, version 2.0, as published by the
 * Free Software Foundation.
 *
 * This program is also distributed with certain software (including but not
 * limited to OpenSSL) that is licensed under separate terms, as designated in a
 * particular file or component or in included license documentation. The
 * authors of MySQL hereby grant you an additional permission to link the
 * program and your derivative works with the separately licensed software that
 * they have included with MySQL.
 *
 * Without limiting anything contained in the foregoing, this file, which is
 * part of MySQL Connector/J, is also subject to the Universal FOSS Exception,
 * version 1.0, a copy of which can be found at
 * http://oss.oracle.com/licenses/universal-foss-exception.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0,
 * for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA
 */

package com.mysql.cj.protocol.a;

import java.io.IOException;
import java.util.Optional;

import com.mysql.cj.Messages;
import com.mysql.cj.conf.RuntimeProperty;
import com.mysql.cj.exceptions.CJPacketTooBigException;
import com.mysql.cj.protocol.MessageReader;
import com.mysql.cj.protocol.SocketConnection;

/**
 * Simple implementation of {@link MessageReader} which handles the receiving of logical MySQL packets from the provided socket input stream.
 * Multi-packets are handled outside of this reader.
 */
public class SimplePacketReader implements MessageReader<NativePacketHeader, NativePacketPayload> {

    protected SocketConnection socketConnection;
    protected RuntimeProperty<Integer> maxAllowedPacket;

    private byte readPacketSequence = -1;

    NativePacketHeader lastHeader = null;
    NativePacketPayload lastMessage = null;

    public SimplePacketReader(SocketConnection socketConnection, RuntimeProperty<Integer> maxAllowedPacket) {
        this.socketConnection = socketConnection;
        this.maxAllowedPacket = maxAllowedPacket;
    }

    @Override
    public NativePacketHeader readHeader() throws IOException {
        if (this.lastHeader == null) {
            return readHeaderLocal();
        }
        NativePacketHeader hdr = this.lastHeader;
        this.lastHeader = null;
        this.readPacketSequence = hdr.getMessageSequence();
        return hdr;
    }

    @Override
    public NativePacketHeader probeHeader() throws IOException {
        this.lastHeader = readHeaderLocal();
        return this.lastHeader;
    }

    private NativePacketHeader readHeaderLocal() throws IOException {
        NativePacketHeader hdr = new NativePacketHeader();

        try {
            this.socketConnection.getMysqlInput().readFully(hdr.getBuffer().array(), 0, NativeConstants.HEADER_LENGTH);
            int packetLength = hdr.getMessageSize();
            if (packetLength > this.maxAllowedPacket.getValue()) {
                throw new CJPacketTooBigException(packetLength, this.maxAllowedPacket.getValue());
            }
        } catch (IOException | CJPacketTooBigException e) {
            try {
                this.socketConnection.forceClose();
            } catch (Exception ex) {
                // ignore
            }
            throw e;
        }

        this.readPacketSequence = hdr.getMessageSequence();
        return hdr;
    }

    @Override
    public NativePacketPayload readMessage(Optional<NativePacketPayload> reuse, NativePacketHeader header) throws IOException {
        if (this.lastMessage == null) {
            return readMessageLocal(reuse, header);
        }
        NativePacketPayload buf = this.lastMessage;
        this.lastMessage = null;
        return buf;
    }

    @Override
    public NativePacketPayload probeMessage(Optional<NativePacketPayload> reuse, NativePacketHeader header) throws IOException {
        this.lastMessage = readMessageLocal(reuse, header);
        return this.lastMessage;
    }

    private NativePacketPayload readMessageLocal(Optional<NativePacketPayload> reuse, NativePacketHeader header) throws IOException {
        try {
            int packetLength = header.getMessageSize();
            NativePacketPayload message;
            if (reuse.isPresent()) {
                message = reuse.get();
                // Set the Buffer to it's original state
                message.setPosition(0);
                // Do we need to re-alloc the byte buffer?
                if (message.getByteBuffer().length < packetLength) {
                    // Note: We actually check the length of the buffer, rather than getBufLength(), because getBufLength()
                    // is not necessarily the actual length of the byte array used as the buffer
                    message.setByteBuffer(new byte[packetLength]);
                }

                // Set the new length
                message.setPayloadLength(packetLength);
            } else {
                message = new NativePacketPayload(new byte[packetLength]);
            }

            // Read the data from the server
            int numBytesRead = this.socketConnection.getMysqlInput().readFully(message.getByteBuffer(), 0, packetLength);
            if (numBytesRead != packetLength) {
                throw new IOException(Messages.getString("PacketReader.1", new Object[] { packetLength, numBytesRead }));
            }
            return message;

        } catch (IOException e) {
            try {
                this.socketConnection.forceClose();
            } catch (Exception ex) {
                // ignore
            }
            throw e;
        }
    }

    @Override
    public byte getMessageSequence() {
        return this.readPacketSequence;
    }

    @Override
    public void resetMessageSequence() {
        this.readPacketSequence = 0;
    }

}
相关推荐
不光头强21 小时前
Spring框架的事务管理
数据库·spring·oracle
百***92021 天前
【MySQL】MySQL库的操作
android·数据库·mysql
q***76661 天前
Spring Boot 从 2.7.x 升级到 3.3注意事项
数据库·hive·spring boot
信仰_2739932431 天前
Redis红锁
数据库·redis·缓存
人间打气筒(Ada)1 天前
Centos7 搭建hadoop2.7.2、hbase伪分布式集群
数据库·分布式·hbase
心灵宝贝1 天前
如何在 Mac 上安装 MySQL 8.0.20.dmg(从下载到使用全流程)
数据库·mysql·macos
想睡hhh1 天前
mysql索引——理解索引机制及操作
mysql
剑动山河1 天前
ubuntu 升级mysql由mysql5.7.42 升级到8.4.0
mysql·ubuntu·adb
奋斗的牛马1 天前
OFDM理解
网络·数据库·单片机·嵌入式硬件·fpga开发·信息与通信
忧郁的橙子.1 天前
一、Rabbit MQ 初级
服务器·网络·数据库