ICode9

精准搜索请尝试: 精确搜索
首页 > 数据库> 文章详细

mybatis第十一话 - mybaits getConnection连接数据库的源码分析

2022-03-19 19:03:14  阅读:181  来源: 互联网

标签:return getConnection mybaits SQLException Connection 源码 null 连接


到本篇文章,整个Mybatis框架的分析应该是都完了,但是也在我心中产生了几个想法,为什么看不到连接数据库的源码,连接数据库的流程是怎么样的?为什么项目重启的第一次连接查询会很慢?
本文主要探索一下mysql数据库的连接过程!

1.入口SimpleExecutor#prepareStatement

  • 前面的代码就不贴了,从这里开始
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
 Statement stmt;
 //获取连接  ###
 Connection connection = getConnection(statementLog);
 stmt = handler.prepare(connection, transaction.getTimeout());
 handler.parameterize(stmt);
 return stmt;
}

//BaseExecutor
protected Connection getConnection(Log statementLog) throws SQLException {
	//这里用的是springboot项目,事务都是由spring管理的
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

2.事务管理器SpringManagedTransaction

public Connection getConnection() throws SQLException {
  //从事务分析的那篇文章 我们得知如果有事务注解的情况下会先连接 这里就不会再建立新的连接了
  if (this.connection == null) {
  	//无事务的情况下 ### 
    openConnection();
  }
  return this.connection;
}

4.工具类DataSourceUtils

  • openConnection -> getConnection -> doGetConnection
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
	//。。。省略部分代码
	logger.debug("Fetching JDBC Connection from DataSource");
	Connection con = fetchConnection(dataSource);
	//。。。省略部分代码
}
  • 这一块正常是到默认的数据池管理的,例如hikari、druid等,这里直接简化源码流程了,直接来到MysqlDataSource
private static Connection fetchConnection(DataSource dataSource) throws SQLException {
	//用的是mysql的连接 这里就直接到MysqlDataSource	
	Connection con = dataSource.getConnection();
	if (con == null) {
		throw new IllegalStateException("DataSource returned null from getConnection(): " + dataSource);
	}
	return con;
}

5.MysqlDataSource

public java.sql.Connection getConnection() throws SQLException {
    return getConnection(this.user, this.password);
}

//getConnection
protected java.sql.Connection getConnection(Properties props) throws SQLException {
    String jdbcUrlToUse = this.explicitUrl ? this.url : getUrl();

    // URL should take precedence over properties
    //初始化一些连接信息 主机 端口 用户名密码 等
    ConnectionUrl connUrl = ConnectionUrl.getConnectionUrlInstance(jdbcUrlToUse, null);
    Properties urlProps = connUrl.getConnectionArgumentsAsProperties();
    urlProps.remove(PropertyKey.HOST.getKeyName());
    urlProps.remove(PropertyKey.PORT.getKeyName());
    urlProps.remove(PropertyKey.DBNAME.getKeyName());
    urlProps.stringPropertyNames().stream().forEach(k -> props.setProperty(k, urlProps.getProperty(k)));
	//找驱动 ###
    return mysqlDriver.connect(jdbcUrlToUse, props);
}

6.数据库驱动 NonRegisteringDriver

@Override
public java.sql.Connection connect(String url, Properties info) throws SQLException {

try {
    if (!ConnectionUrl.acceptsUrl(url)) {
        /*
         * According to JDBC spec:
         * The driver should return "null" if it realizes it is the wrong kind of driver to connect to the given URL. This will be common, as when the
         * JDBC driver manager is asked to connect to a given URL it passes the URL to each loaded driver in turn.
         */
        return null;
    }
ConnectionUrl conStr = ConnectionUrl.getConnectionUrlInstance(url, info);
       switch (conStr.getType()) {
           case SINGLE_CONNECTION:
           		//配置中的driver-class-name: com.mysql.cj.jdbc.Driver ###
               return com.mysql.cj.jdbc.ConnectionImpl.getInstance(conStr.getMainHost());

           case FAILOVER_CONNECTION:
           case FAILOVER_DNS_SRV_CONNECTION:
               return FailoverConnectionProxy.createProxyInstance(conStr);

           case LOADBALANCE_CONNECTION:
           case LOADBALANCE_DNS_SRV_CONNECTION:
               return LoadBalancedConnectionProxy.createProxyInstance(conStr);

           case REPLICATION_CONNECTION:
           case REPLICATION_DNS_SRV_CONNECTION:
               return ReplicationConnectionProxy.createProxyInstance(conStr);

           default:
               return null;
       }
     //。。。省略后续代码       
 }

7.构造类ConnectionImpl

public ConnectionImpl(HostInfo hostInfo) throws SQLException {

	//。。。省略部分代码
	try {
	   //创建一个新的IO
	   createNewIO(false);
	
	   unSafeQueryInterceptors();
	
	  AbandonedConnectionCleanupThread.trackConnection(this, this.getSession().getNetworkResources());
	} catch (SQLException ex) {
	  cleanup(ex);
	
	  // don't clobber SQL exceptions
	  throw ex;
	}	

	//。。。省略部分代码
}	

@Override
public void createNewIO(boolean isForReconnect) {
	//加锁连接
    synchronized (getConnectionMutex()) {
        // Synchronization Not needed for *new* connections, but definitely for connections going through fail-over, since we might get the new connection
        // up and running *enough* to start sending cached or still-open server-side prepared statements over to the backend before we get a chance to
        // re-prepare them...

        try {
            if (!this.autoReconnect.getValue()) {
                connectOneTryOnly(isForReconnect);
                return;
            }
			//连接 ###
            connectWithRetries(isForReconnect);
        } catch (SQLException ex) {
            throw ExceptionFactory.createException(UnableToConnectException.class, ex.getMessage(), ex);
        }
    }
}

8.Socket管理类NativeSocketConnection

  • connectWithRetries -> NativeSession#connect -> NativeSocketConnection#connect
@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;
		//创建一个socket工厂 断点可得到是socketFactory的默认实现StandardSocketFactory
        this.socketFactory = createSocketFactory(propSet.getStringProperty(PropertyKey.socketFactory).getStringValue());
        //连接 ###
        this.mysqlSocket = this.socketFactory.connect(this.host, this.port, propSet, loginTimeout);

        //。。。省略部分代码
        
		//mysql的输出流
        this.mysqlInput = new FullReadInputStream(rawInputStream);
        //mysql的写入流 最终与数据库进行IO写入和读取操作的类
        this.mysqlOutput = new BufferedOutputStream(this.mysqlSocket.getOutputStream(), 16384);
    } catch (IOException ioEx) {
        throw ExceptionFactory.createCommunicationsException(propSet, null, new PacketSentTimeHolder() {
        }, null, ioEx, getExceptionInterceptor());
    }
}

