ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Java连接MySQL:mysql-connector-java驱动配置与排错

Java连接MySQL:mysql-connector-java驱动配置与排错 1. 项目背景与整体思路1.1 这个项目到底要解决什么问题我在日常工作里看过太多人栽在Java连MySQL这个看似基础的事情上。明明SQL写得很溜Java语法也过关结果一到写数据库连接代码就各种报错——ClassNotFoundException、Communications link failure、Time zone不认识、Public Key Retrieval is not allowed……每次看到这些报错截图我都能猜到八成是mysql-connector-java这个驱动没弄明白。这篇内容想跟你聊透一件事怎么用mysql-connector-java把Java程序和MySQL数据库稳稳地连起来。不管你是在做课程设计、毕业设计还是写企业级Spring Boot项目这一步都是绕不开的地基。技术本身不复杂但里面有几个不可见的坑——驱动版本、URL参数、认证协议——踩中一个就能卡你半天。我把这些年实际项目里趟过的路整理出来从零开始把连接过程一步步拆给你看。适合谁看刚学JDBC拿不到连接的新手以及写了几年代码但一直靠复制粘贴能跑就行、没搞懂参数含义的工程师。看完你不仅能跑通还能在别人报错的时候一眼看出问题在哪。1.2 JDBC连接的基本流程Java连MySQL的核心机制是JDBCJava Database Connectivity你可以把它理解成Java官方给各种数据库定的一套插卡接口。MySQL厂商负责把这张卡也就是mysql-connector-java驱动做好Java程序只要对着JDBC接口写代码就行不用关心MySQL内部怎么通信。一次完整的连接流程是固定的五步加载驱动、获取连接、创建语句对象、执行SQL拿到结果、关闭资源。听起来简单但每一步都有讲究。比如加载驱动这一步在JDBC 4.0之后其实可以省略Class.forName但很多人不知道再比如获取连接这一步URL里那些参数每个是什么含义、为什么有的地方写serverTimezone有的地方写useSSL这些都是有实际原因而不是随手加的。先从最让人头疼的环境和驱动版本说起吧因为这个环节出错率最高。2. 环境准备与驱动选型2.1 JDK与MySQL版本搭配很多人上来就敲代码但没确认环境导致后面排查半天发现是版本打架。你至少需要JDK 8以上——虽然JDK 8已经很老了但大多数生产项目还在用mysql-connector-java 8.x对JDK 8的支持很成熟。MySQL数据库这边常见的是5.7和8.0官方在维护路线上有个明显的分界5.7是老一代稳定版8.0是新一代两者的认证方式有差异后面我会专门讲这个坑。版本搭配上我直接给你一个稳妥的组合JDK 8 / 11 / 17 MySQL 5.7.x mysql-connector-java 5.1.49JDK 8 / 11 / 17 MySQL 8.0.x mysql-connector-java 8.0.33如果你用的是MySQL 8.0却拿了5.1的驱动大概率会遇到认证协议不兼容的报错反过来MySQL 5.7配8.x驱动倒是通常能跑但没必要给自己找这种麻烦。选Agent版本的原则就一条让驱动版本和数据库大版本保持一致别混搭。2.2 mysql-connector-java驱动的获取方式驱动本质上就是一个jar包。获取方式有两种看你项目类型。第一种Maven项目直接引依赖。这是现在的主流做法。在pom.xml里加dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId version8.0.33/version /dependency加完之后Maven会自动把jar包拉到本地仓库你不需要手动找下载地址。这里有个细节如果你用的是Maven Central的最新版本可以到官网查一下版本号8.0.33是我用的比较多的稳定版再往后也有更新的但核心用法不变。第二种非Maven项目手动导入jar包。去MySQL官网的下载页找Connector/J下载Platform Independent的zip包解压得到mysql-connector-java-x.x.x.jar然后在IDE里右键项目 → Build Path → Add External JARs或者把它丢进项目的lib目录。我遇到过不少人卡在这里手动导入了jar包但运行时还是报ClassNotFoundException。原因多半是——他把jar包只加到了编译路径没加到运行路径。如果是用命令行运行编译和运行都要带-classpath参数如果是在IDEA/Eclipse里务必检查项目结构里的依赖是不是生效状态。最简单粗暴的验证方法写一行Class.forName(com.mysql.cj.jdbc.Driver)能跑通就说明jar包真的加载到了。2.3 驱动类名与URL格式的演进这一节是很多老教程和新手产生混乱的根源。在mysql-connector-java 5.x时代驱动类名是com.mysql.jdbc.Driver到了8.x时代驱动类名变成了com.mysql.cj.jdbc.Driver注意5.x那个类名在8.x驱动里已经被移除。网上很多教程还在用com.mysql.jdbc.Driver你如果复制到8.x驱动的项目里就会收到ClassNotFoundException。这个坑我见过太多次了很多培训机构的教程到现在都没更新。对应的连接URL格式也不一样5.x常用的String url jdbc:mysql://localhost:3306/demo?useUnicodetruecharacterEncodingutf8;8.x推荐带更多参数String url jdbc:mysql://localhost:3306/demo?useSSLfalseserverTimezoneUTCallowPublicKeyRetrievaltruecharacterEncodingutf8;URL的基本结构是jdbc:mysql://主机:端口/数据库名后面跟问号拼参数。localhost是MySQL装在本机3306是MySQL默认端口demo换成你自己的数据库名就行。3. 核心代码实现与参数详解3.1 最基础的连接代码先给你一个能直接跑通的最小Demo我尽量写干净注意看资源关闭的方式import java.sql.Connection; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.Statement; public class JdbcDemo { public static void main(String[] args) { String driver com.mysql.cj.jdbc.Driver; String url jdbc:mysql://localhost:3306/demo?useSSLfalseserverTimezoneUTCallowPublicKeyRetrievaltruecharacterEncodingutf8; String user root; String password 123456; Connection conn null; Statement stmt null; ResultSet rs null; try { Class.forName(driver); conn DriverManager.getConnection(url, user, password); System.out.println(数据库连接成功); stmt conn.createStatement(); rs stmt.executeQuery(SELECT id, name FROM user); while (rs.next()) { System.out.println(rs.getInt(id) - rs.getString(name)); } } catch (Exception e) { e.printStackTrace(); } finally { // 关闭顺序后打开的先关闭 if (rs ! null) { try { rs.close(); } catch (Exception e) { } } if (stmt ! null) { try { stmt.close(); } catch (Exception e) { } } if (conn ! null) { try { conn.close(); } catch (Exception e) { } } } } }这段代码里有几个点我要特意强调。第一Class.forName(driver)这一行在JDBC 4.0之后其实可以省略因为驱动jar包里带了一个java.sql.Driver的SPI配置文件DriverManager能自动发现驱动。但即使是这样我建议你还是留着这一行。为什么因为在实际开发中显式声明驱动类是让别人读代码时一眼能看懂我们要连的是MySQL而且有些老旧容器的类加载机制确实需要这个动作。这是一个便宜的保险。第二关闭资源的顺序必须是ResultSet → Statement → Connection后打开的先关。JDK 7之后可以用try-with-resources自动关闭代码会清爽很多try (Connection conn DriverManager.getConnection(url, user, password); Statement stmt conn.createStatement(); ResultSet rs stmt.executeQuery(SELECT id, name FROM user)) { while (rs.next()) { System.out.println(rs.getInt(id) - rs.getString(name)); } } catch (Exception e) { e.printStackTrace(); }我强烈建议你用后面这种写法它能省掉一大坨finally代码而且保证每个资源都被正确关闭。数据库连接是宝贵的系统资源连接泄漏是线上事故最常见的元凶之一这个习惯越早养成越好。3.2 连接参数逐项解释我见过很多人连接URL里的参数是照抄的抄对了能跑抄错了报错也不知道为什么。这里我把常用参数逐个说明白。参数名作用典型案例useSSL是否启用SSL加密连接。本地开发环境MySQL通常没配SSL证书设为false避免警告和握手失败useSSLfalseserverTimezone指定服务器时区。MySQL 8.0之后的驱动要求必须显式指定否则报不识别GMT8之类的错误serverTimezoneUTC 或 serverTimezoneAsia/ShanghaiallowPublicKeyRetrieval是否允许客户端自动获取服务器RSA公钥。MySQL 8.0默认认证插件是caching_sha2_password不设这个参数时可能报Public Key Retrieval is not allowedallowPublicKeyRetrievaltruecharacterEncoding指定字符编码解决中文乱码characterEncodingutf8useUnicode是否使用Unicode字符集通常配合characterEncoding一起用useUnicodetrueautoReconnect连接断了是否自动重连。注意这个参数只对空闲连接有效不是万能药autoReconnecttrueserverTimezone这个参数是新手重灾区。如果你在中国建议设成Asia/Shanghai因为有的环境把系统时间设成了本地时间而UTC和东八区差8个小时可能导致时间数据错乱。UTC是种不会错但可能绕弯的写法Asia/Shanghai是更符合我们实际场景的写法。还有一点如果你用的是分隔多个参数在properties文件里写连接串时要把改成amp;这是XML转义规则很多人在Spring的配置文件里栽过这个跟头。3.3 从配置文件读取连接信息写死连接信息在Demo里没问题但真实项目里绝不能这么干。把连接信息外置到配置文件是三年前就该养成的习惯。创建一个db.properties文件jdbc.drivercom.mysql.cj.jdbc.Driver jdbc.urljdbc:mysql://localhost:3306/demo?useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltruecharacterEncodingutf8 jdbc.userroot jdbc.password123456用Properties类加载import java.io.InputStream; import java.util.Properties; public class DbConfig { public static Connection getConnection() throws Exception { Properties props new Properties(); try (InputStream in DbConfig.class.getClassLoader() .getResourceAsStream(db.properties)) { props.load(in); } Class.forName(props.getProperty(jdbc.driver)); return DriverManager.getConnection( props.getProperty(jdbc.url), props.getProperty(jdbc.user), props.getProperty(jdbc.password) ); } }这样做的价值在于改数据库密码、换库、上生产环境只需要改配置文件不用动代码。我见过太多新手把连接信息埋在代码里后来数据库密码一改就得上线整个项目那场面真的灾难。4. 实操过程从零跑通第一个连接4.1 数据库准备代码写完之后你需要一个真实的数据库环境来验证。我建议你亲手创建一个测试库别偷懒直接用别人的。在MySQL命令行里执行CREATE DATABASE demo DEFAULT CHARACTER SET utf8mb4; USE demo; CREATE TABLE user ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) NOT NULL, email VARCHAR(100) ); INSERT INTO user(name, email) VALUES (张三, zhangsanexample.com); INSERT INTO user(name, email) VALUES (李四, lisiexample.com);这里我故意把数据库字符集设成utf8mb4而不是utf8。原因很简单MySQL的utf8实际上是utf8mb3只支持基本多文种平面存emoji或者某些生僻汉字会直接失败或者乱码。utf8mb4才是真正的完整UTF-8现在的主流项目默认都应该用它。创建完数据库之后你可以先用MySQL自带的命令行工具验证一下数据正常SELECT * FROM user;如果这步都查不出来说明问题在MySQL本身不在你的Java代码先别急着调试连接把数据库搞正常再说。这个排查思路很重要——很多人在Java报错之后疯狂改代码却忘了先确认最基本的事实。4.2 用Maven还是手动导入jar包我给个务实建议如果你项目已经用了Maven直接用前面的依赖坐标不用想别的如果你是个纯Java文件就用命令行配合classpath的方式跑一遍这样能逼你理解jar包加载的本质。命令行手动跑的步骤如下# 假设目录结构如下 # /test-jdbc # ├── JdbcDemo.java # └── lib/ # └── mysql-connector-java-8.0.33.jar # 编译 javac -encoding UTF-8 -cp lib/mysql-connector-java-8.0.33.jar JdbcDemo.java # 运行注意运行时的classpath包含当前目录和lib下的jar包 java -cp .;lib/mysql-connector-java-8.0.33.jar JdbcDemoWindows下classpath分隔符是分号;Linux/Mac下是冒号:千万别搞混。我说实话很多人用IDEA用得太顺了反而对这种最底层的东西没有概念一旦脱离IDE就懵了。掌握这个基础操作对你理解ClassNotFoundException到底意味着什么很有帮助。4.3 完整Demo与验证如果你跟着前面走程序跑起来应该会输出数据库连接成功 1 - 张三 2 - 李四看到这个输出你的Java mysql-connector-java连接MySQL算是真正跑通了。接下来我强烈建议你做一个小实验把SQL改成带参数的查询换成PreparedStatementtry (Connection conn DbConfig.getConnection(); PreparedStatement ps conn.prepareStatement(SELECT id, name FROM user WHERE id ?)) { ps.setInt(1, 1); try (ResultSet rs ps.executeQuery()) { while (rs.next()) { System.out.println(rs.getString(name)); } } }为什么强调用PreparedStatement而不是Statement拼接字符串两个理由第一性能上同一个SQL先预编译再多次执行能复用比每次拼字符串强得多第二安全上直接拼接用户输入会产生SQL注入风险?占位符能把参数和SQL语句分开从根上杜绝注入。这不是什么高端技巧是每个写数据库代码的人都应该刻在脑子里的底线。5. 常见问题与排查技巧5.1 问题速查表我把实际项目里出现频率最高的几个报错整理成了一张速查表方便你遇到问题直接对上号。报错关键字问题本质快速解决方案ClassNotFoundException: com.mysql.cj.jdbc.Driver驱动jar包没加载到运行classpath检查依赖/导入的jar包是否在运行路径里ClassNotFoundException: com.mysql.jdbc.Driver驱动类名写错或用了旧教程的类名8.x驱动改类名为com.mysql.cj.jdbc.DriverCommunications link failure网络不通或MySQL没启动/端口不对先ping 3306端口确认服务启动Access denied for user rootlocalhost用户名或密码错误确认MySQL账号密码检查权限Unknown database xxx数据库名不对或没创建CREATE DATABASE确认库名拼写The server time zone value is unrecognized时区未指定URL加上serverTimezoneAsia/ShanghaiPublic Key Retrieval is not allowedMySQL 8.0认证插件导致URL加上allowPublicKeyRetrievaltrueClient does not support authentication protocol驱动版本和MySQL认证协议不匹配升级mysql-connector-java到8.x这张表不是给你背的是给你排查时定位方向的。真实的报错往往很长但关键信息就在最上面的几行你学会抓关键字思路就清晰了。5.2 典型报错分析与解决我挑三个典型问题展开讲这些是我在给别人debug时最常看到的。第一个Communications link failure。这个报错特别有迷惑性因为它后面跟着一大长串英文看着像天书但本质就一句话客户端没能和MySQL服务器建立TCP连接。排查步骤我建议按顺序来确认MySQL真的启动了。Windows下看服务列表有没有MySQL服务Linux下执行systemctl status mysqld或者用netstat -an | grep 3306看端口有没有监听。确认端口对不对。默认3306但有些人装环境时改过端口你的URL要跟着改。确认是不是防火墙拦截了3306。本地测试建议先关防火墙试一次如果关了就好了那就是防火墙规则问题。确认连接串的主机名是不是写对了。远程连接要写IP别写localhost而且MySQL要授权远程访问才行。我之前帮一个同学排查他把URL写成了jdbc:mysql://192.168.1.100:3306/demo但MySQL的bind-address配置只允许本机连接连不上。这种问题不在Java代码层面在于MySQL服务本身的网络配置你得跳出代码去查环境。第二个Public Key Retrieval is not allowed。这是MySQL 8.0用户特有的坑。MySQL 8.0默认的认证插件是caching_sha2_password客户端要用RSA公钥加密传输密码。如果没有配置SSL这个公钥需要客户端主动去服务器获取而驱动默认不允许这个动作所以就报了上面的错。解决方案在URL上加上allowPublicKeyRetrievaltrue。我知道有些资料会对这个参数的安全性有顾虑但在本地开发和测试环境这个参数完全可以接受前提是你自己知道在干什么。生产环境建议走SSL或者用更严格的认证配置而不是无脑开这个参数。第三个The server time zone value is unrecognized。这个报错的根因是MySQL的system_time_zone参数值是CST这类缩写而驱动无法把CST映射到标准时区。解决方案我在前面已经说了URL里加serverTimezoneAsia/Shanghai这里不再重复但我想额外提醒一句如果你用Spring Boot连接MySQL别忘了spring.datasource.url里面同样要带全这些参数。Spring Boot不会帮你自动补全少了该报错还是报错。5.3 连接池与最佳实践跑通单连接之后你还应该在脑子里建立连接池的概念因为真实项目里根本不会用DriverManager.getConnection来获取连接。一个最朴素的理解数据库连接从创建到销毁的开销很大每次查询都新建连接、用完再销毁性能极其拉垮。连接池的做法是预先创建一批连接放在池子里程序要用的时候借走一个用完还回来。这样连接就被复用开销大幅度下降。Java生态里常用的连接池有HikariCPSpring Boot 2.x之后默认选它、Druid阿里开源的自带监控功能、dbcp和c3p0比较老。我给新手的建议是如果你用Spring Boot直接用默认的HikariCP如果你需要在页面上看SQL执行情况、监控慢查询选Druid。连接池的配置里有一组核心参数maximumPoolSize最大连接数、minimumIdle最小空闲数、connectionTimeout获取连接超时时间。很多团队会犯同一个错误——把maximumPoolSize配得很大动辄200、500以为这样性能好。实际上连接数是把双刃剑过多连接会耗尽MySQL的线程资源和内存反而拖慢整体。经验做法是从1020起步结合压测数据上调而不是上来就拍一个很大的数。最后一个忠告在生产环境不要把数据库密码写进代码或者配置文件明文存储至少要用环境变量或者配置中心比如Nacos做统一管理。这是我每次做项目复盘都会跟团队强调的点因为数据库密码泄露导致的事故往往都是从一个不起眼的配置文件开始的。我个人这几年做Java后端项目几乎每天都要和mysql-connector-java打交道。从最早的5.1版本一路用到8.x最大的感受是这个驱动本身并不是什么高深的东西难点在于版本演进带来的细节变化以及很多人对底层原理的一知半解。你只要把驱动类名、URL参数、认证方式这三件事搞明白Java连MySQL对你来说就不再是玄学。如果你手头正被某个连接问题卡住拿着上面的速查表对照一遍基本能找到方向。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进