刚帮同事排查了一个“升级到 MySQL 8.0 之后应用突然连不上”的问题,最后发现他 lib 目录里躺着一个 5.1.49 的旧驱动——项目明明已经换了 MySQL 8.0,驱动还停在五六年前,认证插件和连接协议全都不匹配,不报错才怪。MySQL 8.0 版本的 JDBC 驱动 Jar 包,听起来就是“把整个 Jar 下载下来放进工程里”这么简单,实际用起来却藏了不少细节:驱动坐标换了、驱动类名变了、默认认证插件变了、连接 URL 的参数要求也变了,任何一个地方写错,表现都是连接失败或者直接抛异常。
这篇文章就把 MySQL 8.0 对应的 JDBC 驱动 Jar 包从下载、选版、集成到连接配置、打包、排错一次性捋清楚。适合正在从 MySQL 5.7 往 8.0 迁移的 Java 开发,也适合被“Connector/J 版本不兼容”“Public Key Retrieval is not allowed”“download from maven failed”这些问题折磨到怀疑人生的新手。
1. 驱动 Jar 包的版本演变与选型思路
1.1 MySQL 8.0 驱动跟 5.x 驱动到底差在哪
很多人以为“JDBC 驱动只是个连接工具,新旧无所谓”,这就是最大的误区。MySQL 8.0 相比 5.x 在服务端做了很多底层变化,最典型的就是默认认证插件从mysql_native_password换成了caching_sha2_password,同时原生传输协议也迎来了一次大更新。
老驱动(比如 5.1.49)在设计时根本没有考虑这些新特性,所以连接 MySQL 8.0 时会出现两种典型情况:一种是服务端要求使用caching_sha2_password认证,而老驱动连这个认证插件都不认识,直接抛出Unable to load authentication plugin 'caching_sha2_password';另一种是虽然能连接,但因为协议兼容层是硬适配的,偶发握手失败、字符集错乱、时区异常等问题。
从官方命名上也能看出变化。MySQL 5.x 时代的驱动坐标是mysql:mysql-connector-java,而 8.0 系列从 8.0.31 左右开始,官方把构件名改成了com.mysql:mysql-connector-j,后面也叫 MySQL Connector/J。虽然旧坐标在 8.0 版本里继续发布了一段时间,但新项目建议直接采用新坐标,省得以后迁移时踩坑。
驱动类名也要特别注意。老驱动写的是:
Class.forName("com.mysql.jdbc.Driver");8.0 的驱动类名换成了:
Class.forName("com.mysql.cj.jdbc.Driver");这里注意,com.mysql.jdbc.Driver这个类在 8.0 驱动里仍然存在,但已经被标记为过时,本质是转调com.mysql.cj.jdbc.Driver。代码里写旧类名编译不会报错,但 IDE 会提示 deprecated,而且即使能跑通,也只是兼容性兜底,对于新项目没有理由继续用旧名字。
1.2 版本怎么选才稳
版本选择看起来是“选最新就行”,但实际项目里更合理的策略是“与 MySQL 服务端大版本保持一致,小版本选稳定的补丁版”。
比如数据库是 MySQL 8.0.28,那驱动选8.0.33、8.0.34这类 8.0 系列补丁版都可以。因为驱动和服务端的兼容性是向下看的,8.0 驱动既能连 8.0,也能连 5.7 和 5.6,但 5.x 驱动很难正常连 8.0。
JDK 版本也是一个约束。MySQL Connector/J 8.0 要求 JDK 8 及以上,如果你的运行环境还是 JDK 6/7,那只能选旧版驱动。反过来,如果你的 JDK 很新(比如 JDK 17),那 8.0 系列的驱动基本都能正常工作,因为官方会持续适配。
| 场景 | 推荐驱动版本 | 说明 |
|---|---|---|
| MySQL 8.0 + JDK 8+ | mysql-connector-j 8.0.x 补丁版 | 首选方案,认证、时区、SSL 都支持 |
| MySQL 5.7 + JDK 8+ | mysql-connector-j 8.0.x | 8.0 驱动向下兼容 5.7 |
| 遗留 JDK 6/7 项目 | mysql-connector-java 5.1.49 | 老环境不得已选择,不推荐新项目 |
| 教学环境/单机快速跑通 | mysql-connector-j 8.0.33 | 常见稳定版本,网上资料多 |
还有一个经验:不要看到有新版本就把依赖无脑改成latest或者8.0.99这种通配写法。驱动这种基础组件,锁定具体版本号很重要,因为不同补丁版本对连接参数的默认值可能有微调,升级驱动是一个需要测试的动作,不是改一行配置就完事。
2. 获取正确 Jar 包的几种可靠渠道
2.1 官网下载 Platform Independent 压缩包
最权威的渠道是 MySQL 官网的 Connector/J 下载页。到 dev.mysql.com/downloads/connector/j/ 之后,选择 Platform Independent 选项,下载 zip 或 tar.gz 包。
解压之后,目录里会有一个mysql-connector-j-8.0.xx.jar,这就是核心驱动 Jar。压缩包里还会有源码包和文档,不需要的话直接忽略。注意下载的是 jar 而不是压缩包里那个特别大的mysql-connector-j-8.0.xx.jar同时还有可能同时存在protobuf-java之类的依赖文件——这里要说清楚:MySQL 8.0 驱动为了支持新的认证协议,内部确实引用了 protobuf 相关类,但官方发布的这个 jar 已经把这些依赖打包进去了,所以单文件复制出来直接用是可以的(在大多数标准使用场景下)。
官网下载适合“无法访问 Maven 仓库”或者“需要一个固定 Jar 文件离线拷贝到服务器”的情况。如果项目是 Maven/Gradle 管理依赖,我更推荐直接用仓库坐标,不要下载后手动丢进项目里,后面会解释为什么。
2.2 Maven 中央仓库与镜像坐标
Maven 项目里引入非常简单,在pom.xml加依赖:
<dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.33</version> </dependency>Gradle 项目对应:
implementation 'com.mysql:mysql-connector-j:8.0.33'但很多同学卡在“下载不下来”,IDEA 里报download from maven failed。这个问题的根源基本都在网络:IDEA 默认访问 Maven Central,可国内网络访问中央仓库经常超时或被重置。解决办法是配置阿里云镜像。
在 Maven 的settings.xml里添加 mirror:
<mirror> <id>aliyunmaven</id> <mirrorOf>*</mirrorOf> <name>阿里云公共仓库</name> <url>https://maven.aliyun.com/repository/public</url> </mirror>配置完镜像之后,IDEA 里还要注意:Settings -> Maven -> Repositories 里把对应的仓库 URL 勾选更新一下,然后重新导入项目。如果本地仓库路径存在权限问题,也会导致下载失败,检查一下本地仓库目录是否有写入权限。
还有一个常见场景是公司内部有私服,那就把settings.xml里的 mirror 指向私服地址,而不是阿里云。IDEA 报Download from Maven failed时,我会先去命令行执行:
mvn dependency:resolve -Dartifact=com.mysql:mysql-connector-j:8.0.33这个命令可以直接暴露底层的网络或仓库配置问题,比在 IDEA 里猜要高效得多。
2.3 IDEA 与 Eclipse 手工导入本地 Jar 包
有的项目就是不用 Maven/Gradle,比如教学环境、古早 web 项目,那就需要手工导入 Jar。
IDEA 里的操作路径:
- 把
mysql-connector-j-8.0.33.jar复制到项目根目录下一个叫lib的文件夹里(没有就新建)。 - 在项目上按
F4或者右键选择Open Module Settings。 - 切到
Libraries标签页,点+,选择Java,然后定位到那个 jar 文件。 - 确认之后,再切到
Modules->Dependencies,勾选这个 Library 为compile作用域。
Eclipse 类似:在项目上右键 ->Build Path->Configure Build Path->Add JARs(jar 在项目内)或者Add External JARs(jar 在外部路径),然后确认勾选。
手工导入最常见的问题是:换一台电脑或者换一个人 clone 代码后,lib目录不在版本控制里,导致驱动丢失。所以如果你是手工导入的维护方式,建议把lib目录提交到 Git 仓库,并在 README 里写清楚驱动版本,否则后面的人极容易用错版本。
2.4 离线环境与本地仓库手动安装
服务器或内网环境不能访问外网时,可以把 jar 下载好,手动安装进本地 Maven 仓库:
mvn install:install-file -Dfile=mysql-connector-j-8.0.33.jar -DgroupId=com.mysql -DartifactId=mysql-connector-j -Dversion=8.0.33 -Dpackaging=jar装完之后,项目的依赖坐标不用改,直接就能从本地仓库解析。这个方式比把 jar 硬塞进项目里更干净,因为你依然可以通过pom.xml管版本,而不是散落一堆资源文件。
3. 到代码里:驱动加载与连接配置
3.1 一个最小可用的连接示例
拿到 jar 之后,第一步就是验证能不能真的连上数据库。我习惯写一个只做连接测试的类,不掺业务逻辑:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class ConnTest { public static void main(String[] args) { String url = "jdbc:mysql://127.0.0.1:3306/test" + "?useSSL=false" + "&serverTimezone=Asia/Shanghai" + "&characterEncoding=utf8"; String user = "root"; String password = "your_password"; try (Connection conn = DriverManager.getConnection(url, user, password)) { System.out.println("连接成功:" + conn.getCatalog()); } catch (SQLException e) { e.printStackTrace(); } } }JDBC 4 之后,驱动 Jar 里有 SPI 配置文件(META-INF/services/java.sql.Driver),所以Class.forName("com.mysql.cj.jdbc.Driver")可以不写,DriverManager会自动发现驱动。但如果你在某种特殊容器里遇到No suitable driver found,那就在连接代码前显式写一次Class.forName("com.mysql.cj.jdbc.Driver"),问题通常能解决。
有一点要提醒:连接字符串里千万不要出现空格,尤其不要在参数之间加多余空格,&后面直接跟下一个参数名。很多新手在这里复制粘贴时丢了一个&或写错参数名,导致连接失败。
3.2 常用连接参数逐个拆解
很多人对一长串的连接 URL 心生畏惧,其实就是若干个key=value用&连起来。我把最常用的几个参数按“为什么必须关注”的优先级列一下。
| 参数 | 常见取值 | 作用与坑 |
|---|---|---|
serverTimezone | Asia/Shanghai、UTC | 8.0 驱动强制要求服务端时区信息,不设置会报The server time zone value错误。不要写CST,因为它在美国中部时间和中国标准时间之间是歧义的。 |
useSSL | true/false | 内网或本地开发建议显式false,省去证书配对的麻烦。生产环境按安全要求开启并配置证书。 |
allowPublicKeyRetrieval | true | 使用caching_sha2_password认证且未启用 SSL 时,需要设为true,否则报Public Key Retrieval is not allowed。 |
characterEncoding | utf8 | 解决中文乱码,与useUnicode=true一起用。Connector/J 8.0 稍智能一些,但显式声明更稳。 |
connectTimeout | 5000 | 单位毫秒,建立连接的超时时间,避免数据库不通时线程长期阻塞。 |
socketTimeout | 60000 | 单位毫秒,定义一次 SQL 执行中的网络读超时,防止慢 SQL 把线程拖死。 |
zeroDateTimeBehavior | CONVERT_TO_NULL | 表里存在0000-00-00 00:00:00这种日期时,8.0 驱动默认会抛异常,设置后转为null。 |
rewriteBatchedStatements | true | 批量操作时重写 SQL,配合executeBatch可以大幅提升性能。 |
为什么serverTimezone是 8.0 驱动里经常卡人的点?因为 MySQL 5.7 时代的驱动对时区要求宽松,当时很多人没写这个参数也一直能用;升级到 8.0 后,驱动对getDefaultTimeZone()和数据库时区之间的一致性检查变得严格,不显式指定就直接抛The server time zone value 'CST' is unrecognized之类的异常。所以不管项目之前怎样,到了 8.0 就在 URL 里老老实实带上serverTimezone=Asia/Shanghai。
3.3 连接池配置里的驱动属性
实际项目很少直接DriverManager.getConnection,基本都会用 HikariCP 或 Druid。这里最容易翻车的是配置了连接池,却没有把驱动类名改成新类名。
Spring Boot + HikariCP 的典型配置:
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8&allowPublicKeyRetrieval=true username: root password: your_password hikari: maximum-pool-size: 10 minimum-idle: 2 connection-timeout: 30000如果用 Druid:
spring: datasource: druid: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://127.0.0.1:3306/test?useSSL=false&serverTimezone=Asia/Shanghai username: root password: your_password initial-size: 2 max-active: 10如果你是从老项目迁移,原来配置文件里写的是com.mysql.jdbc.Driver,切换 8.0 驱动后建议一并改成com.mysql.cj.jdbc.Driver。虽然老类名暂时能用,但连接池在创建连接时仍会走旧类的兼容封装,日志里也可能出现 deprecation 提示,没必要留这个隐患。
4. 一个驱动配合下的 JDBC 增删改查与事务控制
4.1 完整增删改查示例
很多新手第一次接触 JDBC 是在“插入用户数据”之类的教学关卡里。我就以一个 user 表为例,展示增删改查的完整写法。
先建表:
CREATE TABLE `user` ( `id` int NOT NULL AUTO_INCREMENT, `name` varchar(50) NOT NULL, `age` int DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;Java 代码:
import java.sql.Connection; import java.sql.DriverManager; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; public class UserDao { private static final String URL = "jdbc:mysql://127.0.0.1:3306/test" + "?useSSL=false&serverTimezone=Asia/Shanghai&characterEncoding=utf8"; private static final String USER = "root"; private static final String PASSWORD = "your_password"; private Connection getConnection() throws SQLException { return DriverManager.getConnection(URL, USER, PASSWORD); } // 新增 public void insertUser(String name, int age) throws SQLException { String sql = "INSERT INTO user(name, age) VALUES (?, ?)"; try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, name); ps.setInt(2, age); ps.executeUpdate(); } } // 查询 public void listUsers() throws SQLException { String sql = "SELECT id, name, age FROM user ORDER BY id"; try (Connection conn = getConnection(); Statement st = conn.createStatement(); ResultSet rs = st.executeQuery(sql)) { while (rs.next()) { System.out.printf("%d | %s | %d%n", rs.getInt("id"), rs.getString("name"), rs.getInt("age")); } } } // 更新 public void updateUserAge(String name, int newAge) throws SQLException { String sql = "UPDATE user SET age = ? WHERE name = ?"; try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setInt(1, newAge); ps.setString(2, name); ps.executeUpdate(); } } // 删除 public void deleteUser(String name) throws SQLException { String sql = "DELETE FROM user WHERE name = ?"; try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { ps.setString(1, name); ps.executeUpdate(); } } }这里一个非常关键的实践经验是:不管新增还是查询,只要 SQL 里涉及外部输入,一律用PreparedStatement而不是Statement字符串拼接。PreparedStatement能预编译 SQL,还能自动处理字符串里的特殊字符,是防止 SQL 注入的第一道防线,也是代码规范问题,不是性能问题。
4.2 事务与回滚
事务控制的本质是“多个操作要么都成功,要么都失败”。JDBC 里默认每个 SQL 操作都是自动提交的,想要事务生效,需要先关闭自动提交。
public void transfer(String fromName, String toName, int amount) throws SQLException { String deductSql = "UPDATE account SET balance = balance - ? WHERE name = ?"; String addSql = "UPDATE account SET balance = balance + ? WHERE name = ?"; try (Connection conn = getConnection()) { conn.setAutoCommit(false); try (PreparedStatement deduct = conn.prepareStatement(deductSql); PreparedStatement add = conn.prepareStatement(addSql)) { deduct.setInt(1, amount); deduct.setString(2, fromName); deduct.executeUpdate(); // 模拟业务校验:如果扣款后余额为负,直接回滚 if (amount <= 0) { throw new RuntimeException("转账金额必须为正"); } add.setInt(1, amount); add.setString(2, toName); add.executeUpdate(); conn.commit(); } catch (Exception e) { conn.rollback(); throw e; } } }注意事务里不能把Connection分散在不同代码块中各自获取,否则每个getConnection()都是独立连接,事务根本不存在。一个事务必须跑在同一个Connection上,这也是为什么真实项目要用连接池管理连接而不是到处DriverManager.getConnection。
4.3 批处理场景下的性能提升
往表里插入大量数据时,一条条executeUpdate不仅代码丑,性能也差。JDBC 提供addBatch+executeBatch:
public void batchInsert(List<String[]> rows) throws SQLException { String sql = "INSERT INTO user(name, age) VALUES (?, ?)"; try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) { conn.setAutoCommit(false); for (String[] row : rows) { ps.setString(1, row[0]); ps.setInt(2, Integer.parseInt(row[1])); ps.addBatch(); } ps.executeBatch(); conn.commit(); } }如果只在代码层面addBatch,但 URL 里没有加rewriteBatchedStatements=true,MySQL 驱动默认还是会一条条发给服务端执行,批处理效果大打折扣。加了rewriteBatchedStatements=true之后,驱动会把多条插入合并成一条多值插入 SQL 发过去,性能能有数量级的提升。这个参数在数据导入场景里值得专门留意。
5. 把驱动 Jar 包打好并随应用一起发布
5.1 用 Spring Boot 插件打可执行 Jar
用 Spring Boot 开发时,最常见的诉求是“本地能跑,打成 jar 部署到服务器却找不到驱动”。这通常是因为没有把mysql-connector-j依赖打进去。Spring Boot 的spring-boot-maven-plugin默认会把项目依赖全部打进可执行 jar,所以理论上不需要额外配置。
但如果你在pom.xml里把依赖标记成了provided或runtime以外的作用域,或者手动排除了某些依赖,驱动就有可能在打包时被漏掉。检查办法是解压打出来的 jar,看看BOOT-INF/lib/下有没有mysql-connector-j-8.0.33.jar。没有就检查pom.xml依赖声明。
5.2 Maven Shade 插件打成 Fat Jar
非 Spring Boot 的普通 Maven 项目,想打一个含驱动和其他依赖的 Fat Jar,可以用maven-shade-plugin:
<plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-shade-plugin</artifactId> <version>3.5.1</version> <executions> <execution> <phase>package</phase> <goals> <goal>shade</goal> </goals> <configuration> <transformers> <transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> <mainClass>com.example.Main</mainClass> </transformer> </transformers> <filters> <filter> <artifact>*:*</artifact> <excludes> <exclude>META-INF/*.SF</exclude> <exclude>META-INF/*.DSA</exclude> <exclude>META-INF/*.RSA</exclude> </excludes> </filter> </filters> </configuration> </execution> </executions> </plugin>这里的 filter 排除签名文件很关键。因为某些 jar 带了 jar 签名信息,全部合并到 Fat Jar 后,JVM 在做类校验时会因签名冲突报SecurityException或SignatureException。不加这个 filter,很多人会在“本地能跑、打出来的 jar 不能跑”的怪圈里转很久。
5.3 命令行手动编译和运行业务代码
如果你没有用构建工具,只想在命令行快速跑一个 JDBC 示例,那需要把 mysql-connector jar 和编译后的 class 文件一起放到 classpath 里。
Linux/macOS:
javac -encoding UTF-8 -cp .:mysql-connector-j-8.0.33.jar UserDao.java java -cp .:mysql-connector-j-8.0.33.jar UserDaoWindows:
javac -encoding UTF-8 -cp .;mysql-connector-j-8.0.33.jar UserDao.java java -cp .;mysql-connector-j-8.0.33.jar UserDaoWindows 下 classpath 分隔符是分号;,Linux/macOS 下是冒号:,这个细节非常容易踩。写脚本的时候尽量不要把路径写死,可以用相对路径或者用$(pwd)拼一个当前目录。
6. 部署场景下的驱动位置与框架集成
6.1 Tomcat/Web 应用把 Jar 放在哪里
传统 Web 应用部署到 Tomcat 时,驱动 Jar 可以放在两个地方:项目自身的WEB-INF/lib,或者 Tomcat 的lib目录。
我建议放在项目WEB-INF/lib,理由是一个 Tomcat 上可能跑多个应用,每个应用依赖的驱动版本未必一致。如果塞进 Tomcat 全局lib,多个应用之间会出现类加载串扰,A 项目需要的 8.0.33 可能与 B 项目用的 5.1.49 冲突,表现就是两个项目轮流出问题,排查起来很痛苦。所以驱动跟应用走,是一个省心的原则。
6.2 Flink 等框架的 JDBC 连接器异常
“Flink JDBC 连接器异常”在部署 MySQL 8.0 环境时也很高频。表现通常是:作业提交后,TaskManager 日志里抛ClassNotFoundException: com.mysql.cj.jdbc.Driver。
这个问题的处理思路是让驱动 Jar 出现在运行时的类加载路径上。最简单的做法是把mysql-connector-j-8.0.33.jar复制到 Flink 的lib目录,重启 Flink 集群或任务。如果不想全局放,也可以提交作业时用-C file:///path/to/mysql-connector-j-8.0.33.jar参数附带这个依赖。
这种框架类的问题本质都一样:开发环境有驱动,运行环境没有。排查时先看执行节点的日志,确认是No suitable driver还是ClassNotFoundException,前者往往是DriverManager没识别到驱动,后者是驱动类根本没被加载,处理方式略有差异。
6.3 服务器端部署 MySQL 8.0 后的驱动配套
在一些运维监控场景,比如 CentOS 上部署监控工具并且后端使用 MySQL 8.0,JDBC 驱动版本不匹配同样会导致监控数据写入失败或服务探活异常。监控服务如果是用 Java 写的,通常在其安装目录下的lib里会带一个驱动 Jar,部署完 MySQL 8.0 后不能想当然认为“驱动还在就行”,建议检查一下驱动版本是否跟 MySQL 8.0 匹配。
这类基础组件问题,最容易反噬的时间点是“数据库升级、应用没升级”的过渡期。很多团队决定升级 MySQL 8.0 后,第一件事是停服升级数据库,最后才想起来改应用驱动,这会导致整个窗口期应用都在报错。正确的节奏是先把新版本驱动在测试环境验证,再把应用一起升级。
7. 高频异常排查速查表
把常见错误按“现象、原因、解法”整理成了一张表,排查时对照着看能省不少时间。
| 错误信息或现象 | 常见原因 | 解决思路 |
|---|---|---|
ClassNotFoundException: com.mysql.cj.jdbc.Driver | 驱动 jar 没有进入 classpath | 确认依赖坐标、本地仓库 jar 是否存在,或检查打包后的 lib |
No suitable driver found for jdbc:mysql://... | 驱动类被容器隔离,DriverManager没发现 | 显式写Class.forName("com.mysql.cj.jdbc.Driver"),检查 classpath |
Public Key Retrieval is not allowed | 使用caching_sha2_password认证且未开启 SSL | URL 加allowPublicKeyRetrieval=true |
Unable to load authentication plugin 'caching_sha2_password' | 驱动版本过旧,不认识 MySQL 8.0 认证插件 | 升级到 8.0.x 驱动;或把用户认证插件改回mysql_native_password(不推荐) |
The server time zone value 'CST' is unrecognized | 缺少时区配置,或时区缩写有歧义 | URL 加serverTimezone=Asia/Shanghai |
SSL connection error/ 证书警告 | 客户端与服务端 SSL 参数不一致 | 内网先显式useSSL=false,生产按安全规范配置证书 |
Communications link failure | 数据库地址不通、端口未开放、防火墙拦截 | 用telnet或nc测试端口连通性,查网络和安全组 |
| 中文乱码 | 数据库字符集与客户端字符集不统一 | URL 加characterEncoding=utf8,确认表结构为utf8mb4 |
Jar not loaded/ 多个驱动版本冲突 | classpath 中同时存在多个 connector 版本 | 用mvn dependency:tree定位,排除旧版本 |
download from maven failed | Maven 无法访问中央仓库 | 配置阿里云镜像或公司私服,检查本地仓库权限 |
Access denied for user 'root'@'...' | 用户名、密码错误,或授权主机不符 | 检查账号授权、密码,以及连接的 host 白名单 |
| 驱动能连接但 SQL 执行超时 | 网络延迟、锁等待、慢 SQL | 设置connectTimeout和socketTimeout,优化 SQL |
实际排查驱动连接问题时,我一般按照“版本 -> 类名 -> URL 参数 -> 网络连通性 -> 认证方式”这五个步骤依次确认。先确认驱动版本是 8.0.x,再确认驱动类名是不是com.mysql.cj.jdbc.Driver,然后看 URL 里有没有serverTimezone和useSSL这两个高频坑参数,再测试端口连通性,最后看认证插件。八成以上的问题在这五步之内就能定位。
8. 几个从项目实战里沉淀的习惯
最后分享几个自己在实际维护 MySQL 8.0 驱动过程中养成的习惯,算不上什么高深技巧,但确实能减少很多重复踩坑。
第一个习惯是:驱动版本固定写死,并且用一个常量管理。不要在pom.xml里写“最新的那个版本号”或者“网上查到的统一版本号”,要明确锁定8.0.33或你自己验证过的版本,后续升级驱动要当作一次功能升级来处理,走测试流程。
第二个习惯是:连接参数放配置中心或配置文件,不散落在 Java 代码里。serverTimezone、useSSL、connectTimeout这些参数在开发环境、测试环境、生产环境可能是不同值,写进代码再改就太痛苦了。用 Spring Boot 的application.yml或环境变量去承载它们,要灵活得多。
第三个习惯是:拿到一个新项目或者接手一个老项目,第一件事查驱动版本和连接 URL,而不是急着写业务代码。因为驱动版本不对,后面所有基于连接池的代码都会在运行时暴露问题,而且报错信息五花八门,越早发现成本越低。
第四个习惯是:本地准备一个“连接测试专用类”,不依赖 Spring、不依赖连接池,工具类一样直接跑。这个类可以随时验证“当前 classpath 下驱动能不能连上数据库”,在做驱动升级、环境切换、docker 数据库启动后非常有价值。
MySQL 8.0 的 JDBC 驱动 Jar 包本身只是一个文件,但围绕它的版本选择、连接参数、打包策略和排错方法,才是实际项目中真正要花时间的部分。希望这篇内容能帮你少走几步弯路,遇到驱动相关的问题也能有清晰的排查路径。