JDBC底层源码解读(一位瘫痪多年的朋友看完后飞檐走壁,至今下落不明)

什么是JDBC?一百个实习生和应届生一百个都说是连接和操作数据库的方法。
JDBC,也就是 Java DataBase Connectivity (Java 数据库连接),其实是由Java语言编写的一堆接口和一些class类组成的一套工具类的程序。它里面的一堆接口,都是给不同的数据库服务商定义的一套Java连接数据库的一套规则,一套规范。也就是说JDBC并不能给我们直接连接和操作数据库,真正操作数据库的,是各大数据库服务商按照JDBC规范写出来的驱动Jar包,JDBC和驱动包的关系,就是很单纯很清纯的接口与实现类的关系,修电脑与不过夜的关系。

在这里插入图片描述

简单看下JDBC怎么连接我本地测试数据库的:

在这里插入图片描述

不难看出,JDBC主要有四步操作:

1:加载数据库驱动
2:获取数据库连接
3:创建Statement/PreparedStatement
4:执行CRUD操作,封装返回结果ResultSet

下面开始讲大道理:

一:加载数据库驱动

在这里插入图片描述
在这里插入图片描述

都说了驱动Jar包,那就可以通过全限定类名通过反射方式来创建和加载。加载进来之后,在DriverManager驱动管理器中就会有一个方法registerDriver的,就会悄咪咪找上加载进来的驱动。既然是规范,那些各大数据库服务商驱动也不能乱来,都要老老实实实现一个java.sql.Driver的接口。
在这里插入图片描述
在这里插入图片描述

二:获取数据库连接

在这里插入图片描述
这一步会返回一个链接Connection,创建链接主要在getConnection方法,进去看。
在这里插入图片描述
在这里插入图片描述

那个被循环的是一个装满了已注册的驱动的ArrayList,以前老版本好像也是个vector

在这里插入图片描述

这都不是重点,我们还要继续往driver.connect方法里面看:

在这里插入图片描述

然后可以看到这里那么多种连接类型,各自都有什么用,创建过程是怎样,就不每个进去看了,只看最简单的ConnectionImpl通过什么创建链接
public ConnectionImpl(HostInfo hostInfo) throws SQLException {
    
    
        try {
    
    
            this.origHostInfo = hostInfo;
            this.origHostToConnectTo = hostInfo.getHost();
            this.origPortToConnectTo = hostInfo.getPort();
            this.database = hostInfo.getDatabase();
            this.user = StringUtils.isNullOrEmpty(hostInfo.getUser()) ? "" : hostInfo.getUser();
            this.password = StringUtils.isNullOrEmpty(hostInfo.getPassword()) ? "" : hostInfo.getPassword();
            this.props = hostInfo.exposeAsProperties();
            this.propertySet = new JdbcPropertySetImpl();
            this.propertySet.initializeProperties(this.props);
            this.nullStatementResultSetFactory = new ResultSetFactory(this, (StatementImpl)null);
            this.session = new NativeSession(hostInfo, this.propertySet);
            this.session.addListener(this);
            this.autoReconnectForPools = this.propertySet.getBooleanProperty(PropertyKey.autoReconnectForPools);
            this.cachePrepStmts = this.propertySet.getBooleanProperty(PropertyKey.cachePrepStmts);
            this.autoReconnect = this.propertySet.getBooleanProperty(PropertyKey.autoReconnect);
            this.useUsageAdvisor = this.propertySet.getBooleanProperty(PropertyKey.useUsageAdvisor);
            this.reconnectAtTxEnd = this.propertySet.getBooleanProperty(PropertyKey.reconnectAtTxEnd);
            this.emulateUnsupportedPstmts = this.propertySet.getBooleanProperty(PropertyKey.emulateUnsupportedPstmts);
            this.ignoreNonTxTables = this.propertySet.getBooleanProperty(PropertyKey.ignoreNonTxTables);
            this.pedantic = this.propertySet.getBooleanProperty(PropertyKey.pedantic);
            this.prepStmtCacheSqlLimit = this.propertySet.getIntegerProperty(PropertyKey.prepStmtCacheSqlLimit);
            this.useLocalSessionState = this.propertySet.getBooleanProperty(PropertyKey.useLocalSessionState);
            this.useServerPrepStmts = this.propertySet.getBooleanProperty(PropertyKey.useServerPrepStmts);
            this.processEscapeCodesForPrepStmts = this.propertySet.getBooleanProperty(PropertyKey.processEscapeCodesForPrepStmts);
            this.useLocalTransactionState = this.propertySet.getBooleanProperty(PropertyKey.useLocalTransactionState);
            this.disconnectOnExpiredPasswords = this.propertySet.getBooleanProperty(PropertyKey.disconnectOnExpiredPasswords);
            this.readOnlyPropagatesToServer = this.propertySet.getBooleanProperty(PropertyKey.readOnlyPropagatesToServer);
            String exceptionInterceptorClasses = this.propertySet.getStringProperty(PropertyKey.exceptionInterceptors).getStringValue();
            if (exceptionInterceptorClasses != null && !"".equals(exceptionInterceptorClasses)) {
    
    
                this.exceptionInterceptor = new ExceptionInterceptorChain(exceptionInterceptorClasses, this.props, this.session.getLog());
            }

            if ((Boolean)this.cachePrepStmts.getValue()) {
    
    
                this.createPreparedStatementCaches();
            }

            if ((Boolean)this.propertySet.getBooleanProperty(PropertyKey.cacheCallableStmts).getValue()) {
    
    
                this.parsedCallableStatementCache = new LRUCache((Integer)this.propertySet.getIntegerProperty(PropertyKey.callableStmtCacheSize).getValue());
            }

            if ((Boolean)this.propertySet.getBooleanProperty(PropertyKey.allowMultiQueries).getValue()) {
    
    
                this.propertySet.getProperty(PropertyKey.cacheResultSetMetadata).setValue(false);
            }

            if ((Boolean)this.propertySet.getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) {
    
    
                this.resultSetMetadataCache = new LRUCache((Integer)this.propertySet.getIntegerProperty(PropertyKey.metadataCacheSize).getValue());
            }

            if (this.propertySet.getStringProperty(PropertyKey.socksProxyHost).getStringValue() != null) {
    
    
                this.propertySet.getProperty(PropertyKey.socketFactory).setValue(SocksProxySocketFactory.class.getName());
            }

            this.dbmd = this.getMetaData(false, false);
            this.initializeSafeQueryInterceptors();
        } catch (CJException var5) {
    
    
            throw SQLExceptionsMapping.translateException(var5, this.getExceptionInterceptor());
        }

        try {
    
    
            this.createNewIO(false);
            this.unSafeQueryInterceptors();
            AbandonedConnectionCleanupThread.trackConnection(this, this.getSession().getNetworkResources());
        } catch (SQLException var3) {
    
    
            this.cleanup(var3);
            throw var3;
        } catch (Exception var4) {
    
    
            this.cleanup(var4);
            throw SQLError.createSQLException((Boolean)this.propertySet.getBooleanProperty(PropertyKey.paranoid).getValue() ? Messages.getString("Connection.0") : Messages.getString("Connection.1", new Object[]{
    
    this.session.getHostInfo().getHost(), this.session.getHostInfo().getPort()}), "08S01", var4, this.getExceptionInterceptor());
        }
    }

