mybatis第十一话 - mybaits getConnection连接数据库的源码分析
2022/3/20 2:29:36
本文主要是介绍mybatis第十一话 - mybaits getConnection连接数据库的源码分析,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
到本篇文章,整个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的安装及使用
云想衣裳花想容,春风拂槛露华浓
这篇关于mybatis第十一话 - mybaits getConnection连接数据库的源码分析的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-12-27Nacos多环境配置学习入门
- 2024-12-27Nacos快速入门学习入门
- 2024-12-27Nacos快速入门学习入门
- 2024-12-27Nacos配置中心学习入门指南
- 2024-12-27Nacos配置中心学习入门
- 2024-12-27Nacos做项目隔离学习入门
- 2024-12-27Nacos做项目隔离学习入门
- 2024-12-27Nacos初识学习入门:轻松掌握服务发现与配置管理
- 2024-12-27Nacos初识学习入门:轻松掌握Nacos基础操作
- 2024-12-27Nacos多环境配置学习入门