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的安装及使用
云想衣裳花想容,春风拂槛露华浓
标签:return,getConnection,mybaits,SQLException,Connection,源码,null,连接 来源: https://blog.csdn.net/qq_35551875/article/details/123429120