虽然又臭又长,但是还是看到了通过socketFactory创建出socket进行TCP长链接,数据之间的交互就是通过socketInputStream输入流和socketOutPutStream输出流控制入参和出参,参数包括用户名密码,SQL语句,查询结果集等等,个人能力有限就只了解到这里,再深入的背不了,开发也用不上,就暂时没去管了。

三:通过第二步创建好的connection链接,就可以创建SQL执行环境Statement或者PreparedStatement

关于未预编译和预编译的区别,这里暂时不深究了

在这里插入图片描述

在这里插入图片描述

同理Statement的我们也看一下,比预编译的会简单很多

在这里插入图片描述

在这里他们都会返回一个stmt对象,也就是statement, 它到底是什么?为什么有了它就可以执行SQL?

在这里插入图片描述
在这里插入图片描述

带着疑问进去看,发现都实现了或者继承了JdbcStatement,我们就很自然想到了是JDBC一种规范

在这里插入图片描述

再进来看,发现它继承了一个Statement接口一个Query接口,其中Statement接口果然就是提供了操作数据库的API,也就是上面说的执行SQL的环境,这些接口起到的就是一个规范化的作用,不管增删改,都对应着executeUpdate方法,查方法就对应着executeQuery方法。

在这里插入图片描述

它的一些实现类,就是我们进来的入口,很清楚看到实现类都是在com.mysql.cj.jdbc驱动包里面的。而关于Query,更多的是一些自定义的内容,例如拿到当前statement ID,检查是否超时,设置过期时间,设置执行时间,设置返回集类型…太多了就不一个个点开看了

在这里插入图片描述
在这里插入图片描述

创建执行环境是Statement,执行是最后一步干的事情,我们继续看~

四:通过上面几步创建好了长链接,创建好了执行环境,这一步终于开始执行查询和封装ResultSet返回结果集对象

显而易见,这里调用的executeQuery就是上面我们看的JDBC定的规范,返回是一个ResultSet(只有查询操作才是ResultSet,增删改操作都是返回一个int)。我们要看具体的执行,就要进去到对应的实现类也就是自己导入的那个驱动jar包里面看

在这里插入图片描述

