《Spring Boot 入门》12.1 数据源与连接配置

本节把图书管理服务从内存 Map 换到真正的数据库:引入 spring-boot-starter-data-jpa 与 H2、MySQL 驱动,逐项讲清 spring.datasource.* 与 HikariCP 关键参数的默认值,用表格对比 ddl-auto 四个取值的风险,并演示 4.1 新增的惰性连接获取与启动期连不上库的真实报错。

本节目标:把「图书管理服务」的存储从内存 Map 换成数据库,讲清 spring-boot-starter-data-jpa 依赖、spring.datasource.* 配置、HikariCP 连接池参数与 spring.jpa.* 常用项,并给出启动期连不上库的排查路径。
适用版本:Spring Boot 4.1.x(Java 21)

12.1 数据源与连接配置

第 5 章到第 11 章,我们的 BookService 一直把数据放在一个 Map<Long, Book> 里。这在教学阶段没问题,但只要你重启应用,加进去的书就全没了。从本章开始,我们把「图书管理服务」的存储换成真正的数据库。整章三节共用同一个领域模型:

package com.example.bookstore.domain;

public record Book(
        Long id,
        String title,
        String author,
        String isbn,
        int publishedYear) {
}

12.1 先把数据源接通,12.2 把 Book 变成实体并抽出 BookRepository,12.3 用派生查询把「按作者查、按年份区间查」这些需求写出来。本章示例统一用 H2 内存库,因为它零安装、可重复,跑一遍就有结果;末尾再给一套 MySQL 配置供你换到真实场景。

12.1.1 一次访问要经过多少层

很多人第一次配数据源时,把所有东西都叫「数据库连接」。实际上从你的代码到磁盘上的数据,中间至少隔了四层,任何一层配错都会在启动时炸掉:

层职责对应组件
JDBC 驱动把 JDBC 调用翻译成数据库的私有协议com.mysql:mysql-connector-j、com.h2database:h2
数据源 / 连接池复用 TCP 连接、限制并发、处理失效重连HikariCP
ORM把 Java 对象映射成表行,生成 SQLHibernate 7.2
仓库抽象提供 save、findById 等方法Spring Data JPA 2025.1

Spring Boot 的自动配置把后三层都装好了,你只需要提供两样东西:驱动依赖和连接参数。理解这张表,后面的每个配置项你都能对上号。

12.1.2 引入依赖

在 pom.xml 里加一个 starter 和两个驱动(演示用 H2,真实场景用 MySQL):

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
  </dependency>

  <!-- 演示用:H2 内存数据库,随应用启停 -->
  <dependency>
    <groupId>com.h2database</groupId>
    <artifactId>h2</artifactId>
    <scope>runtime</scope>
  </dependency>

  <!-- 真实场景:MySQL 8.x -->
  <dependency>
    <groupId>com.mysql</groupId>
    <artifactId>mysql-connector-j</artifactId>
    <scope>runtime</scope>
  </dependency>
</dependencies>

spring-boot-starter-data-jpa 会传递引入 Hibernate 7.2、Spring Data JPA 2025.1 和 HikariCP 7.0——这正是 4.x 的依赖大版本口径。两个驱动都标 runtime,因为编译期我们只依赖 javax.sql.DataSource 这类标准接口,不需要具体的驱动类。

注意这里没有 spring-boot-starter-jdbc。data-jpa 已经包含了它,重复声明只会让依赖树更乱。

12.1.3 spring.datasource.* 配置项全解

最小可运行的 H2 配置只有三行。放进 src/main/resources/application.yml:

spring:
  datasource:
    url: jdbc:h2:mem:bookstore;DB_CLOSE_DELAY=-1
    username: sa
    password: ""
    driver-class-name: org.h2.Driver
  h2:
    console:
      enabled: true
      path: /h2-console
  • jdbc:h2:mem:bookstore 表示一个名为 bookstore 的内存库,进程内有效。
  • DB_CLOSE_DELAY=-1 让最后一个连接关闭后库依然保留,否则连接池一收缩数据就没了,调试时会很困惑。
  • driver-class-name 其实可以省略:Spring Boot 会从 url 的 jdbc: 前缀推断驱动。显式写上更利于排查。
  • 打开 h2-console 后,访问 http://localhost:8080/h2-console 就能看到表和数据,填 JDBC URL 时务必和上面完全一致。

常用配置项速查:

