0%

MySQL JDBC Driver源码解读

MySQL JDBC Driver源码解读

You can get the source code from Github: https://github.com/mysql/mysql-connector-j

当调用com.mysql.cj.jdbc.Driver类时, 静态代码块会默认加载驱动.

Driver

DriverManager.getConnection时,会通过SPI获取

无需手动注册

ClassLoader加载com.mysql.cj.jdbc.Driver时,会通过静态内部类注册。
也就是说,SPI扫到这个类,就会注册.

设计比较巧妙的一点:

classDiagram

NonRegisteringDriver <|-- Driver

NonRegisteringDriver: Connection connect(String url, Properties info)

创建Connection

sequenceDiagram
  participant ClientImpl
  participant DriverManager
  participant Driver
  participant Connection
  participant NativeSession
  participant NativeProtocol
  participant StatementImpl

alt 创建连接
ClientImpl ->> DriverManager:  getConnection()
DriverManager ->> DriverManager: 加载MySQL的Driver
DriverManager ->> Driver: connect()
Driver ->> Connection: ()
Connection ->> NativeSession: connect()
NativeSession ->> NativeProtocol: connect()
end

alt 执行查询
ClientImpl ->> Connection:  createStatement()
Connection ->> StatementImpl: ()
Connection -->> ClientImpl:  Statement

ClientImpl ->> StatementImpl:  execute()
StatementImpl ->> NativeSession: execSQL()获取结果
end

alt 查询结果
ClientImpl ->> StatementImpl: getResultSet()
StatementImpl -->> ClientImpl: ResultSet
end

构建ConnectionUrl

数据库Url和连接参数 的容器
根据schema来区分ConnectionUrl的类型ConnectionUrl.Type, Type中定义了创建不同ConnectionUrl的类名和参数。

构建Connection

Connection代表与特定数据库的会话。在 Connection 的上下文中,执行 SQL 语句并返回结果。

没有Connection持有一个NativeSession

创建新的socket链接 -> 与服务端握手 -> 构建与服务端的通信协议 -> 通过MySQL协议与服务端连接

NativeSession

与SocketServer的会话

NativeSocketConnection

持有与服务端链接的InputStream和OutputStream

SocketFactory

允许在驱动程序中创建可插入套接字的接口

创建Socket链接

com.mysql.cj.protocol.Protocol

协议提供与 MySQL 服务器通信的设施

充斥着好多pocket相关的类

DNS更新后新ip未生效问题

StandardSocketFactory.connect方法中通过域名获取ip地址。

https://github.com/mysql/mysql-connector-j/blob/805f872a57875f311cb82487efcfb070411a3fa0/src/main/core-impl/java/com/mysql/cj/protocol/StandardSocketFactory.java#L130

1
InetAddress[] possibleAddresses = InetAddress.getAllByName(this.host);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// look-up or remove from cache  
Addresses addrs;
if (useCache) {
addrs = cache.get(host);
} else {
addrs = cache.remove(host);
if (addrs != null) {
if (addrs instanceof CachedAddresses) {
// try removing from expirySet too if CachedAddresses
expirySet.remove(addrs);
}
addrs = null;
}
}

if (addrs == null) {
// create a NameServiceAddresses instance which will look up
// the name service and install it within cache... Addresses oldAddrs = cache.putIfAbsent(
host,
addrs = new NameServiceAddresses(host, reqAddr)
);
if (oldAddrs != null) { // lost putIfAbsent race
addrs = oldAddrs;
}
}

// ask Addresses to get an array of InetAddress(es) and clone it
return addrs.get().clone();

#InetAddress 带了缓存

在Java中,InetAddress 类用于处理IP地址,其内部实现了DNS缓存机制以提高域名解析的效率。通过设置JVM参数或者系统属性,可以调整InetAddress的DNS缓存行为。

1. JVM参数设置

可以通过在启动Java应用程序时传递JVM参数来设置DNS缓存的行为。以下是常用的JVM参数:

  • java -Dnetworkaddress.cache.ttl=10 -jar test.jar:这个参数设置了DNS缓存的时间为10秒。默认情况下,如果不解设,缓存时间为30秒。
  • java -Dnetworkaddress.cache.ttl=0 -jar test.jar:设置为0意味着不缓存解析成功的结果,每次解析都会访问DNS服务器。
  • java -Dnetworkaddress.cache.ttl=-1 -jar test.jar:设置为-1表示永久缓存解析成功的结果,直到应用程序重启。

2. 系统属性设置

除了JVM参数,还可以通过设置系统属性来调整DNS缓存策略:

  • Security.setProperty("networkaddress.cache.ttl", "-1"):通过编程方式设置DNS缓存时间为永久。

3. 源码级别的设置

在Java代码中,可以通过修改InetAddressCachePolicy类的行为来控制DNS缓存策略。例如,可以通过修改cachePolicy静态变量的值来控制缓存时间。

4. 安全管理器(SecurityManager)的影响

当设置了安全管理器(SecurityManager)时,对于DNS缓存时间的默认行为会有所改变:

  • 如果设置了SecurityManager,默认的CachePolicyFOREVER,即永久缓存DNS的结果,直到进程终止。
  • 如果没有设置SecurityManager,可以通过修改java.security文件中的相关属性来控制DNS缓存行为。

注意事项

  • 永久缓存DNS结果(FOREVER)可能会导致应用程序无法响应DNS变化,因此在生产环境中应谨慎使用。
  • 缓存策略的设置应该根据应用程序的实际需求和网络环境来决定,以确保既高效又安全。
  • 在设置DNS缓存策略时,还应考虑无效DNS缓存时间的设置,即当DNS解析失败时的缓存时间,默认情况下这个时间为10秒。

总结

  1. 创建对象,大量使用反射的原因?
    灵活