public ResultSet executeQuery(String sql) throws SQLException {
    
    
        try {
    
    
            synchronized(this.checkClosed().getConnectionMutex()) {
    
    
                JdbcConnection locallyScopedConn = this.connection;
                this.retrieveGeneratedKeys = false;
                this.checkNullOrEmptyQuery(sql);
                this.resetCancelledState();
                this.implicitlyCloseAllOpenResults();
                if (sql.charAt(0) == '/' && sql.startsWith("/* ping */")) {
    
    
                    this.doPingInstead();
                    return this.results;
                } else {
    
    
                    this.setupStreamingTimeout(locallyScopedConn);
                    if (this.doEscapeProcessing) {
    
    
                        Object escapedSqlResult = EscapeProcessor.escapeSQL(sql, this.session.getServerSession().getServerTimeZone(), this.session.getServerSession().getCapabilities().serverSupportsFracSecs(), this.session.getServerSession().isServerTruncatesFracSecs(), this.getExceptionInterceptor());
                        sql = escapedSqlResult instanceof String ? (String)escapedSqlResult : ((EscapeProcessorResult)escapedSqlResult).escapedSql;
                    }

                    char firstStatementChar = StringUtils.firstAlphaCharUc(sql, findStartOfStatement(sql));
                    this.checkForDml(sql, firstStatementChar);
                    CachedResultSetMetaData cachedMetaData = null;
                    if (this.useServerFetch()) {
    
    
                        this.results = this.createResultSetUsingServerFetch(sql);
                        return this.results;
                    } else {
    
    
                        CancelQueryTask timeoutTask = null;
                        String oldDb = null;

                        try {
    
    
                            timeoutTask = this.startQueryTimer(this, this.getTimeoutInMillis());
                            if (!locallyScopedConn.getDatabase().equals(this.getCurrentDatabase())) {
    
    
                                oldDb = locallyScopedConn.getDatabase();
                                locallyScopedConn.setDatabase(this.getCurrentDatabase());
                            }

                            if ((Boolean)locallyScopedConn.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) {
    
    
                                cachedMetaData = locallyScopedConn.getCachedMetaData(sql);
                            }

                            locallyScopedConn.setSessionMaxRows(this.maxRows);
                            this.statementBegins();
                            this.results = (ResultSetInternalMethods)((NativeSession)locallyScopedConn.getSession()).execSQL(this, sql, this.maxRows, (NativePacketPayload)null, this.createStreamingResultSet(), this.getResultSetFactory(), cachedMetaData, false);
                            if (timeoutTask != null) {
    
    
                                this.stopQueryTimer(timeoutTask, true, true);
                                timeoutTask = null;
                            }
                        } catch (OperationCancelledException | CJTimeoutException var16) {
    
    
                            throw SQLExceptionsMapping.translateException(var16, this.exceptionInterceptor);
                        } finally {
    
    
                            this.query.getStatementExecuting().set(false);
                            this.stopQueryTimer(timeoutTask, false, false);
                            if (oldDb != null) {
    
    
                                locallyScopedConn.setDatabase(oldDb);
                            }

                        }

                        this.lastInsertId = this.results.getUpdateID();
                        if (cachedMetaData != null) {
    
    
                            locallyScopedConn.initializeResultsMetadataFromCache(sql, cachedMetaData, this.results);
                        } else if ((Boolean)this.connection.getPropertySet().getBooleanProperty(PropertyKey.cacheResultSetMetadata).getValue()) {
    
    
                            locallyScopedConn.initializeResultsMetadataFromCache(sql, (CachedResultSetMetaData)null, this.results);
                        }

                        return this.results;
                    }
                }
            }
        } catch (CJException var19) {
    
    
            throw SQLExceptionsMapping.translateException(var19, this.getExceptionInterceptor());
        }
    }

获取要操作的database
在这里插入图片描述
execSQL,一看这名字,肯定就是它负责执行的没错了。进去看看
在这里插入图片描述
在这里插入图片描述
返回的var21,就是ResultSet类型的
再上一层的的result,就一样是一个Wrapper包裹,实现了IO.序列化接口,方便把对象转换为有序字节流,在网络上传输或者保存,返回的时候,也是通过反序列化,一层层包成resultSet对象返回,提供给我们一个.next方法返回的Boolean值判断是否还有下一个元素,然后根据Key(参数名)读取它的value(查到的结果)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

小彩蛋:在上面我们可以看出,JDBC规范接口的类是在Java的启动类加载器进行加载的,而我们后面引入的驱动jar包则是通过扩展类加载器加载的,而JDBC的Drive中又要根据不同数据库厂商的jar包来实现不同的数据库连接,所以这就是对双亲委派模式的一个破坏。
以上就是我的源码总结,虽然又长又臭,但是还是看下来了~如果有总结得不对的地方欢迎留言给我。

猜你喜欢

转载自blog.csdn.net/whiteBearClimb/article/details/108258485
今日推荐