ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

SpringBoot用JDBC直连MySQL:连接串配置、连接池调优与避坑指南

2026/10/8 8:28:57 拓冰建站 浏览量
SpringBoot用JDBC直连MySQL:连接串配置、连接池调优与避坑指南 简介一份基于Java的SpringBoot框架、通过JDBC连接MySQL数据库的完整工程方案主要面向刚开始接触SpringBoot与数据持久化开发的Java初学者也适合需要快速搭建数据访问层代码参考的开发者。包内既包含可运行的Java工程源码与编译后的class字节码也配有MySQL安装程序与可视化工具安装包同时提供SQL初始化脚本和XML、properties等配置文件方便从环境安装、建库建表到代码调试一体化实践。整个压缩包共136个文件体积约637.42MB其中dll动态库和exe、msi安装程序占比最高承载着运行库与安装部件另外还有少量Java、class、HTML、PHP等文件分别对应程序逻辑、网页辅助页面和辅助脚本并附带PDF说明文档和txt笔记便于查阅细节。资源整体目录清晰覆盖连接配置、控制器编写、用户实体定义等关键环节从内容预览即可看到控制器、实体类、启动类等典型SpringBoot工程结构。目前已有598人学习下载对于希望对照教程完整走通JDBC连接MySQL流程的读者来说这套自带安装包和源码的资料可以大幅减少环境配置时间尤其适合离线实操与随堂复盘无论是初次接触JDBC还是想快速复用现成配置都能从中获得明确参考路径。1. SpringBoot 用 JDBC 直连 MySQL这套资源解决的是「连得上」到「跑得稳」在 MyBatis 和 JPA 铺天盖地的今天SpringBoot 项目里还在手写 JDBC 的场景其实比想象中多老系统迁移、报表模块、临时数据同步脚本以及面试题里必问的数据库连接原理。这套资源不是给你一个全自动 ORM而是把 SpringBoot 用 JDBC 直连 MySQL 的完整链路摊开——驱动怎么选、连接串每个参数在防什么、JdbcTemplate 怎么写、事务怎么管、连接池怎么调全都在里面源码可以直接改包名跑起来。适合两类人一类是刚接触 SpringBoot 的 Java 开发者想搞清楚数据库连接这层黑匣子别被框架封装蒙住眼另一类是项目里被 MyBatis 封装惯了、遇到连接超时、时区报错、连接被断就无从下手的同学。看完你会明白JDBC 直连不是倒退而是帮你把数据库连接的每一层逻辑攥在自己手里。2. 环境与驱动准备MySQL 安装、JDBC 驱动坐标与连接串参数2.1 MySQL 5.7 还是 8.0选型差异直接决定驱动类名拿到这套资源先别急着跑第一件事是确认本机 MySQL 版本。5.7 和 8.0 在 JDBC 接入上最大的差异不在 SQL 语法而在认证插件和驱动类名。MySQL 5.7 默认用 mysql_native_password 认证对应旧驱动类 com.mysql.jdbc.Driver连接串不传 useSSL、serverTimezone 通常也能连上。MySQL 8.0 换成 caching_sha2_password必须用新驱动 com.mysql.cj.jdbc.Driver连接串缺了 allowPublicKeyRetrievaltrue 会直接报错。两版差异整理成一张表更方便对照对比项MySQL 5.7MySQL 8.0默认认证插件mysql_native_passwordcaching_sha2_password驱动类名com.mysql.jdbc.Drivercom.mysql.cj.jdbc.Driver连接串必备参数无特殊要求serverTimezone、allowPublicKeyRetrievalSpringBoot 2.x 自动引入需手动指定 5.1.x默认 8.0.x这里有个版本陷阱得点名。SpringBoot 3.x 之后 MySQL 驱动坐标变了2.x 时代是mysql:mysql-connector-java3.x 之后变成com.mysql:mysql-connector-j。你从旧教程复制依赖又碰上高版本 SpringBoot大概率撞上「驱动类找不到」的怪问题。判断标准很直接maven 仓库里驱动的 groupId 是 com.mysql 还是 mysql一看便知。提示判定版本最稳的方法是看驱动包名。5.1.x 用 com.mysql.jdbc.Driver8.x 用 com.mysql.cj.jdbc.Driver两者互换必报 ClassNotFoundException。2.2 Windows 10 上安装 MySQL 5.7.44初始化、服务注册与密码重置本机没装 MySQL 的话建议装 5.7.44 的 zip 版比安装向导版好控制得多。下载解压后在解压目录下新建 my.ini关键配置就五行[mysqld] basedirD:/mysql-5.7.44-winx64 datadirD:/mysql-5.7.44-winx64/data port3306 character-set-serverutf8mb4basedir 和 datadir 一定改成你自己的绝对路径路径里不要有中文和空格否则初始化阶段可能直接失败。接着用管理员身份打开 cmd切到解压目录的 bin 目录执行mysqld --initialize-insecure --console mysqld --install MySQL57 net start MySQL57 mysql -u root --skip-password第一行初始化数据目录--initialize-insecure生成空密码 root 账户方便第一次登录第二行把 MySQL 注册成 Windows 服务服务名可自定义第三行启动服务第四行免密登录。登录后立刻改密码ALTER USER rootlocalhost IDENTIFIED BY 你自己的密码; FLUSH PRIVILEGES;这里有几个常见卡点提示 mysql 不是内部命令是 bin 目录没加 PATHcd 过去再执行即可初始化报找不到 data 目录手动建一个空的 data 文件夹再跑 initialize启动服务报 1067 错误多半是缺 VC 运行库装 2015-2019 Redistributable 就能解决。这套流程在 5.7.44 和 8.0 上各跑过两遍按这个顺序走基本一遍过。2.3 JDBC 连接串每个参数都在防什么坑连接串是整套方案里最容易被忽略又最容易翻车的部分。一段完整的 8.0 连接串长这样jdbc:mysql://localhost:3306/mall?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltruerewriteBatchedStatementstrue逐个说下参数的作用参数作用不设置的后果useUnicodetruecharacterEncodingutf8字符集中文乱码useSSLfalse关闭 SSL 握手本地握手慢、日志刷屏serverTimezoneAsia/Shanghai指定时区8.0 驱动启动即报错allowPublicKeyRetrievaltrue8.0 公钥获取连接被拒rewriteBatchedStatementstrue批处理合并批量插入慢一个数量级useUnicodetrue 配合 characterEncodingutf8是保证中文不乱码的标准搭配MySQL 服务端字符集最好也设 utf8mb4注意 MySQL 里的 utf8 不是完整 UTF-8utf8mb4 才是。useSSLfalse 适合本地开发和内网跨公网访问生产库时我会改成 true 并配证书别图省事。serverTimezone 是 8.0 驱动下的必现问题后面避坑章单独讲。allowPublicKeyRetrievaltrue 同样只在 8.0 下需要公网环境有中间人风险正确做法是导入服务端证书而不是长期开着这个开关。rewriteBatchedStatements 默认是 false不打开JDBC 批处理会退化成逐条执行性能差距能到一个数量级。这些参数不是越多越好。资源里的默认配置对应本地开发和内网测试场景生产环境请按安全要求逐个复核。3. 数据源与 JdbcTemplate 落地从配置到第一个增删改查3.1 pom.xml 依赖只需要两个 starterSpringBoot 接 MySQL 的最小依赖只有两个——spring-boot-starter-jdbc 和 MySQL 驱动。前者自带数据源自动配置和 JdbcTemplate不需要手动引入 commons-dbcp 或 c3p0。dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-jdbc/artifactId /dependency dependency groupIdmysql/groupId artifactIdmysql-connector-java/artifactId scoperuntime/scope /dependency第一个依赖会在自动配置阶段创建 DataSource 和 JdbcTemplate 两个 Bean第二个依赖声明 MySQL 驱动scope 是 runtime编译期不需要引用驱动类运行期才加载。SpringBoot 3.x 下第二个依赖改成com.mysql:mysql-connector-j其他不变。这里有个很多人踩过的点SpringBoot 2.x 会自动引入 HikariCP 作为默认连接池所以 pom 里看不到连接池依赖但 application.yml 里写spring.datasource.hikari开头的配置是生效的。如果项目强行加了 druid-spring-boot-starter 又没排掉 HikariCP会出现两个连接池 Bean 打架表现为 druid 参数配了但监控页面全是 0。3.2 application.yml 数据源配置逐项说明资源里的 application.yml 建议直接拿来改账号密码参数按生产习惯调的spring: datasource: url: jdbc:mysql://localhost:3306/mall?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/ShanghaiallowPublicKeyRetrievaltruerewriteBatchedStatementstrue username: root password: root123456 driver-class-name: com.mysql.cj.jdbc.Driver hikari: minimum-idle: 5 maximum-pool-size: 20 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 connection-test-query: SELECT 1driver-class-name 显式声明驱动类名SpringBoot 虽然能自动推断但多数据源或特殊版本场景容易推错显式写最稳。HikariCP 的 minimum-idle 是最小空闲连接数maximum-pool-size 是最大连接数connection-timeout 是拿连接的最大等待时间单位毫秒超过抛异常。idle-timeout 是空闲连接存活时间max-lifetime 是连接最长生命周期这两个必须配合 MySQL 的 wait_timeout 一起看否则会遇到经典的 8 小时断连问题。一个容易忽略的点max-lifetime 一定要小于 MySQL 服务端的 wait_timeout。MySQL 默认 wait_timeout 是 28800 秒池中连接空闲超过这个时间会被服务端断开如果 max-lifetime 反而更大客户端会拿到一条已经死掉的连接。这里设 1800000 毫秒30 分钟强制客户端在服务端回收之前主动丢弃。connection-test-query 配了 SELECT 1HikariCP 交付连接前会做一次探活。HikariCP 官方认为连接测试不是必需的但国内网络环境和中间层设备复杂开着更保险。3.3 JdbcTemplate 增删改查参数绑定与 RowMapper数据源配好后JdbcTemplate 是 SpringBoot 官方给的 JDBC 封装它在原生 Connection 和 Statement 之上加了参数绑定、结果集映射和异常翻译。资源里附的 UserDao 核心方法拆开看Repository public class UserDao { private final JdbcTemplate jdbcTemplate; public UserDao(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } public ListUser findByAge(int minAge) { String sql SELECT id, name, age, email FROM user WHERE age ?; return jdbcTemplate.query(sql, new BeanPropertyRowMapper(User.class), minAge); } public int insertUser(User user) { String sql INSERT INTO user(name, age, email) VALUES(?, ?, ?); return jdbcTemplate.update(sql, user.getName(), user.getAge(), user.getEmail()); } public int updateEmail(int id, String email) { String sql UPDATE user SET email ? WHERE id ?; return jdbcTemplate.update(sql, email, id); } public int deleteById(int id) { String sql DELETE FROM user WHERE id ?; return jdbcTemplate.update(sql, id); } }构造器注入 JdbcTemplate 是 Spring 推荐的方式比 Autowired 字段注入更好测试。query 方法接收三个参数——SQL、RowMapper、可变参数。BeanPropertyRowMapper 自动把结果集列名转成驼峰属性名但下划线列 create_time 映射不到 createTime要么自己实现 RowMapper要么 SQL 里用 AS 别名。update 方法返回受影响行数用于判断删除或更新是否真正命中数据。注意问号占位符对应 PreparedStatement天然防 SQL 注入。动态拼接 SQL 属于高危写法再急也别这么干这是 JDBC 场景的基本底线。3.4 Transactional 在 JDBC 场景的实际行为回滚与隔离级别Spring 的 Transactional 在 JDBC 场景下用 AOP 包了一层事务管理底层把 Connection 的 autocommit 关掉执行完按结果提交或回滚。这个机制遇到自调用会失效先记住这一点。Service public class TransferService { private final JdbcTemplate jdbcTemplate; public TransferService(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } Transactional(rollbackFor Exception.class, isolation Isolation.READ_COMMITTED, timeout 5) public void transfer(int fromId, int toId, BigDecimal amount) { jdbcTemplate.update(UPDATE account SET balance balance - ? WHERE id ?, amount, fromId); if (amount.compareTo(BigDecimal.ZERO) 0) { throw new IllegalArgumentException(转账金额不能为负); } jdbcTemplate.update(UPDATE account SET balance balance ? WHERE id ?, amount, toId); } }rollbackFor Exception.class 必须写。Spring 默认只回滚 RuntimeException 和 Error业务异常和检查异常不加这个参数就不会回滚数据会悬在半空。isolation 用 READ_COMMITTED是大多数业务系统的合理选择MySQL 默认是 REPEATABLE_READ账务类高并发场景下 READ_COMMITTED 配合 InnoDB 行锁更可控。timeout 5 单位是秒超过强制回滚防止长事务锁表拖垮连接池。关键细节Transactional 必须通过 Spring 代理调用才生效。同一个类里 A 方法调本类的 B 方法B 上的注解会被跳过。资源里的调用方是独立的 Service就是为了避开自调用陷阱。4. 批处理与事务模板性能上量后的三个必改点4.1 批量插入的三种写法逐条、batchUpdate、rewriteBatchedStatements数据量小怎么插都无所谓一旦到报表导入、数据迁移这种几万条起步的场景逐条 insert 的性能会让你怀疑人生。三种写法我实际测过差距非常直观循环单条 update一万条大概 8 到 10 秒batchUpdate 不开 rewriteBatchedStatements3 到 4 秒batchUpdate 配合连接串里的 rewriteBatchedStatementstrue一万条压到 1 秒以内。public void batchInsert(ListUser users) { String sql INSERT INTO user(name, age, email) VALUES(?, ?, ?); jdbcTemplate.batchUpdate(sql, users, 500, (ps, user) - { ps.setString(1, user.getName()); ps.setInt(2, user.getAge()); ps.setString(3, user.getEmail()); }); }batchUpdate 的重载方法接收四个参数——SQL、集合、批次大小、参数设置回调。500 表示每攒 500 条再发给数据库这个值不是越大越好MySQL 默认 max_allowed_packet 是 4MB单批数据超限会直接报 packet too large。回调里 ps.setString、ps.setInt 的顺序必须和 SQL 占位符一一对应。另外注意重载区别如果集合是 ListObject[]不用写回调直接传二维数组两种重载别混用。rewriteBatchedStatements 的原理是驱动把多条 INSERT 合并成一条多 VALUES 语句网络往返次数大幅减少。但它只对纯 INSERT 有明显效果UPDATE 和 DELETE 的批处理合并收益有限。还有个副作用合并语句后拿自增主键的方式会变批量插入后再取 generated keys 需要专门处理。如果业务依赖插入后马上拿主键先逐条插入或者改用别的主键策略。4.2 手动事务模板 TransactionTemplate注解之外的另一只手注解事务好用但在两种场景会卡壳代码需要根据运行条件决定事务边界或者事务代码跑在非 Spring 管理的线程里。资源里提供了 TransactionTemplate 的编程式写法Service public class CleanupService { private final TransactionTemplate transactionTemplate; private final JdbcTemplate jdbcTemplate; public CleanupService(TransactionTemplate transactionTemplate, JdbcTemplate jdbcTemplate) { this.transactionTemplate transactionTemplate; this.jdbcTemplate jdbcTemplate; } public void cleanExpiredLogs(int days) { transactionTemplate.execute(status - { jdbcTemplate.update(DELETE FROM log WHERE create_time ?, LocalDate.now().minusDays(days)); int count jdbcTemplate.queryForObject(SELECT COUNT(*) FROM log, Integer.class); if (count 100000) { status.setRollbackOnly(); return Boolean.FALSE; } jdbcTemplate.update(UPDATE summary SET log_count ?, count); return Boolean.TRUE; }); } }TransactionTemplate 的 execute 方法接收一个回调回调里返回布尔值或对象。setRollbackOnly() 可以在事务中途主动标记回滚比抛异常优雅适合「检查到异常状态但不想走异常栈」的场景。execute 返回 Boolean.TRUE 对应提交FALSE 对应回滚。隔离级别和超时时间可以在构造 TransactionTemplate 时设置资源里默认跟着 SpringBoot 自动配置走用到了再显式覆盖。这里有个使用习惯值得注意TransactionTemplate 属于编程式事务每次 execute 开启一个新事务。千万不要在 execute 回调里再调另一个带 Transactional 的方法内外层事务传播行为叠加后提交回滚会变得很难预期。4.3 分页查询LIMIT OFFSET 与 count 的配合JDBC 层面做分页没有 MyBatis PageHelper 那种插件式方案只能自己写 LIMIT。资源里的做法是查一次 count 再查一页数据标准的物理分页public PageResultUser page(int pageNum, int pageSize) { int offset (pageNum - 1) * pageSize; String countSql SELECT COUNT(*) FROM user; String dataSql SELECT id, name, age, email FROM user ORDER BY id DESC LIMIT ? OFFSET ?; Integer total jdbcTemplate.queryForObject(countSql, Integer.class); ListUser list jdbcTemplate.query(dataSql, new BeanPropertyRowMapper(User.class), pageSize, offset); return new PageResult(total, list); }OFFSET 的计算公式要记牢pageNum 从 1 开始offset (pageNum - 1) * pageSize。jdbcTemplate.query 的可变参数顺序和 SQL 占位符顺序一致这里是先 pageSize 后 offset对应 LIMIT 后面的两个问号。count 查询和列表查询是两条 SQL中间如果有并发写入两者可能不一致后台管理场景可接受对账类页面建议放到同一个事务里执行。分页还有个隐藏性能坑数据量大后 OFFSET 越来越慢因为 MySQL 要扫描并丢弃前 offset 行。几十万行的表翻到后面几页耗时从毫秒级涨到秒级很正常。资源里的 SQL 注释位置标了替换方案核心表改成基于主键游标的分页也就是记住上一页最后一条 id用 WHERE id ? LIMIT ? 代替 OFFSET。这是性能上量之后才需要的改造别在一开始过度设计。5. 避坑排查JDBC 连 MySQL 的 5 个高频翻车现场5.1 Public Key Retrieval is not allowed现象SpringBoot 启动时或执行第一条 SQL 时报错错误信息明确写着 Public Key Retrieval is not allowed。原因MySQL 8.0 默认认证插件是 caching_sha2_password客户端第一次连接需要向服务端索取公钥做密码加密传输JDBC 驱动出于安全考虑默认不允许明文获取公钥。5.7 及更早版本没这个问题所以老教程里看不到这个参数。解决连接串加 allowPublicKeyRetrievaltrue。不想放开就在 MySQL 里把用户认证插件改回 mysql_native_password执行 ALTER USER rootlocalhost IDENTIFIED WITH mysql_native_password BY 密码; 本地开发选前者最省事安全要求高的环境选后者并在服务端维护好 SSL。5.2 Communications link failure三类原因逐个排现象应用启动连接报 Communications link failure或者运行中突然大量出现提示 The last packet successfully received from the server was...原因这个报错是综合性的排过几十次九成归到三类MySQL 服务没启动、3306 端口被防火墙挡、host 配错。不是玄学是链路某一环断了。解决先在命令行验证服务状态。netstat -ano | findstr 3306 看端口是否监听telnet 127.0.0.1 3306 看 TCP 层通不通再用 mysql -u root -p 实际登录一次排除账号密码问题。三步定位到具体环节。如果本机 telnet 通但应用连不上检查连接串 host 是不是写了 localhost——Java 解析 localhost 有时会走 IPv6 的 ::1而 MySQL 只监听 IPv4这种情况改成 127.0.0.1 立刻就好。这是最隐蔽的一类查了半天防火墙结果死在 IPv6 解析上。提示localhost 解析成 ::1 的问题排查成本很高连接串统一写 127.0.0.1省掉一整类事故。5.3 serverTimezone 时区错乱与 8 小时连接断开现象启动报 The server time zone value is unrecognized或者查出来的时间比数据库实际时间少 8 小时再或者连接池里的连接隔一段时间再用就报 connection is closed。原因时区报错是 8.0 驱动必须显式指定 serverTimezone时间差 8 小时是应用 JVM 时区与数据库时区不一致连接断开是 MySQL 服务端 wait_timeout 默认 8 小时回收了空闲连接连接池不知道还把死连接发给应用。解决连接串固定加 serverTimezoneAsia/Shanghai最通用。连接池 max-lifetime 必须小于 wait_timeout推荐设 30 分钟1800000 毫秒让客户端主动丢弃而不是等服务端来杀。JVM 时区在启动参数加 -Duser.timezoneAsia/Shanghai 兜底。三个点一起改时间类和断连问题基本绝迹。5.4 Access denied for user密码错误与 host 限定的双重陷阱现象通信正常但登录被拒错误信息是 Access denied for user rootlocalhost (using password: YES)。原因要么账号密码真不对要么账号存在但 host 不匹配。MySQL 账号由 user host 联合决定rootlocalhost 和 root% 是两个不同账号应用从 127.0.0.1 连走 localhost 规则从其他机器连走 % 规则。解决先用命令行登录验证密码。确认密码没问题后查 mysql.user 表SELECT user, host, plugin FROM mysql.user WHERE userroot; 如果只有 localhost 且应用需要远程连接执行 CREATE USER root% IDENTIFIED BY 密码; 再授权。注意 8.0 下新建用户默认还是 caching_sha2_password连接串的 allowPublicKeyRetrievaltrue 必须带上否则远程连会卡在 5.1 那个报错上。5.5 连接池连接失效connection is closed 与 SELECT 1 探活现象应用跑一段时间后随机某次请求报 Connection is closed重启后恢复过一阵复发或者压测时并发稍高就大量连接池超时抛 HikariPool timeout 异常。原因底层连接被数据库或中间件回收连接池里的对象已失效。wait_timeout 8 小时是最常见元凶另一个是 maximum-pool-size 配太小峰值并发高时连接被占满后续请求全卡在 connection-timeout 等待上。解决三个参数一起调max-lifetime 设 180000030 分钟connection-test-query 配 SELECT 1让 HikariCP 交付前探活maximum-pool-size 按并发调整HikariCP 官方建议是cores * 2 effective_spindle_countSSD 环境下 20 到 50 是常见范围。同时在 MySQL 服务端把 wait_timeout 调小到和连接池对齐比如 SET GLOBAL wait_timeout3600两边数值匹配后死连接出现频率会大幅下降。6. 进阶用 Actuator 验证连接池调优效果而不是靠感觉6.1 调优最怕调完不知道有没有效果HikariCP 参数上一章给了一组生产值但每个系统并发模型不同直接抄配置不如掌握验证方法。SpringBoot Actuator 能把 HikariCP 实时指标暴露出来这是我每套 JDBC 项目必做的一步。先加依赖dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-actuator/artifactId /dependency再在 application.yml 放行端点management: endpoints: web: exposure: include: health,metrics启动后访问 /actuator/metrics/hikaricp.connections.active 看活跃连接数访问 /actuator/metrics/hikaricp.connections.pending 看排队等待的请求数。pending 持续大于 0 说明连接池不够用active 长期低于 maximum-pool-size 的 30%说明配多了可以往下调省内存。光看默认 health 不够我还自定义了一个 DatabaseHealthIndicator用 JdbcTemplate 执行 SELECT 1把数据库探活做成独立健康检查端点Component public class DatabaseHealthIndicator implements HealthIndicator { private final JdbcTemplate jdbcTemplate; public DatabaseHealthIndicator(JdbcTemplate jdbcTemplate) { this.jdbcTemplate jdbcTemplate; } Override public Health health() { try { Integer r jdbcTemplate.queryForObject(SELECT 1, Integer.class); return r ! null r 1 ? Health.up().build() : Health.down().build(); } catch (Exception e) { return Health.down(e).build(); } } }查询能返回 1 就 UP抛异常就 DOWN监控系统直接拉这个端点数据库断了 30 秒内就能告警。建议你拿到资源后先跑一遍这个监控链路再回头调连接池参数手里有指标支撑决策比靠猜稳得多。从那以后我每次新建 JDBC 项目都会强制走一遍最小连接验证命令行登录确认账号密码、连接串五个参数逐个核对、max-lifetime 与 wait_timeout 对齐、最后看一眼 Actuator 指标再放业务代码。这套流程挡掉了至少七成生产环境连接类事故希望帮到你。本文还有配套的精品资源点击获取