到这里,最终与数据库进行IO写入和读取操作的类就是这两个类FullReadInputStream、BufferedOutputStream

9.连接类StandardSocketFactory

  • 断点可以得知createSocketFactory,最终到StandardSocketFactory#connect,找到以下代码
//获取连接的主机数
InetAddress[] possibleAddresses = InetAddress.getAllByName(this.host);
if (possibleAddresses.length == 0) {
    throw new SocketException("No addresses for host");
}
// save last exception to propagate to caller if connection fails
SocketException lastException = null;
//如果多个地址需要循环连接 只要一个能连接上就截止
for (int i = 0; i < possibleAddresses.length; i++) {
    try {
    	//就当前类 return new Socket();
        this.rawSocket = createSocket(pset);
		//设置一些默认的配置
        configureSocket(this.rawSocket, pset);
		//将地址和端口 检查下并封装
        InetSocketAddress sockAddr = new InetSocketAddress(possibleAddresses[i], this.port);
        //如果不使用临时端口,则绑定到本地端口
        if (localSockAddr != null) {
            this.rawSocket.bind(localSockAddr);
        }
		//通过socket 建立连接
        this.rawSocket.connect(sockAddr, getRealTimeout(connectTimeout));
		//如果成功就会break掉
        break;
    } catch (SocketException ex) {
        lastException = ex;
        resetLoginTimeCountdown();
        this.rawSocket = null;
    }
}

10.在现在的项目中,基本上会出现第一次查询时比较慢

在现在的项目中,基本上会出现第一次查询时比较慢,那是因为数据库连接时延迟连接的,简单说明一下Druid连接池
DruidDataSource#CreateConnectionThread#run以线程的形式存在的,找到这段代码

//该方法是通过init进来的
if (emptyWait) {
    // 必须存在线程等待,才创建连接
    if (poolingCount >= notEmptyWaitThreadCount //
            && (!(keepAlive && activeCount + poolingCount < minIdle))
            && !isFailContinuous()
    ) {
    	//利用了Condition锁的消费生产队列模式
        empty.await();
    }

    // 防止创建超过maxActive数量的连接
    if (activeCount + poolingCount >= maxActive) {
        empty.await();
        continue;
    }
}

//。。。省略部分代码

//后续才是数据库的真正连接,然后会交由该数据连接池处理
//最终保存在private volatile DruidConnectionHolder[] connections;
boolean result = put(connection);

//在put添加完后会执行唤醒消费者的操作 同时连接数+1
notEmpty.signal();
notEmptySignalCount++;
  • DruidDataSource#getConnectionInternal
if (maxWait > 0) {
	//notEmpty.awaitNanos(estimate);  如果已经是最大的连接数了 等待指定时长获取连接
	holder = pollLast(nanos);
} else {
	//notEmpty.await(); 直接等待 等待创建新的连接或者是否连接后唤醒
    holder = takeLast();
}

以上就是本章的全部内容了。

上一篇:mybatis第十话 - mybaits整个事务流程的源码分析
下一篇:mysql第一话 - mysql基于docker的安装及使用

云想衣裳花想容,春风拂槛露华浓

标签:return,getConnection,mybaits,SQLException,Connection,源码,null,连接
来源: https://blog.csdn.net/qq_35551875/article/details/123429120

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有