属性作用备注
spring.datasource.urlJDBC 连接串必填,决定连哪个库
spring.datasource.username用户名H2 默认 sa
spring.datasource.password密码空字符串要写 "",不能留空
spring.datasource.driver-class-name驱动类通常可省略,自动推断
spring.datasource.typeDataSource 实现类默认 Hikari 数据源,一般不改
spring.datasource.name连接池名字多数据源时用来区分
spring.datasource.hikari.*连接池参数见下一节
spring.datasource.connection-fetch连接获取时机4.1 新增,见 12.1.7

换成 MySQL,只需要改 url 和凭据,其他都不动:

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/bookstore?useSSL=false&serverTimezone=Asia/Shanghai
    username: bookstore
    password: ${DB_PASSWORD}
    driver-class-name: com.mysql.cj.jdbc.Driver

密码写成 ${DB_PASSWORD} 是第 6 章讲过的外部化配置写法,避免把明文提交进仓库。

12.1.4 HikariCP 关键参数与默认值

HikariCP 是 Boot 默认的连接池。它默认值合理,绝大多数项目不需要改;但你必须知道它们,因为线上问题十有八九出在这里。下表是 4.x 下的默认值:

参数默认值含义
maximum-pool-size10池中最多同时存在的连接数
minimum-idle同 maximum-pool-size池中保持的空闲连接数下限
connection-timeout30000 ms从池里借不到连接时的等待上限,超时抛异常
idle-timeout600000 ms(10 分钟)空闲连接被回收前的存活时间
max-lifetime1800000 ms(30 分钟)一条物理连接的最长寿命
validation-timeout5000 ms校验连接是否可用的超时
pool-nameHikariPool-1池名,日志里会出现

配置时写在 spring.datasource.hikari 下:

spring:
  datasource:
    hikari:
      maximum-pool-size: 20
      minimum-idle: 5
      connection-timeout: 3000
      max-lifetime: 1200000

几个容易踩的点:

  1. 池不是越大越好。连接数是数据库的稀缺资源,20 个应用实例各开 20 条就是 400 条,MySQL 默认 max_connections 只有 151。起步 10 条、压测后再调,比盲目调大安全得多。
  2. max-lifetime 必须小于数据库侧的连接存活上限。MySQL 默认 wait_timeout 是 28800 秒(8 小时),远大于 30 分钟,所以默认值是安全的;但若中间有防火墙掐空闲连接,就要把 max-lifetime 调到防火墙阈值以下,否则会拿到已被切断的连接。
  3. connection-timeout 不是 SQL 执行超时。它只管「借连接」这一步。SQL 本身跑太久,要另配语句超时。

12.1.5 spring.jpa.* 常用配置

数据源接通后,Hibernate 还需要知道「怎么对待表结构」。常用项如下:

spring:
  jpa:
    hibernate:
      ddl-auto: update
    show-sql: true
    open-in-view: false
    properties:
      hibernate:
        format_sql: true
  • show-sql: true 把 Hibernate 生成的 SQL 打到控制台,开发期观察行为很方便。
  • properties.hibernate.format_sql: true 让这些 SQL 换行缩进,否则是一整行,几乎没法读。
  • open-in-view 默认是 true,它会把数据库连接一直持有到视图渲染结束,容易掩盖懒加载问题。新项目建议显式关掉(12.2 会看到它对 LazyInitializationException 的影响)。

ddl-auto 是本节最需要讲透的一个。它有五个取值,其中四个最常用:

取值启动行为数据风险适用环境
none什么都不做无生产(配合迁移工具)
validate校验表结构与实体是否一致,不一致则启动失败无生产(推荐)
update增量补齐缺失的表/列不删列、语义不可控本地开发
create每次启动删表重建数据全部丢失集成测试
create-drop启动建表,关闭时删表数据全部丢失演示、临时验证

补充一个容易忽略的默认值规则:当检测到嵌入式数据库(如 H2)时,ddl-auto 默认是 create-drop;连接外部数据库时默认是 none。这就是为什么很多人本地用 H2 不写任何 ddl-auto 也能跑起来——它一直在偷偷删表重建。

12.1.6 为什么 ddl-auto 只适合开发环境

update 看起来最省事,加个字段自动补列,为什么生产不能用?三个理由:

  1. 它只加不减。删掉一个实体字段,对应的列会留在表里;改字段类型,它通常也不会动。跑久了表结构和你的实体就悄悄对不上了。
  2. 它不处理数据迁移。给一张已有数据的表加一个非空列,update 会直接失败,或者留下语义不明的默认值。
  3. 多实例会打架。生产通常多个实例同时启动,多个进程并发执行 DDL,轻则报错,重则锁表。

