目前的 InMemoryHealthStatusRepository 可以保存每个服务的最新状态,但数据只存在于当前应用进程中。应用重启后数据会消失,部署多个实例时,每个实例也会拥有互不相同的 Map。要让 Repository 使用真正的持久化存储,第一步不是立即编写 SQL,而是先让 Spring Boot 建立可靠的数据库连接。
本章准备一个本地 PostgreSQL,添加 JDBC 依赖,配置 DataSource,并通过测试确认应用确实能够从连接池取得可用连接。下一章再使用自动配置好的连接执行 SQL。
1. 连接结构
Java 应用访问 PostgreSQL 时,中间会经过几层协作对象:
1HealthStatusRepository2-> Spring JDBC3-> DataSource4-> 连接池中的 Connection5-> PostgreSQL JDBC 驱动6-> PostgreSQL 服务
这些名称经常一起出现,但职责并不相同:
| 名称 | 当前项目中的职责 |
|---|---|
| PostgreSQL | 独立运行的数据库服务,真正保存表和数据 |
| JDBC | Java 访问关系数据库的一套标准 API |
| PostgreSQL 驱动 | 把 JDBC 调用转换成 PostgreSQL 能理解的网络协议 |
Connection | 一次数据库会话,可以执行语句、提交或回滚事务 |
DataSource | 获取数据库连接的标准入口 |
| 连接池 | 复用一组连接,避免每次请求都重新建立网络连接 |
| Spring JDBC | 管理常见 JDBC 流程,让业务代码专注于 SQL 和结果映射 |
Repository 最终会依赖 Spring JDBC,而不是在每个方法中直接读取用户名、密码并创建连接。Spring Boot 根据依赖和配置创建 DataSource Bean,Spring JDBC 再从这个 Bean 获取连接。
DataSource 不等于某一条固定连接。它更像一个连接提供者:调用方使用连接时从连接池借出,用完后归还。连接池可以控制最大连接数、等待时间和连接生命周期,避免数据库被无限制的新连接耗尽。
2. 准备 PostgreSQL
如果本机已经有可访问的 PostgreSQL,可以直接准备一个名为 spring 的数据库和用户,然后跳到下一节。为了让开发环境容易复现,本章也提供一份 Docker Compose 配置:
01name: spring-health0203services:04postgres:05image: postgres:1706environment:07POSTGRES_DB: spring08POSTGRES_USER: spring09POSTGRES_PASSWORD: spring10ports:11- "5432:5432"12volumes:13- postgres_data:/var/lib/postgresql/data14healthcheck:15test: ["CMD-SHELL", "pg_isready -U spring -d spring"]16interval: 5s17timeout: 3s18retries: 101920volumes:21postgres_data:
这里的数据库名、用户名和密码都使用 spring,仅用于本地教学环境。postgres_data 是命名卷,删除或重建容器时数据仍然保留;执行 docker compose down -v 才会连同该项目的数据卷一起删除。
启动数据库并查看健康状态:
1docker compose up -d2docker compose ps
状态变为 healthy 后,可以先在容器内使用 psql 验证数据库本身:
1docker compose exec postgres psql -U spring -d spring -c 'select current_database(), current_user;'
预期能够看到数据库名和用户都为 spring。这一步只证明 PostgreSQL 已经可以工作,还没有证明 Java 应用拥有驱动和正确配置。
3. JDBC 依赖
在 pom.xml 的 dependencies 中加入 Spring JDBC Starter 和 PostgreSQL 驱动:
01<dependency>02<groupId>org.springframework.boot</groupId>03<artifactId>spring-boot-starter-jdbc</artifactId>04</dependency>0506<dependency>07<groupId>org.postgresql</groupId>08<artifactId>postgresql</artifactId>09<scope>runtime</scope>10</dependency>
两项依赖解决的是不同问题:
| 依赖 | 提供的能力 |
|---|---|
spring-boot-starter-jdbc | Spring JDBC、事务基础设施和默认连接池 |
org.postgresql:postgresql | PostgreSQL 对 JDBC 标准的具体实现 |
Starter 会传递引入 HikariCP。只要没有提供其他连接池或自定义 DataSource,Spring Boot 会优先使用 HikariCP 创建池化数据源。PostgreSQL 驱动只在运行应用和测试连接时需要,因此使用 runtime 作用域;业务源码通过 JDBC 标准和 Spring JDBC API 编译,不必直接依赖驱动实现类。
版本继续由 Spring Boot Parent 管理,不需要在这两个依赖上手写版本号。可以用依赖树确认最终解析结果:
1./mvnw dependency:tree -Dincludes=org.springframework:spring-jdbc,org.postgresql:postgresql,com.zaxxer:HikariCP
4. DataSource 配置
Spring Boot 使用 spring.datasource 下的属性配置数据源。把下面内容与已有的 application.yaml 合并,不要删除前面章节中的 app.health 和 spring.mvc 配置:
1spring:2datasource:3url: ${DB_URL:jdbc:postgresql://localhost:5432/spring}4username: ${DB_USERNAME:spring}5password: ${DB_PASSWORD}6hikari:7maximum-pool-size: 108connection-timeout: 3000
JDBC URL 可以拆成下面几部分:
1jdbc:postgresql://localhost:5432/spring2│ │ │ │ └─ 数据库名3│ │ │ └────── 端口4│ │ └──────────────── 主机5│ └───────────────────────────── 数据库类型6└────────────────────────────────── JDBC 协议前缀
${DB_URL:...} 表示优先读取环境变量 DB_URL,没有设置时使用冒号后的本地默认值。用户名采用相同方式。密码写成 ${DB_PASSWORD},不提供默认值,避免把可直接使用的密码提交到仓库。
本地启动前设置环境变量:
1export DB_PASSWORD='spring'2./mvnw spring-boot:run
也可以只为当前命令提供变量:
1DB_PASSWORD='spring' ./mvnw spring-boot:run
maximum-pool-size 表示连接池最多管理 10 条连接,不表示启动时会立即建立 10 条连接。connection-timeout 表示池中暂时没有可用连接时,调用方最多等待 3000 毫秒。连接池大小应该结合数据库允许连接数、应用实例数和实际并发调整,而不是简单设置得越大越好。
通常不需要设置 driver-class-name。Spring Boot 可以根据 jdbc:postgresql: URL 和 classpath 中的驱动推断驱动类。只有自动识别失败或需要使用特殊驱动实现时才应显式配置。
5. 自动配置
添加依赖并提供 URL 后,Spring Boot 会按条件完成下面的组装:
1读取 spring.datasource 配置2-> 在 classpath 中找到 PostgreSQL Driver3-> 在 classpath 中找到 HikariCP4-> 创建 HikariDataSource Bean5-> 基于 DataSource 创建 Spring JDBC 组件6-> Repository 可以通过构造方法声明依赖
这仍然遵循前面学习的 IoC。业务类只声明自己需要 DataSource 或更上层的 JdbcClient,不负责读取环境变量,也不负责决定连接池实现。
下面的写法虽然能够得到连接,却不适合作为 Repository 的日常实现:
1Connection connection = DriverManager.getConnection(url, username, password);
它把配置读取、连接创建和数据访问混在同一个类中,还绕过了 Spring 管理的连接池和事务同步。后面的 Repository 会注入 JdbcClient,由 Spring JDBC 从当前事务关联的 DataSource 中取得连接。
还要注意,创建 DataSource Bean 不一定等于应用启动时已经完成数据库通信。连接池可能延迟到第一次借用连接时才真正连接 PostgreSQL,所以仅看到应用启动成功不能完全证明 URL、账号和网络都正确。
6. 连接验证
为了验证完整链路,可以编写一个只负责基础设施检查的集成测试:
01package cn.usehook.spring;0203import java.sql.Connection;04import java.sql.SQLException;05import javax.sql.DataSource;06import org.junit.jupiter.api.Test;07import org.springframework.beans.factory.annotation.Autowired;08import org.springframework.boot.test.context.SpringBootTest;0910import static org.assertj.core.api.Assertions.assertThat;1112@SpringBootTest13class DatabaseConnectionTests {1415private final DataSource dataSource;1617DatabaseConnectionTests(@Autowired DataSource dataSource) {18this.dataSource = dataSource;19}2021@Test22void connectsToPostgresql() throws SQLException {23try (Connection connection = dataSource.getConnection()) {24assertThat(connection.isValid(2)).isTrue();25assertThat(connection.getMetaData().getDatabaseProductName())26.isEqualTo("PostgreSQL");27}28}29}
@SpringBootTest 启动应用上下文,因此测试取得的是 Spring Boot 实际配置的 DataSource。getConnection() 从连接池借出连接,try 语句结束时调用 close();对于池化连接,close() 通常表示归还连接,而不是直接关闭底层网络连接。
确保 PostgreSQL 容器已经就绪,再运行测试:
1DB_PASSWORD='spring' ./mvnw -Dtest=DatabaseConnectionTests test
连接失败时,可以按错误出现的位置排查:
| 现象 | 常见原因 | 检查方向 |
|---|---|---|
| 找不到 JDBC Driver | PostgreSQL 依赖没有进入运行时 classpath | 检查 pom.xml 和依赖树 |
Connection refused | 数据库未启动、主机或端口错误 | 检查 docker compose ps 和 JDBC URL |
| 认证失败 | 用户名或密码不一致 | 检查 Compose 与环境变量 |
| 数据库不存在 | URL 最后一段名称错误 | 检查 POSTGRES_DB 和 JDBC URL |
| 获取连接超时 | 连接池耗尽或数据库响应过慢 | 检查活跃请求、池大小和数据库状态 |
| 配置占位符无法解析 | 未设置 DB_PASSWORD | 在启动或测试命令中提供环境变量 |
这个测试确认了 PostgreSQL、驱动、配置、连接池和 DataSource 已经能够协作,但还没有执行应用自己的查询。下一章会在这条连接之上使用 JdbcClient,学习如何绑定参数并读取 SQL 结果。