MySQL连接需配置serverTimezone=UTC,JdbcTemplate的queryForObject要求有且仅一行否则抛异常,驱动类名须为com.mysql.cj.jdbc.Driver,测试需加@Transactional自动回滚。

application.yml 里 MySQL 连接配置写不对,spring.datasource.url 最容易漏掉 serverTimezone=UTC
Spring Boot 启动时连不上 MySQL,十次有八次是 url 缺少时区参数。MySQL 8+ 默认要求显式指定时区,否则抛 java.sql.SQLException: The server time zone value '...' is unrecognized。
正确写法必须带 serverTimezone,且推荐用 UTC(避免本地时区夏令时等干扰):
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver-
useSSL=false:开发环境可关,生产务必配 TLS -
allowPublicKeyRetrieval=true:MySQL 8.0.13+ 密码认证方式变更后必需 -
driver-class-name必须显式声明,Spring Boot 2.4+ 不再自动推断 - 注意
&是 YAML 中的特殊字符,要用&转义,不是&
JdbcTemplate 查询空结果不报错,但 queryForObject 找不到数据会直接抛 EmptyResultDataAccessException
这是最常踩的坑:以为 queryForObject 返回 null,其实它严格要求“有且仅有一行”,没数据或有多行都炸。
常见场景:查用户登录态、根据 ID 取单条记录。别硬扛异常,该用就用 query + isEmpty() 或 queryOptional:
立即学习“Java免费学习笔记(深入)”;
List<Map<String, Object>> result = jdbcTemplate.query("SELECT * FROM user WHERE id = ?",
new Object[]{1L},
new ColumnRowMapper());- 用
queryForObject前先确认业务逻辑是否真能保证“必有一条” - 想安全取单条?改用
jdbcTemplate.query("...", args, rs -> rs.next() ? mapRow(rs, 0) : null) -
queryForList和query返回空集合,不会抛异常
MySQL 驱动版本和 Spring Boot 版本不匹配,ClassNotFoundException: com.mysql.jdbc.Driver 或连接卡死
Spring Boot 2.4+ 默认使用 MySQL Connector/J 8.x,旧版驱动类名从 com.mysql.jdbc.Driver 改成 com.mysql.cj.jdbc.Driver。如果还写老类名,启动就报 ClassNotFoundException;如果依赖没对齐,可能卡在连接池初始化不动。
- 检查
pom.xml是否引入了mysql:mysql-connector-java,且版本 ≥ 8.0.28 - 确认
application.yml中driver-class-name是com.mysql.cj.jdbc.Driver - 若用 Gradle,避免同时引入
mysql-connector-java和mysql-connector-j—— 后者是新包名,前者已废弃 - IDEA 里右键 Maven → Reload 后,点开 External Libraries 看实际加载的是哪个 JAR
测试 JdbcTemplate 时没清空表或事务没回滚,testInsertThenQuery 第二次跑就失败
单元测试默认不开启事务,插入的数据会真实落库,下次跑同一测试可能因主键冲突或条件重复而失败。
解决方法很简单:加 @Transactional 注解,让测试方法运行在事务中,方法结束自动 rollback:
@SpringBootTest
@Transactional
class UserDaoTest {
@Test
void testInsertThenQuery() {
jdbcTemplate.update("INSERT INTO user(name) VALUES(?)", "test");
Integer count = jdbcTemplate.queryForObject("SELECT COUNT(*) FROM user WHERE name = ?",
Integer.class, "test");
assertThat(count).isEqualTo(1);
}
}- 不需要手动
@Rollback,@Transactional默认就是回滚 - 别在测试里用
@Sql加载 SQL 文件后又手动 insert —— 容易时序错乱 - 如果测试要验证“事务真的没提交”,可以另起一个非事务线程去查库,但绝大多数情况没必要
真正麻烦的是嵌套事务、异步调用、或者用了 @Modifying 的自定义 Repository 方法——那些地方的事务边界容易被忽略,得看实际执行时的代理链和传播行为。