正确的分工是:开发和演示用 update(或 H2 默认的 create-drop),生产用 validate,真正的表结构变更交给迁移工具。第 15 章的 Flyway 就是干这个的:用版本化的 SQL 脚本管理 schema,ddl-auto 退化成一道「实体和库是否一致」的守门校验。

12.1.7 多数据源的简要说明

有些系统需要同时连两个库(比如主库 + 只读库)。Spring Boot 只自动配置一个 DataSource,第二个必须自己声明:

@Configuration
class SecondDataSourceConfig {

    @Bean
    @ConfigurationProperties("app.second-datasource")
    DataSource secondDataSource() {
        return DataSourceBuilder.create().build();
    }
}

@ConfigurationProperties 会把 app.second-datasource.* 绑到返回的 DataSource 上。要点是:一个标 @Primary 作为默认,其余的靠 @Qualifier 精确注入;如果两边都要 JPA,还要各自声明 EntityManagerFactory 和 @EnableJpaRepositories(basePackages = ...) 把仓库分到不同包。

多数据源的心智成本很高,能用单库就别上。确实需要读写分离时,优先考虑分库分表中间件或 Spring Data 的多模块能力,而不是手工拼两套 EntityManagerFactory。

12.1.8 4.1 新增:惰性 JDBC 连接获取

4.1 引入了一个新属性 spring.datasource.connection-fetch:

spring:
  datasource:
    connection-fetch: lazy

默认行为(eager)下,连接池在启动阶段就会尝试建立/校验连接,数据库不可用会直接让应用启动失败。设为 lazy 后,真正的连接获取推迟到第一次执行 SQL 时才发生,于是应用可以先把容器启动起来,等数据库恢复后再服务请求。

它适合两类场景:数据库与应用一起编排启动、数据库启动比应用慢;以及你希望「应用能起来」和「数据库能连通」两件事解耦,由健康检查而不是启动失败来暴露问题。要注意 lazy 不是免死金牌——第一次请求仍会失败,只是失败点从启动期挪到了运行期,必须配合就绪探针一起用。

12.1.9 启动时连接失败:真实报错与排查

配置写错时,最典型的报错长这样:

2026-09-26T10:00:03.118+08:00 ERROR 51234 --- [           main] com.zaxxer.hikari.pool.HikariPool        : HikariPool-1 - Exception during pool initialization.
java.sql.SQLException: Connection refused
    at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:112)
    ...
Caused by: java.net.ConnectException: Connection refused
    at sun.nio.ch.Net.pollConnect(Native Method)
    ...

看到 Exception during pool initialization,就按下面顺序查:

  1. 库起来了吗。Connection refused 基本就是端口没人监听,先确认数据库进程和端口。
  2. URL 对吗。库名拼错、端口写错、时区参数缺失(MySQL 常见)都会在这里暴露。
  3. 凭据对吗。用户名密码错误通常是 Access denied for user。
  4. 驱动在不在。忘了加驱动依赖会报 Failed to load driver class。
  5. 根本没配 URL。如果连数据源都没配,Boot 会报一句很直白的话:Failed to configure a DataSource: 'url' attribute is not specified and no embedded datasource could be configured.——看到它说明你既没配外部库,classpath 上也没有嵌入式驱动。

排查这类问题时,把 logging.level.com.zaxxer.hikari 调成 DEBUG,能看到池的创建、借出、归还全过程,比猜快得多。

小结

本节完成了从内存到数据库的切换,重点有四条:

  • 接数据库只需两样东西:驱动依赖和连接参数;spring-boot-starter-data-jpa 已把 ORM、仓库抽象和连接池带齐。
  • HikariCP 参数默认值基本够用,最需要理解的是 maximum-pool-size(别盲目调大)与 max-lifetime(要小于数据库侧的空闲上限)。
  • ddl-auto 是开发期工具:update 方便但不可控,生产请用 validate,真正的结构变更交给第 15 章的 Flyway。
  • 4.1 的 spring.datasource.connection-fetch=lazy 把连接获取推迟到首次使用,用于解耦应用启动与数据库可用性。

数据源通了,但 Book 现在还是一张表和一堆字段,没有任何 Java 侧映射。下一节我们把它变成 @Entity,并抽出 BookRepository。

阅读导航:上一节:11.3 拦截器与过滤器 · 下一节:12.2 实体与 Repository 。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「java」更多文章

  1. 《Spring Boot 入门》18.3 打包与运行
  2. 《Spring Boot 入门》18.2 实现
  3. 《Spring Boot 入门》18.1 需求与设计