Java连接MySQL数据库

Java连接MySQL数据库

<!-- 必须:MySQL驱动 -->
<!-- mysql-connector-java 是 MySQL 官方(现 Oracle)提供的 JDBC 驱动,不是 Java 标准库的一部分,但被广泛认可为连接 MySQL 的标准方式。 -->
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.37</version>
    <!-- <version>8.0.33</version> -->
</dependency>

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 的这种设计带来了几个优势:

  1. 性能更好:避免驱动遍历的开销
  2. 确定性更强:明确知道使用哪个驱动,避免驱动冲突
  3. 配置更灵活:可以强制指定使用特定驱动版本
  4. 问题排查更容易:驱动选择逻辑清晰
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<DruidPooledConnection, Object> activeConnections = new IdentityHashMap<>();

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加密功能。

posted @ 2025-11-28 14:16  deyang  阅读(139)  评论(0)    收藏  举报