Spring Boot 3.x连接MySQL 8.0:JDBC配置与排错 简介面向Java后端初学者与需要快速搭建数据库访问示例的开发者这份Spring Boot整合JDBC连接MySQL的解决方案以可运行项目为核心配套源码、数据库脚本及运行环境安装文件适合课程实验、自学练手和项目原型参考。压缩包共136个文件约637.42MB包含java源码、class编译文件、properties配置、sql脚本以及dll、exe、msi等驱动与安装组件另有zip资源包和说明文档目录覆盖源码、依赖与部署所需材料。已有601人学习下载。读者可从中获得较完整的工程结构、JDBC数据源配置示例、实体与控制层代码、建表脚本和依赖环境便于理解Spring Boot访问MySQL的基本链路也可用于本地复现、对照排错和二次改造减少从零搜集驱动与示例的成本。1. 版本先对齐JDK、Spring Boot 与 MySQL 驱动的三件套Java SpringBoot 使用 JDBC 连接 MySQL 数据库这件事最常见的翻车点不在业务代码而在版本。同一套源码在旧机器上跑得好好的换台机器一启动就抛No suitable driver found for jdbc:mysql://...或者连上了却卡在Public Key Retrieval is not allowed。前者是驱动包没进 classpath后者是 MySQL 8.0 的默认认证插件和旧驱动对不上。这套环境太常见了几乎每个 Java 后端工程都要走一遍但装不好就是启动起不来、连上连不稳、跑起来乱码三连。JDK 17 Spring Boot 3.x MySQL 8.0 是当前最主流的组合JDK 8 Spring Boot 2.7.x MySQL 5.7 是存量项目里最常见的组合两条线都要心里有数。这篇笔记按「装数据库 → 建库建表 → 配工程 → 排错 → 验证」走一遍照着做能省掉大半天。2. 装 MySQL 8.0 与建库字符集、时区、认证插件三处最容易写错数据库这一层是整条链路的底座装歪了后面怎么调都别扭。很多人图省事装完一路「下一步」结果字符集是 latin1、时区是 UTC、认证插件是 caching_sha2_password等 Spring Boot 连上来才一个坑一个坑地补。我一般会在安装阶段就把这三项一次性钉死后面少改配置。2.1 安装包怎么选8.0.x 与 5.7 的差别落在哪MySQL 8.0 和 5.7 对使用者影响最大的不是性能数字而是三件事默认字符集从 latin1 变成 utf8mb4、默认认证插件从 mysql_native_password 变成 caching_sha2_password、默认排序规则多了一套utf8mb4_0900_ai_ci。前两条直接影响 Java 端能不能连上第三条影响中文排序和唯一索引的行为。常见做法是新项目直接上 8.0.x老项目迁移前先确认驱动版本。判断依据很简单看驱动包 artifactId 和版本号组合JDKSpring BootMySQL驱动坐标新项目173.x8.0.xcom.mysql:mysql-connector-j存量项目82.7.x5.7 / 8.0.xmysql:mysql-connector-java8.0.x极老项目82.x5.7mysql:mysql-connector-java5.1.x能换就换8.0.33 之后官方把驱动坐标从mysql:mysql-connector-java改成了com.mysql:mysql-connector-j这是个硬变更pom 里还写着老坐标会直接拉不到包。升级时如果只改版本号不改 groupId构建就红。安装包我一般选压缩包版本而不是安装向导版本。向导版会顺手塞一堆服务、环境变量和 Data 目录约定出问题不好定位压缩包版本目录结构清楚my.ini放哪、数据放哪都是自己说了算后面删干净也方便。2.2 my.ini 里真正要改的五项解压之后先在根目录建一个my.iniWindows 下注意是 ANSI 或 UTF-8 无 BOM带 BOM 会导致第一项配置读不到。下面这份是我常用的最小可用版本[mysqld] port3306 basedirD:/mysql-8.0.36 datadirD:/mysql-8.0.36/data character-set-serverutf8mb4 collation-serverutf8mb4_0900_ai_ci default-time-zone08:00 max_connections500 wait_timeout28800几个参数值得单独说character-set-server和collation-server必须成对出现。只写字符集不写排序规则MySQL 会按版本默认值补8.0 补utf8mb4_0900_ai_ci5.7 补utf8mb4_general_ci跨版本迁移时唯一索引可能因为比较规则不同而报重复键。default-time-zone08:00用的是偏移量写法不是Asia/Shanghai。后者要求数据库里导入过时区表压缩包版本默认没导写了会启动失败。偏移量写法没有夏令时问题对我们这边够用。wait_timeout默认 28800 秒8 小时。这个值和 Spring Boot 连接池的max-lifetime是联动的后面配连接池时会回头对一次。如果确定要走老认证插件加一行default_authentication_pluginmysql_native_password。注意这一项在 8.0.34 之后已被标记弃用8.4 直接移除替代写法是authentication_policymysql_native_password。我的建议是别在这一层妥协直接升级驱动到 8.x保留 caching_sha2_password安全性和兼容性都更省心。提示basedir和datadir里不要出现中文和空格。用D:/mysql-8.0.36这种正斜杠写法反斜杠在 ini 里容易被当转义。2.3 初始化、注册服务与首次登录配置写好后先初始化数据目录。这一步只在第一次做目录非空会报错# Windows初始化数据目录root 账号生成随机密码并打印到控制台 mysqld --defaults-fileD:/mysql-8.0.36/my.ini --initialize --console # 注册为系统服务并启动 mysqld --install MySQL80 --defaults-fileD:/mysql-8.0.36/my.ini net start MySQL80--initialize会生成一个随机 root 密码直接打在控制台里第一次登录要用。如果手滑关了窗口只能清空 data 目录重来这是最常见的后悔药没处买。想省事可以用--initialize-insecure生成空密码 root登录后立刻改但只在完全隔离的本机开发环境这么干。Linux 下如果是 tar 包方式对应的是# 初始化并启动注意 --user 要和 datadir 的属主一致 bin/mysqld --defaults-file/opt/mysql/my.cnf --initialize --usermysql bin/mysqld_safe --defaults-file/opt/mysql/my.cnf --usermysql 首次登录后先把 root 密码改掉再看一眼三个关键参数是否生效ALTER USER rootlocalhost IDENTIFIED BY Root_Str0ng_Pwd; SELECT character_set_server, collation_server, time_zone, version;如果character_set_server还是 latin1说明my.ini没被读到。排查顺序是配置文件路径对不对 → 有没有被--defaults-file显式指定 → 文件编码有没有 BOM。三个都排除还不对就看服务启动时加载的是哪个路径这一步能省掉大量瞎猜。2.4 建库建表与最小权限账号应用不要用 root 连库这是底线。建一个只对业务库有增删改查权限的账号即使配置文件泄露影响面也可控。-- 建库显式指定字符集和排序规则别依赖服务端默认值 CREATE DATABASE demo_app DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci; -- 建应用账号% 表示允许从任意主机连生产环境建议限定具体网段 CREATE USER app_rw% IDENTIFIED BY App_Rw_2024; -- 只给业务库的 DML 权限DDL 留给运维账号 GRANT SELECT, INSERT, UPDATE, DELETE ON demo_app.* TO app_rw%; FLUSH PRIVILEGES; -- 建一张用于验证的表 CREATE TABLE demo_app.t_user ( id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 主键, user_name VARCHAR(64) NOT NULL COMMENT 用户名, email VARCHAR(128) DEFAULT NULL COMMENT 邮箱, created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT 创建时间, PRIMARY KEY (id), UNIQUE KEY uk_user_name (user_name) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COLLATEutf8mb4_0900_ai_ci;建库语句里显式写CHARACTER SET是有必要的。服务端默认值改了不影响已建好的库反过来别人机器上服务端默认值不是 utf8mb4 时你的建库脚本也不会跟着歪。%这个 host 部分要理解清楚MySQL 的账号是用户名主机二元组app_rw%和app_rwlocalhost是两个不同账号权限互不相通。本地用127.0.0.1连的时候MySQL 会匹配到localhost走 socket这时候如果只建了%的账号某些版本上会出现「密码对但连不上」的玄学现象。稳妥做法是本地开发两个都建或者连接串里统一写127.0.0.1走 TCP。账号建完立刻用命令行验证一遍别等 Spring Boot 启动才发现问题mysql -h 127.0.0.1 -P 3306 -u app_rw -p -D demo_app -e SELECT COUNT(*) FROM t_user;这条能跑通说明网络、端口、账号、权限、库名五件事全对后面 Spring Boot 连不上就只可能是配置或驱动问题排查范围一下子缩小一半。这一步我每次装环境都不会跳。3. 搭 Spring Boot 工程依赖坐标与 application.yml 配置数据库通了接下来是工程侧。这一章的核心就两件事pom 里放对两个依赖yaml 里写对一段 URL。听起来简单但每个参数背后都有对应的失败模式写错了报错信息往往指向别处很费时间。3.1 依赖坐标starter-jdbc 与驱动包的关系用 JDBC 而不是 JPA 的时候依赖其实只有两个spring-boot-starter-jdbc提供JdbcTemplate、事务管理、数据源自动装配mysql-connector-j提供真正的数据库协议实现。两者缺一不可只加驱动会出现「数据源配置了但没有 JdbcTemplate」的情况只加 starter 会在启动时报Failed to determine a suitable driver class。!-- 提供 JdbcTemplate、DataSource 自动配置、事务管理 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency !-- 真正的 MySQL 协议驱动8.0.33 之后的坐标是 com.mysql:mysql-connector-j -- dependency groupIdcom.mysql/groupId artifactIdmysql-connector-j/artifactId scoperuntime/scope /dependencyscope写runtime是惯例因为业务代码里不该出现com.mysql.cj.jdbc.Driver这类类名全部通过 JDBC 接口访问数据库换库时不用改代码。Spring Boot 会根据 URL 自动推断驱动类driver-class-name一般可以不写写上也无害我喜欢写上出问题时一眼能看到用的是哪个驱动。如果项目里还有连接池的显式依赖注意别引两份。Spring Boot 2.x 起默认连接池是 HikariCPstarter-jdbc 已经带进来了再单独引 c3p0 或 dbcp2 会出现多数据源装配歧义。3.2 application.yml 里 URL 的六个参数逐项拆解这一段 URL 是整章最容易写错的地方我把每个参数为什么加说清楚spring: datasource: # 关键参数逐个说明见下方文字 url: jdbc:mysql://127.0.0.1:3306/demo_app?useSSLfalseallowPublicKeyRetrievaltruecharacterEncodingutf8serverTimezoneAsia/ShanghairewriteBatchedStatementstrueconnectTimeout3000socketTimeout60000 username: app_rw password: App_Rw_2024 driver-class-name: com.mysql.cj.jdbc.DriveruseSSLfalse本机和内网环境关掉 TLS 握手少一层证书校验启动快一点。生产环境如果走公网或跨机房应该改成true并配好信任库这里不是省事而是省错了地方。allowPublicKeyRetrievaltrueMySQL 8.0 的 caching_sha2_password 在非 SSL 连接下需要向服务端取公钥来加密密码不加这个参数直接报Public Key Retrieval is not allowed。这是 JDBC 连 8.0 最常见的第一个坑。characterEncodingutf8告诉驱动客户端用什么编码收发。注意这里写utf8就行驱动会映射到 utf8mb4写utf8mb4在部分驱动版本上反而不认。serverTimezoneAsia/Shanghai驱动解析DATETIME/TIMESTAMP时用的时区。不写这一项遇到服务端时区是CST这类缩写时会抛The server time zone value 中国标准时间 is unrecognized。写Asia/Shanghai而不是GMT8因为前者带历史夏令时规则虽然我们不用但驱动内部换算更稳。rewriteBatchedStatementstrue批量插入时把多条 INSERT 改写成一条多值 INSERT几千条数据的批处理能从几十秒降到一两秒。这个参数不开JdbcTemplate.batchUpdate就是假批量。connectTimeout和socketTimeout连接建立超时和读写超时单位毫秒。不设的话默认值很长数据库挂掉时接口会一直挂着不返回把线程池拖死。设短一点让失败快速暴露。yaml 里 URL 加双引号是必须的。URL 里全是和?yaml 解析器对裸写字符串里的特殊字符容忍度不高加引号最省心。3.3 HikariCP 五个要调的参数连接池参数不是越多越好真正影响行为的就五个spring: datasource: hikari: pool-name: demo-hikari maximum-pool-size: 20 # 最大连接数单实例建议 10~20 minimum-idle: 5 # 最小空闲连接保持热连接 connection-timeout: 3000 # 从池里拿连接的最长等待毫秒 idle-timeout: 600000 # 空闲连接回收时间10 分钟 max-lifetime: 1740000 # 连接最大存活时间29 分钟必须小于 MySQL wait_timeoutmaximum-pool-size的经验值是CPU 核数 * 2 磁盘数但实际受数据库max_connections限制。二十个实例每个配 20就是 400 个连接服务端max_connections只给 500 的话稍微有点波动就打满。单实例配 10 到 20 是常见区间别贪大。max-lifetime和 MySQL 的wait_timeout联动是重点。MySQL 默认 28800 秒断开空闲连接如果你的max-lifetime比它大就会出现「池里拿到的连接已经被服务端单方面掐了」报错是Communications link failure而且只在低峰期偶发非常难查。稳妥做法是max-lifetime比wait_timeout小至少 1 分钟1740000 毫秒29 分钟是 HikariCP 官方推荐值前提是你把wait_timeout调到 30 分钟以上。connection-timeout设 3000 毫秒意思是拿不到连接就快速失败。默认 30 秒太长接口超时都到网关了你这边还在等。connection-test-query在新版本里可以不配。JDBC4 之后驱动支持isValid()HikariCP 会自动用写个SELECT 1反而是多余的一次往返。3.4 JdbcTemplate 跑通增删改查配置对了代码就简单。下面是插入并回填自增主键的写法这是 JdbcTemplate 里最容易写错的一处Repository public class UserDao { private final JdbcTemplate jdbcTemplate; public UserDao(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } // 插入并回填自增主键注意第二个参数要用 PreparedStatementCreator 形式 public long insert(String userName, String email) { String sql INSERT INTO t_user(user_name, email) VALUES (?, ?); KeyHolder keyHolder new GeneratedKeyHolder(); jdbcTemplate.update(connection - { PreparedStatement ps connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); ps.setString(1, userName); ps.setString(2, email); return ps; }, keyHolder); Number key keyHolder.getKey(); if (key null) { throw new IllegalStateException(插入未返回主键检查表是否设置了 AUTO_INCREMENT); } return key.longValue(); } // 查询用 RowMapper避免 queryForList 拿到一堆 LinkedCaseInsensitiveMap public ListString findAllNames() { return jdbcTemplate.query(SELECT user_name FROM t_user ORDER BY id DESC LIMIT 100, (rs, rowNum) - rs.getString(user_name)); } }jdbcTemplate.update(sql, args...)这个重载不返回主键想要主键必须用PreparedStatementCreator加KeyHolder的形式。getKey()在联合主键或者非自增表上会返回 null所以补了一个判空。queryForList返回的是ListMapString,Object字段名大小写取决于驱动取的时候容易踩空指针业务代码统一用RowMapper更稳。4. 连接排错五类最常见的连不上与驱动报错现场装环境这件事装成功只说明路走对了能不能长期跑稳全看排错能力。这一章的五类现场是我在不同环境里反复遇到的每条都按现象、原因、解决走一遍。4.1 连接被拒Communications link failure现象启动时报com.mysql.cj.jdbc.exceptions.CommunicationsException: Communications link failure堆栈里跟一句Connection refused。原因三种可能。一是 MySQL 服务压根没起二是bind-address只监听 127.0.0.1而连接串写的是别的地址三是防火墙拦了 3306。还有一种隐藏情况端口被别的进程占了服务显示启动成功但实际没绑上。解决先在服务器本机用telnet 127.0.0.1 3306或mysql -h 127.0.0.1 -u app_rw -p验证本机通、远程不通就是网络层。再看my.ini有没有写bind-address0.0.0.05.7 之后默认值改成了*但压缩包版本没有默认配置项不写就是只监听本地。最后看netstat -an | grep 3306确认端口处于 LISTEN 状态。4.2 认证失败Access denied 与 Public Key Retrieval现象Access denied for user app_rwlocalhost (using password: YES)或者Public Key Retrieval is not allowed。原因前者是账号匹配问题app_rw%不代表能从 localhost 用户名密码登录成功因为 MySQL 把localhost和%当两个账号。加上密码里如果有、#这类字符写在 yaml 里没加引号会被截断。后者是 MySQL 8.0 默认认证插件 caching_sha2_password 在非 SSL 连接下需要取公钥。解决账号问题用SELECT user, host FROM mysql.user;看清楚到底建了哪些缺哪个补哪个。密码问题把 yaml 里的 password 用单引号包起来。公钥问题在 URL 上加allowPublicKeyRetrievaltrue或者干脆开 SSL。还有一个下策是把账号改回mysql_native_password8.0.34 之后这个插件已经弃用8.4 移除不建议在新环境这么做。4.3 驱动缺失与时区No suitable driver / 时区值无法识别现象java.sql.SQLException: No suitable driver found for jdbc:mysql://...或者启动时报The server time zone value 中国标准时间 is unrecognized or represents more than one time zone。原因第一个是驱动包不在 classpath常见于坐标写成com.mysql:mysql-connector-javagroupId 错、或者scope写了provided导致打包时被排除。第二个是 URL 缺serverTimezone驱动无法把服务端的时区缩写映射成具体时区。解决mvn dependency:tree | grep mysql看一眼驱动到底在不在坐标对不对。时区问题在 URL 加serverTimezoneAsia/Shanghai同时把服务端default-time-zone设成08:00。两边都设比只设一边稳。4.4 连接池耗尽HikariPool 超时与泄漏现象压测或长时间运行后出现HikariPool-1 - Connection is not available, request timed out after 3000ms重启就好过一阵又出现。原因九成是连接泄漏代码里手动getConnection()之后没有 close或者用了jdbcTemplate之外的原生 JDBC 路径忘了关。剩下的一成是池子确实小了或者某条慢 SQL 占着连接不放。解决先把spring.datasource.hikari.leak-detection-threshold设成 20000毫秒超时会打印持有连接的线程堆栈直接定位到泄漏点。这是连接池的黑匣子比猜快得多。池大小要看实际并发和 SQL 耗时不是越大越好池子太大反而会把数据库压垮。4.5 中文与 Emoji 乱码字符集链路三处没对齐现象英文正常中文变成问号或乱码Emoji 直接插入报Incorrect string value。原因字符集是一条链服务端、库表、连接、客户端四段任意一段不是 utf8mb4整条链就降级。最常见的漏点是表字段建成了utf8三字节存不下四字节的 Emoji而不是utf8mb4。解决按顺序查SELECT character_set_server、SHOW CREATE DATABASE demo_app、SHOW FULL COLUMNS FROM t_user、URL 里的characterEncoding。四处都对齐到 utf8mb4 之后重启应用再测。已经建错的表用ALTER TABLE t_user CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;修但大表上这条语句会锁表要在低峰期做。注意MySQL 里的utf8不是真正的 UTF-8最多三字节。凡是看到utf8_general_ci的老表都要当作隐患记一笔。5. 装完怎么验证一条 SELECT 到完整 CRUD 的自检流程数据库和服务都装好了工程也配完了最后一步是把「能启动」变成「能跑通」。启动成功不代表连上了Spring Boot 的 HikariCP 默认懒初始化第一次真正取连接可能发生在第一个请求进来的时候这时候报错就很被动。我一般会加一个显式的健康检查接口启动时就把连接探一遍RestController RequestMapping(/db) public class DbHealthController { private final JdbcTemplate jdbcTemplate; public DbHealthController(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } // 一次性把版本、字符集、时区、连接数都查出来比单查 1 信息量大 GetMapping(/ping) public MapString, Object ping() { return jdbcTemplate.queryForMap( SELECT VERSION() AS version, character_set_database AS charset, time_zone AS tz, max_connections AS maxConn); } }启动后访问/db/ping返回体里四个字段都对得上说明驱动、URL、账号、权限、字符集、时区六件事一次全通。这一步的价值在于把分散的检查合成一次调用而不是出了事再一项项试。验证清单我固定按这个顺序走检查项命令或接口期望结果服务存活netstat -an | grep 3306处于 LISTEN账号可登mysql -h 127.0.0.1 -u app_rw -p能进 demo_app应用连通GET /db/ping返回版本与 utf8mb4写入回填POST插入一条看返回主键主键非 0中文与 Emoji插入含中文和表情的用户名查询原样返回池化复用连续调 50 次 ping无超时连接数稳定最后两项最容易被跳过但也最能暴露问题。中文和 Emoji 那条能一次性验证完字符集链路连续 50 次调用则能看出连接池是不是在泄漏。我一般会在本地把这两条跑完再交付比上线后半夜被告警叫醒强太多。还有一个习惯mvn dependency:tree的输出我会存一份到工程根目录换机器、换人接手时直接比对比口头交接靠谱。配置这东西改的时候觉得记得住过俩月全是玄学。从那以后我每次搭这套环境都会先跑一遍/db/ping再写业务代码把数据库这一层钉死之后再往上堆功能返工少很多。希望帮到你。本文还有配套的精品资源点击获取