做 Java 后端的同学,这两年多半都撞见过同一个疑惑:pom 里用了多年的mysql:mysql-connector-java,怎么突然在一些官方示例和新项目里变成了com.mysql:mysql-connector-j?我第一次看到新坐标的时候还以为是某个第三方做的精简版,差点五花八门去核对 jar 包。后来把官方发布记录和 Maven 仓库的版本列表翻了一遍才弄明白:这俩坐标确实对应同一个 MySQL 官方 JDBC 驱动,只是从 8.0.31 版本开始,官方把构件名从mysql-connector-java精简成了mysql-connector-j,后续的维护和迭代也都迁移到了新坐标上。如果你正要升级驱动、新建工程,或者只是被旧文档坑到怀疑人生,这篇文章正好能帮你把这些来龙去脉一次性捋清楚。
1. 两个驱动坐标的诞生背景与本质区别
1.1 老坐标 mysql-connector-java 是怎么回事
Java 程序要用 JDBC 连 MySQL,绕不开官方驱动。这个驱动在产品层面一直叫 MySQL Connector/J,翻译成 Maven 坐标则是groupId:artifactId的结构。
早年间,开发者写依赖时最常见的是这一行:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.49</version> </dependency>后来官方把 groupId 统一成了com.mysql,这才是绝大多数 8.0 项目里熟悉的写法:
<dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency>这段历史里真正没变的,是artifactId一直叫mysql-connector-java。5.x、6.x、8.0.x 一路用下来,大家也习惯了,满世界教程、面试题、老项目里全是这个名字。
1.2 新坐标 mysql-connector-j 是什么时候出现的
新坐标出现在 8.0.31。版本号继续沿用 8.0.x 序列,但 artifactId 从mysql-connector-java变成了mysql-connector-j,groupId 保持com.mysql不变:
<dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.33</version> </dependency>从 Maven 仓库的版本列表能看得很清楚:mysql-connector-java在 8.0.31、8.0.32、8.0.33 这几个版本上仍然有同步发布,但更新版本比如 8.4.0、9.x 系列,就只出现在mysql-connector-j这个新坐标下了。也就是说,新坐标不只是改了个名字,更是驱动后续版本的实际容器。
1.3 一表看清两者的核心差异
| 对比项 | mysql-connector-java(旧坐标) | mysql-connector-j(新坐标) |
|---|---|---|
| Maven 坐标 | com.mysql:mysql-connector-java | com.mysql:mysql-connector-j |
| 主要版本范围 | 5.x、6.x、8.0.0 至 8.0.33 | 8.0.31 至今,含 8.4、9.x |
| 驱动类名 | 8.x 为 com.mysql.cj.jdbc.Driver | 与旧坐标 8.x 完全相同 |
| 包名 | com.mysql.cj.* | com.mysql.cj.* |
| JDBC URL | jdbc:mysql://host:port/db | jdbc:mysql://host:port/db |
| 官方推荐程度 | 新项目不再推荐 | 新项目推荐使用 |
| 后续更新 | 只出到 8.0.33 | 持续更新,支持新版本 |
这里要给刚接触的同学提个醒:同一版本号下的两套坐标,内容上是同一个 jar,功能差异可以认为为零。很多网上教程为了省事,只说“改成新坐标就行”,却没解释清楚版本节点,导致有人把业务代码里所有类名都改了一遍,纯属白忙活。
2. 官方改名背后的版本迭代逻辑
2.1 为什么 8.0.31 成了分水岭
很多人会问,驱动没换,为什么要折腾坐标?我个人的理解是,这是 Connector/J 产品线在版本策略上的一次收敛。
MySQL 官方从 8.0 开始,把驱动内部结构做了一次大调整,最明显的就是com.mysql.jdbc.*下的老类被彻底移到了com.mysql.cj.*下。这个动作在 8.0 刚发布时就已经完成了大部分,但外层的 Maven 坐标还带着-java尾巴。产品名本身是 Connector/J,J 已经代表 Java,再拼一个-java后缀确实显得冗余。官方借着 8.0.31 的发布,把 artifactId 和产品名对齐,精简成mysql-connector-j,后面发版本就顺着新名字走了。
还有一层现实原因:旧坐标mysql-connector-java承担了太多历史包袱,搜索页面上经常会和文件名、文档目录、第三方二次封装混淆。把构件名改短,能减少不少歧义。
2.2 新老坐标在 Maven 中央仓库里的交叉发布期
不是所有项目都能立刻搬到新坐标,官方因此在 8.0.31 到 8.0.33 这几版做了双轨发布:旧坐标和新坐标同时有对应版本的 jar。
也就是说,你现在去 Maven 中央仓库搜mysql-connector-java,仍然能拉到 8.0.33 这个最终版;搜mysql-connector-j,则能看到从 8.0.31 开始的完整版本链。这个“交叉发布期”其实很有讲究,它避免了一次性强切导致大量旧项目无法升级的问题。但从 8.4.0 开始,新版本只发布在新坐标下,旧坐标的版本线基本就停在 8.0.33 了。
如果项目里还想继续升级到 8.4 或更新的驱动,那就必须切到mysql-connector-j,没有第二条路。
2.3 直接代替还是完全弃用:使用边界在哪
这里的边界其实很清晰:
- 新项目、新模块、新微服务,直接用
com.mysql:mysql-connector-j。 - 旧项目如果只是小版本升级,比如从 8.0.30 升到 8.0.33,继续用旧坐标也不算错误,因为这段区间官方还在同步发布。
- 但如果目标是长期维护、后续要吃到驱动对 MySQL 9.x 的支持、新认证协议修复等更新,就得尽早切到新坐标。
我见过不少项目把“用哪个坐标”当成纯粹的文本替换问题,结果换完之后依赖树里出现了两个不同 groupId 的同名 jar,启动时类加载错乱,排查了半天才发现是依赖冲突。这个坑后面我会单独用一节来细说。
3. Maven 与 Gradle 项目里如何正确替换
3.1 Maven 替换步骤与坐标对照
Maven 项目里替换依赖很简单,但要分版本处理。
如果你当前用的是 5.x 或 6.x,建议先跨到 8.0.x 最新版,直接用旧坐标:
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.30</version> </dependency>跑通后再换成新坐标:
<dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.33</version> </dependency>这里有个容易忽略的点:老工程里的 groupId 可能是mysql,而新坐标的 groupId 是com.mysql。如果只改了 artifactId 没改 groupId,Maven 会报“找不到依赖”,因为mysql:mysql-connector-j这个组合根本不存在。
还有个更稳妥的做法:如果你的工程用了统一的 BOM 或父 pom 管理版本,新坐标在父 pom 里可能已经预置了版本号。比如很多新版本的聚合工程会把mysql-connector-j放到 dependencyManagement 里,子模块里只需要写 groupId 和 artifactId,不用写 version,避免各处版本不一致。
3.2 Gradle 替换步骤与注意事项
Gradle 项目同样直接改依赖字符串:
dependencies { implementation 'com.mysql:mysql-connector-j:8.0.33' }如果你原本写的是:
implementation 'mysql:mysql-connector-java:8.0.33'改成上面那样即可。
但我强烈建议改完之后跑一次:
gradle dependencies --configuration runtimeClasspath重点看依赖树里是否还残留旧坐标。Gradle 的依赖冲突策略默认会选最高版本,但两个坐标之间不是同一 artifact,Gradle 并不会自动把它们视为冲突,于是会出现两个 jar 同时躺在 classpath 上的情况。两个 jar 里的类名高度重叠,最终加载哪个看 classpath 顺序,启动时一旦调用了旧版本的类又缺少新方法,就会抛NoSuchMethodError这类诡异问题。
我以前处理过一个服务,明明代码里用的是新驱动特性,运行时报方法找不到,最后发现是传递依赖带进来的旧坐标 jar 排到了前面。所以说,改坐标只是第一步,清理干净依赖树才是关键。
3.3 Spring Boot 环境下的替换细节
Spring Boot 项目里,依赖管理通常由父 pom 统一控制。不同 Boot 版本对 MySQL 驱动的坐标管理并不一致:老一点的 Boot 2.x 默认管理的是mysql-connector-java,新一点的 Boot 3.x 则把受管坐标切到了mysql-connector-j。
如果你在较老版本的 Spring Boot 里直接写新坐标而不写版本号,会因为 dependencyManagement 里没有这条记录而拉取失败;反过来,在新版 Boot 里写旧坐标,也有同样的问题。
我的习惯是:在 Spring Boot 的application.yaml或application.properties里显式写好驱动类名和 URL:
spring: datasource: url: jdbc:mysql://localhost:3306/demo?useSSL=false&serverTimezone=Asia/Shanghai driver-class-name: com.mysql.cj.jdbc.Driver username: root password: root不要偷懒把driver-class-name省略。虽然 JDBC 4 之后可以通过 SPI 自动注册驱动,但各种连接池和中间件在实际初始化时会读取这个配置,写上类名更可控。类名不管新旧坐标,8.x 版本都是com.mysql.cj.jdbc.Driver,这一点完全不用改。
4. 包名、驱动类名与版本兼容性对照
4.1 class 名和 URL 写法有没有变化
很多人会被驱动类名绕晕,这里详细说清楚。
先说类名。MySQL 驱动在 8.0 之前的经典写法是:
com.mysql.jdbc.DriverConnector/J 8.0 之后换成了:
com.mysql.cj.jdbc.Driver注意中间多了cj这一层。新坐标mysql-connector-j同样是 8.x 系列,驱动类名和旧坐标 8.x 完全一致,都是com.mysql.cj.jdbc.Driver。所以改坐标根本不用动代码里的类名。
再说 URL。JDBC 连接串依然是:
jdbc:mysql://ip:port/databaseName如果遇到时区问题,要加参数:
jdbc:mysql://ip:port/databaseName?useSSL=false&serverTimezone=Asia/Shanghai这部分在 8.0.31 前后没有任何变化,也没必要因为改了坐标而调整连接参数。很多人刚切换到新坐标时,发现启动日志里出现Could not create connection to database server,往往不是坐标问题,而是时区参数没配好。
4.2 驱动版本与 MySQL 服务端版本的匹配表
驱动坐标改了,但兼容的基本原则没变。这里给出一张常用匹配参考:
| 驱动版本 | 推荐 MySQL 服务端版本 | 说明 |
|---|---|---|
| 5.1.x | MySQL 5.5 / 5.6 / 5.7 | 老项目常见,协议较旧 |
| 8.0.x | MySQL 5.7 / 8.0 / 8.4 | 兼容性较好,推荐 5.7 以上使用 |
| 8.4.x / 9.x | MySQL 8.0 / 8.4 / 9.x | 新版本仅发布在新坐标 |
如果你还在维护 MySQL 5.6 或更老的服务端,上 8.x 驱动就要谨慎测试。官方文档里的兼容矩阵写得很明确,并不是说驱动版本越新越能覆盖老服务端,有时候新驱动支持的认证方式老服务端并不具备,反而会连不上。
遇到这类问题,先看认证插件。MySQL 8.0 默认的caching_sha2_password认证,老驱动不一定支持,通常体现为Public Key Retrieval is not allowed或无法完成身份验证。处理办法是驱动升级到 8.x,同时在连接参数里设置:
allowPublicKeyRetrieval=true不过这个参数在生产环境里要谨慎开启,它允许客户端从服务端拉取公钥,存在一定安全风险,最好只在调试阶段用。
4.3 JDK 版本约束与编译级别
Connector/J 8.x 系列最低要求 JDK 8,这是官方给出的基准。如果你的项目还在 JDK 7 上跑,直接上 8.0.31 之后的版本基本没戏,老老实实留在 5.1.x 或者先升级 JDK,再把驱动切到新坐标。
JDK 8 以上的项目切到新坐标几乎是无缝的。不过我建议把编译和运行环境的 JDK 小版本也一并确认,比如某些老系统用的 JDK 8 小版本过低,驱动里引用的某些类或方法在更早版本里不存在,启动时也会报ClassNotFoundException。这种问题排查起来挺折磨人,因为第一反应永远是依赖写错了,实际上是把新驱动跑在了过老的运行时上。
5. 迁移过程中的常见坑与排查实录
5.1 两个坐标同时出现在依赖树里
这是切新坐标后出现频率最高的坑。项目中直接依赖写的是新坐标,但某个第三方库内部还传递依赖着旧坐标mysql:mysql-connector-java,于是两个 jar 同时出现在 classpath。
症状很典型:编译期正常,启动期却偶尔报NoSuchMethodError、AbstractMethodError,甚至在某个环境下跑得好好的,换个环境就失败。原因就是两个 jar 内的类路径完全一致,JVM 按 classpath 顺序加载,谁在前面算谁的。
定位命令很简单。Maven 项目:
mvn dependency:tree -Dincludes=mysql,com.mysqlGradle 项目:
gradle dependencies --configuration runtimeClasspath处理方式是在依赖声明里排除旧坐标:
<dependency> <groupId>com.mysql</groupId> <artifactId>mysql-connector-j</artifactId> <version>8.0.33</version> </dependency>如果旧坐标是第三方库带进来的,则需要在该第三方依赖里加排除:
<exclusions> <exclusion> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </exclusion> </exclusions>共享 jar 命名空间的坑,一定要用 exclusion 而不是只靠默认冲突策略,因为这两个坐标不满足 Maven 的“同 groupId 同 artifactId”冲突判定。
5.2 引入新坐标后启动报 ClassNotFound 的分析
有人改完坐标后启动报:
java.lang.ClassNotFoundException: com.mysql.jdbc.Driver别急着怀疑坐标对不对,先看你的类名。如果项目原本用的是 5.x,代码或配置里的驱动类名是com.mysql.jdbc.Driver,而新驱动是 8.x,这个类已经被移到了com.mysql.cj.jdbc.Driver。
也就是说,ClassNotFound 的真实原因是类名与驱动版本不匹配,而不是坐标写错。解决办法两个方向:
- 把配置里的驱动类名改成
com.mysql.cj.jdbc.Driver - 或者在配置里干脆不写 driver-class-name,让 JDBC 自动通过 SPI 注册
第二种方式更省事,但前提是项目里没有自定义的类加载逻辑。有些老容器应用会提前加载驱动类,这种情况下还是显式指定类名更可靠。
还有一种隐蔽情况:本地 Maven 仓库里有之前缓存的旧 jar,坐标虽然写的是新名字,IDE 却把资源指向了旧 jar。这种时候执行为:
mvn clean mvn dependency:purge-local-repository -DreResolve=true或者干脆手动删掉本地仓库~/.m2/repository下的com/mysql和mysql目录,重新拉取,能解决绝大多数由缓存导致的环境诡异问题。
5.3 旧项目升级时最容易忽略的三件事
第一件事是反射代码。部分老项目在启动时会通过反射加载驱动类:
Class.forName("com.mysql.jdbc.Driver");升级到 8.x 后这行代码必炸。改成:
Class.forName("com.mysql.cj.jdbc.Driver");或者直接删掉,因为 JDBC 4 以上的服务提供者机制会自动注册。
第二件事是连接池配置。像老版本的 Druid、HikariCP、C3P0,配置文件里都可能写死driverClassName=com.mysql.jdbc.Driver。只升级驱动 jar 而不同步改连接池配置,应用启动时会一直报找不到类。建议全局搜一下项目里所有的com.mysql.jdbc字符串,一处不落全改掉。
第三件事是时区与 SSL 参数。老项目线上用 5.x 驱动时可能没配serverTimezone,MySQL 8.x 之后服务端默认时区规则变了,新的驱动会严格校验,连接串里不补时区参数就会失败。切换坐标前先把连接串改成包含serverTimezone=Asia/Shanghai的完整形式,跑通了再上线,能省掉夜间救火的痛苦。
6. 新旧坐标并存时我的选型建议
聊了这么多,落到实际操作层面,我的建议其实很朴素:新工程一律用com.mysql:mysql-connector-j,旧工程在做驱动升级时优先切换到新坐标,并显式指定版本号,不要依赖父 pom 的隐式版本。原因也很简单:新坐标是后续版本迭代的主线,旧坐标停在 8.0.33,你再怎么锁版本也等不来新特性。
不过有一点要记住:坐标名称的变化代表的是产物流转方式的变化,不改变驱动的运行模型。所有跟数据库交互的代码、连接池参数、事务配置,都不会因为坐标改成mysql-connector-j而自动优化。真正决定驱动是否好用的,还是你项目里如何配置连接参数、如何处理时区、如何管理连接池。我见过不少团队把精力全放在“换坐标”上,结果生产环境里连接泄漏照样发生,驱动版本再新也无济于事。
最后分享一个经验:每次做这类依赖升级,先把mvn dependency:tree的结果保存一份,和升级后的依赖树做 diff。坐标改了、版本号变了、多出来的依赖、少掉的依赖,一眼就能看清。这套方法既能防踩旧 jar 残留的坑,也能在出问题时快速回滚。你自己在项目里试一次,就知道它有多值。