目录Java连接MySQL数据库Java数据库连接方式对比选择建议连接示例驱动选择核心区别总结1. JDBC 原生的驱动选择机制2. Druid 的驱动选择机制3. JdbcUtils.getDriverClassName() 的工作原理4. 性能对比5. 配置方式的灵活性6. 实际执行流程对比7. 优势分析8. 实际验证代码总结连接过程连接后JDBC VS DruidJDBC连接后直接返回Druid连接后放入连接池MySQL 协议协议特点对比MySQL 5.7 vs 8.0 主要区别对比MySQL 驱动主要变化连接示例对比版本匹配建议:MySQL 8.0 SSL 验证机制SSL验证的几种模式为什么驱动能"验证通过"?生产环境正确做法
Java连接MySQL数据库
Java数据库连接方式对比
性能:HikariCP > Druid > DBCP2 > C3P0 > JDBC原生。除了JDBC原生无连接池,其他都有连接池。
连接方式
类型
必需依赖
优点
缺点
生产推荐
JDBC原生
无连接池
mysql-connector-java
简单易用、无额外依赖
性能差、无连接复用
❌
HikariCP
连接池
mysql-connector-java HikariCP
性能最优、轻量快速
监控功能相对简单
✅
Druid
连接池
mysql-connector-java druid
监控功能强大、防SQL注入
配置相对复杂
✅
DBCP2
连接池
mysql-connector-java commons-dbcp2
稳定成熟、Apache项目
性能中等、配置复杂
⚠️
C3P0
连接池
mysql-connector-java c3p0
历史悠久、稳定性好
性能最差、已过时
❌
选择建议
新项目: 首选 HikariCP (性能) 或 Druid (监控)
学习测试: 使用 JDBC原生
老项目: 维持现有连接池或迁移到HikariCP
所有方式都必须导入 mysql-connector-java 驱动
连接示例
JdbcExample
public class JdbcExample {
public static void main(String[] args) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
// 1. 加载数据库驱动
// Class.forName("com.mysql.cj.jdbc.Driver");
// 2. 建立连接
String url = "jdbc:mysql://localhost:3306/test";
String username = "root";
String password = "root";
conn = DriverManager.getConnection(url, username, password);
// 3. 创建Statement对象
stmt = conn.createStatement();
// 4. 执行SQL查询
String sql = "SELECT * FROM users";
rs = stmt.executeQuery(sql);
// 5. 处理结果集
while (rs.next()) {
int id = rs.getInt("id");
String name = rs.getString("name");
String email = rs.getString("email");
System.out.println("ID: " + id + ", Name: " + name + ", Email: " + email);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 6. 关闭资源
try {
if (rs != null) rs.close();
if (stmt != null) stmt.close();
if (conn != null) conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
DruidExample
public class DruidExample {
private static DruidDataSource dataSource;
static {
// 初始化数据源
dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/test?useSSL=false&serverTimezone=UTC");
dataSource.setUsername("root");
dataSource.setPassword("root");
// 配置连接池参数
dataSource.setInitialSize(5); // 初始连接数
dataSource.setMinIdle(5); // 最小空闲连接
dataSource.setMaxActive(20); // 最大连接数
dataSource.setMaxWait(60000); // 获取连接超时时间
dataSource.setTimeBetweenEvictionRunsMillis(60000); // 检查间隔
dataSource.setMinEvictableIdleTimeMillis(300000); // 最小生存时间
// DruidDataSourceFactory.createDataSource(props);
}
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
public static void main(String[] args) {
try (Connection conn = getConnection()) {
String sql = "SELECT * FROM users WHERE id = ?";
try (PreparedStatement p = conn.prepareStatement(sql)) {
p.setInt(1, 1);
try (ResultSet rs = p.executeQuery()) {
while (rs.next()) {
System.out.println("ID: " + rs.getInt("id"));
System.out.println("Name: " + rs.getString("name"));
System.out.println("Email: " + rs.getString("email"));
}
}
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
驱动选择
// JDBC原生原生的 conn = DriverManager.getConnection(url, username, password);是从已注册的drive里面顺序建立连接,连接成功就直接返回使用
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
// Druid 是通过配置driverClass或者this.jdbcUrl配置选择已经注册的driver
// DruidDataSource类里的方法
protected void resolveDriver() throws SQLException {
if (this.driver != null) {
if (this.driverClass == null) {
this.driverClass = this.driver.getClass().getName();
}
} else {
if (this.driverClass == null || this.driverClass.isEmpty()) {
this.driverClass = JdbcUtils.getDriverClassName(this.jdbcUrl);
}
if (MockDriver.class.getName().equals(this.driverClass)) {
this.driver = MockDriver.instance;
} else if ("com.alibaba.druid.support.clickhouse.BalancedClickhouseDriver".equals(this.driverClass)) {
Properties info = new Properties();
info.put("user", this.username);
info.put("password", this.password);
info.putAll(this.connectProperties);
this.driver = new BalancedClickhouseDriver(this.jdbcUrl, info);
} else {
if (this.jdbcUrl == null && (this.driverClass == null || this.driverClass.length() == 0)) {
throw new SQLException("url not set");
}
this.driver = JdbcUtils.createDriver(this.driverClassLoader, this.driverClass);
}
}
}
核心区别总结
1. JDBC 原生的驱动选择机制
// DriverManager.getConnection() 的工作方式
for(DriverInfo aDriver : registeredDrivers) {
// 遍历所有已注册的驱动,顺序尝试
Connection con = aDriver.driver.connect(url, info);
if (con != null) { // 第一个能识别该URL的驱动返回非null连接,就使用它
return con;
}
}
特点:
顺序尝试:按驱动注册顺序逐个尝试
第一个匹配原则:第一个能识别 URL 的驱动获胜
性能开销:可能需要多次尝试才能找到正确的驱动
2. Druid 的驱动选择机制
// DruidDataSource.resolveDriver() 的工作方式
protected void resolveDriver() throws SQLException {
if (this.driverClass == null || this.driverClass.isEmpty()) {
// 关键:通过URL分析直接确定驱动类名
this.driverClass = JdbcUtils.getDriverClassName(this.jdbcUrl);
}
// 直接创建指定驱动类的实例
this.driver = JdbcUtils.createDriver(this.driverClassLoader, this.driverClass);
}
特点:
直接定位:通过 URL 分析直接确定需要的驱动类
精确创建:直接实例化特定驱动,无需遍历尝试
性能优化:避免不必要的驱动尝试
3. JdbcUtils.getDriverClassName() 的工作原理
这个方法通过 URL 前缀直接映射到驱动类:
// JdbcUtils.getDriverClassName() 简化逻辑
public static String getDriverClassName(String url) {
if (url.startsWith("jdbc:derby:")) {
return "org.apache.derby.jdbc.EmbeddedDriver";
} else if (url.startsWith("jdbc:mysql:")) {
return "com.mysql.cj.jdbc.Driver"; // 直接返回MySQL驱动类
} else if (url.startsWith("jdbc:oracle:")) {
return "oracle.jdbc.OracleDriver";
} else if (url.startsWith("jdbc:microsoft:")) {
return "com.microsoft.jdbc.sqlserver.SQLServerDriver";
}
// ... 其他数据库映射
}
4. 性能对比
原生 JDBC:
尝试驱动A → 失败
尝试驱动B → 失败
尝试驱动C → 成功 → 返回连接
Druid:
分析URL → 确定需要驱动C → 直接创建驱动C实例 → 返回连接
5. 配置方式的灵活性
Druid 提供了多种驱动指定方式:
// 方式1:通过URL自动推断(最常用)
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
// 方式2:显式指定驱动类名
dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
// 方式3:直接设置驱动实例
dataSource.setDriver(DriverManager.getDriver("jdbc:mysql://localhost:3306/test"));
6. 实际执行流程对比
原生 JDBC:
Connection conn = DriverManager.getConnection(url, user, password);
// ↑ 内部遍历所有注册的驱动,找到能处理该URL的第一个驱动
Druid:
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
Connection conn = dataSource.getConnection();
// ↑ 内部:resolveDriver() → 直接创建MySQL驱动实例 → createPhysicalConnection()
7. 优势分析
Druid 的这种设计带来了几个优势:
性能更好:避免驱动遍历的开销
确定性更强:明确知道使用哪个驱动,避免驱动冲突
配置更灵活:可以强制指定使用特定驱动版本
问题排查更容易:驱动选择逻辑清晰
8. 实际验证代码
public class DriverSelectionTest {
public static void main(String[] args) throws SQLException {
// 测试原生JDBC的驱动选择
System.out.println("=== 原生JDBC驱动选择 ===");
Connection nativeConn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test", "root", "password");
System.out.println("原生连接类: " + nativeConn.getClass().getName());
nativeConn.close();
// 测试Druid的驱动选择
System.out.println("=== Druid驱动选择 ===");
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mysql://localhost:3306/test");
dataSource.setUsername("root");
dataSource.setPassword("password");
Connection druidConn = dataSource.getConnection();
System.out.println("Druid连接类: " + druidConn.getClass().getName());
System.out.println("Druid使用的驱动类: " + dataSource.getDriver().getClass().getName());
druidConn.close();
dataSource.close();
}
}
总结
Druid 通过主动解析 URL 并直接创建特定驱动实例,避免了原生 JDBC 的顺序遍历机制,这在性能、确定性和可配置性方面都带来了优势。这是 Druid 在设计上的一个巧妙优化。
连接过程
SPI加载驱动 + 建立数据库连接
应用启动
↓
DriverManager初始化 (调用DriverManager的任何方法)
↓
ServiceLoader.load(Driver.class)
↓
扫描所有jar包的 META-INF/services/java.sql.Driver文件 # (💡SPI加载驱动)
↓
发现驱动类名:com.mysql.cj.jdbc.Driver
↓
通过反射加载驱动类
↓
执行驱动类的静态代码块
↓
DriverManager.registerDriver(new Driver()) ← # 驱动注册完成!
↓
驱动加载完成 ✅(内存中已有驱动实例)
↓
应用调用 DriverManager.getConnection()
↓
DriverManager根据URL选择合适的已注册驱动
↓
建立数据库连接 ✅
建立连接详细描述
# DriverManager.getConnection()详细描述
应用调用 DriverManager.getConnection()
↓
Driver.connect()
↓
ConnectionImpl.getInstance()
↓
new ConnectionImpl() 构造函数
↓
createNewIO()
↓
new MysqlIO() ← 关键类!
↓
MysqlIO构造函数中建立Socket连接
↓
this.socketFactory.connect(this.host, this.port, props)
↓
this.rawSocket.connect(sockAddr, this.getRealTimeout(connectTimeout)); # new Socket().connect(); // ← 最底层TCP连接!
↓
MySQL协议握手和认证
↓
建立数据库连接 ✅
连接后JDBC VS Druid
JDBC连接后直接返回
// JDBC原生的 conn = DriverManager.getConnection(url, username, password);是从已注册的drive里面顺序建立连接,连接成功就直接返回使用
for(DriverInfo aDriver : registeredDrivers) {
// If the caller does not have permission to load the driver then
// skip it.
if(isDriverAllowed(aDriver.driver, callerCL)) {
try {
println(" trying " + aDriver.driver.getClass().getName());
Connection con = aDriver.driver.connect(url, info);
if (con != null) {
// Success!
println("getConnection returning " + aDriver.driver.getClass().getName());
return (con);
}
} catch (SQLException ex) {
if (reason == null) {
reason = ex;
}
}
} else {
println(" skipping: " + aDriver.getClass().getName());
}
}
Druid连接后放入连接池
// 创建时:部分源码 DruidDataSource的init()方法
while(this.poolingCount < this.initialSize) {
try {
PhysicalConnectionInfo pyConnectInfo = this.createPhysicalConnection();
DruidConnectionHolder holder = new DruidConnectionHolder(this, pyConnectInfo);
this.connections[this.poolingCount++] = holder; // 创建好后放入connections
} catch (SQLException var18) {
LOG.error("init datasource error, url: " + this.getUrl(), var18);
if (this.initExceptionThrow) {
connectError = var18;
break;
}
Thread.sleep(3000L);
}
}
// 获取时:
// 部分源码 DruidDataSource的getConnectionInternal(long maxWait)方法
DruidConnectionHolder holder;
if (maxWait > 0L) {
holder = this.pollLast(nanos); // 从connections拿一个,connections会少一个
} else {
holder = this.takeLast(); // 从connections拿一个,connections会少一个
}
....
holder.incrementUseCount();
DruidPooledConnection poolalbeConnection = new DruidPooledConnection(holder);
return poolalbeConnection;
// 部分源码 DruidDataSource的getConnectionDirect(long maxWaitMillis)方法
if (this.removeAbandoned) {
StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
poolableConnection.connectStackTrace = stackTrace;
poolableConnection.setConnectedTimeNano();
poolableConnection.traceEnable = true;
this.activeConnectionLock.lock();
try {
this.activeConnections.put(poolableConnection, PRESENT); // 放入activeConnections
} finally {
this.activeConnectionLock.unlock();
}
}
// 空闲连接(可立即使用的连接)
private volatile DruidConnectionHolder[] connections;
// 活跃连接(正在被应用使用的连接)
private final Map
MySQL 协议
JDBC MySQL 驱动其实是基于 TCP 协议,类似于 HTTP 也是基于 TCP 的协议。
当使用 jdbc:mysql://localhost:3306/mydb 时:
// JDBC 驱动内部实际上建立的是 TCP 连接
Socket socket = new Socket("localhost", 3306); // MySQL 默认端口 3306
协议特点对比
特性
HTTP 协议
MySQL 协议
基于
TCP
TCP
默认端口
80/443
3306
协议格式
文本协议
二进制协议
连接方式
短连接/长连接
长连接
认证机制
Basic Auth/OAuth 等
MySQL 原生认证
可以通过网络工具观察到:
# 查看 MySQL 连接
netstat -an | grep 3306
# 输出:tcp4 0 0 192.168.1.100.54321 192.168.1.200.3306 ESTABLISHED
MySQL 5.7 vs 8.0 主要区别对比
特性类别
MySQL 5.7
MySQL 8.0
说明
性能优化
基础优化
窗口函数、CTE、直方图
8.0查询性能大幅提升
JSON支持
基础JSON功能
JSON增强、路径表达式
8.0的JSON处理更强大
字符集
默认latin1
默认utf8mb4
8.0更好支持中文和emoji
数据字典
基于文件
事务性数据字典
8.0更稳定可靠
窗口函数
不支持
支持
8.0支持高级分析函数
CTE
不支持
支持递归和非递归CTE
8.0支持复杂查询
索引
普通索引
隐藏索引、降序索引
8.0索引功能更丰富
安全性
基础安全
角色管理、密码策略增强
8.0安全性更高
复制功能
传统复制
增强型复制、组复制
8.0高可用性更好
版本支持
已停止官方支持
官方持续维护
建议使用8.0
总结:MySQL 8.0在性能、功能和安全性方面都有显著提升,是新项目的首选版本。
MySQL 驱动主要变化
驱动方面
MySQL 5.7
MySQL 8.0
关键影响
默认驱动
mysql-connector-java 5.x
mysql-connector-java 8.x
版本不兼容
驱动类名
com.mysql.jdbc.Driver
com.mysql.cj.jdbc.Driver
必须更新
连接URL
jdbc:mysql://host:3306/db
需添加参数: ?serverTimezone=Asia/Shanghai&useSSL=false
时区必须配置
SSL处理
默认不强制SSL
默认要求SSL连接
需显式关闭或配置SSL
认证插件
mysql_native_password
caching_sha2_password
8.0默认新认证方式
时区要求
可选
必须指定serverTimezone
避免时区错误
连接示例对比
// MySQL 5.7
Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test", "user", "pass");
// MySQL 8.0
Class.forName("com.mysql.cj.jdbc.Driver");
Connection conn = DriverManager.getConnection(
"jdbc:mysql://localhost:3306/test?serverTimezone=Asia/Shanghai&useSSL=false&requireSSL=false",
"user", "pass");
注意:升级到MySQL 8.0时,应用程序的驱动配置必须相应更新。
MySQL服务和驱动不一致时,可能会有兼容性问题,最好同步更新!!!
版本匹配建议:
服务端版本
推荐驱动版本
驱动类名
MySQL 5.6
5.1.x
com.mysql.jdbc.Driver
MySQL 5.7
5.1.x 或 8.0.x
com.mysql.cj.jdbc.Driver
MySQL 8.0
8.0.x
com.mysql.cj.jdbc.Driver
MySQL 8.0 SSL 验证机制
SSL验证的几种模式
验证模式
配置参数
适用场景
证书要求
不验证
useSSL=false
开发环境
不需要证书
不验证证书
useSSL=true&verifyServerCertificate=false
内网环境
只需要加密,不验证身份
完全验证
useSSL=true&verifyServerCertificate=true
生产环境
需要CA证书
为什么驱动能"验证通过"?
// 常见的开发环境配置:
String url = "jdbc:mysql://localhost:3306/db?" +
"useSSL=true&" + // 启用SSL加密
"verifyServerCertificate=false&" + // 关键:不验证证书有效性
"requireSSL=true";
// 这样配置时:
// 1. 连接会使用SSL加密
// 2. 但不会验证服务器证书是否可信
// 3. 即使使用自签名证书也能连接成功
生产环境正确做法
// 1. 获取服务器的CA证书
// 2. 在客户端配置信任库
// 3. 启用完整验证
String url = "jdbc:mysql://localhost:3306/db?" +
"useSSL=true&" +
"verifyServerCertificate=true&" +
"requireSSL=true&" +
"trustCertificateKeyStoreUrl=file:/path/to/truststore&" +
"trustCertificateKeyStorePassword=password";
开发环境中通常通过 verifyServerCertificate=false 来绕过证书验证,只使用SSL加密功能。