跳到主内容
创建时间: 2026-09-16最后更新: 2026-09-16

目前的 InMemoryHealthStatusRepository 可以保存每个服务的最新状态,但数据只存在于当前应用进程中。应用重启后数据会消失,部署多个实例时,每个实例也会拥有互不相同的 Map。要让 Repository 使用真正的持久化存储,第一步不是立即编写 SQL,而是先让 Spring Boot 建立可靠的数据库连接。

本章准备一个本地 PostgreSQL,添加 JDBC 依赖,配置 DataSource,并通过测试确认应用确实能够从连接池取得可用连接。下一章再使用自动配置好的连接执行 SQL。

1. 连接结构

Java 应用访问 PostgreSQL 时,中间会经过几层协作对象:

database-connection-stack.txt
1
HealthStatusRepository
2
-> Spring JDBC
3
-> DataSource
4
-> 连接池中的 Connection
5
-> PostgreSQL JDBC 驱动
6
-> PostgreSQL 服务

这些名称经常一起出现,但职责并不相同:

名称当前项目中的职责
PostgreSQL独立运行的数据库服务,真正保存表和数据
JDBCJava 访问关系数据库的一套标准 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 配置:

compose.yaml
01
name: spring-health
02
03
services:
04
postgres:
05
image: postgres:17
06
environment:
07
POSTGRES_DB: spring
08
POSTGRES_USER: spring
09
POSTGRES_PASSWORD: spring
10
ports:
11
- "5432:5432"
12
volumes:
13
- postgres_data:/var/lib/postgresql/data
14
healthcheck:
15
test: ["CMD-SHELL", "pg_isready -U spring -d spring"]
16
interval: 5s
17
timeout: 3s
18
retries: 10
19
20
volumes:
21
postgres_data:

这里的数据库名、用户名和密码都使用 spring,仅用于本地教学环境。postgres_data 是命名卷,删除或重建容器时数据仍然保留;执行 docker compose down -v 才会连同该项目的数据卷一起删除。

启动数据库并查看健康状态:

terminal.bash
1
docker compose up -d
2
docker compose ps

状态变为 healthy 后,可以先在容器内使用 psql 验证数据库本身:

terminal.bash
1
docker 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 驱动:

pom.xml
01
<dependency>
02
<groupId>org.springframework.boot</groupId>
03
<artifactId>spring-boot-starter-jdbc</artifactId>
04
</dependency>
05
06
<dependency>
07
<groupId>org.postgresql</groupId>
08
<artifactId>postgresql</artifactId>
09
<scope>runtime</scope>
10
</dependency>

两项依赖解决的是不同问题:

依赖提供的能力
spring-boot-starter-jdbcSpring JDBC、事务基础设施和默认连接池
org.postgresql:postgresqlPostgreSQL 对 JDBC 标准的具体实现

Starter 会传递引入 HikariCP。只要没有提供其他连接池或自定义 DataSource,Spring Boot 会优先使用 HikariCP 创建池化数据源。PostgreSQL 驱动只在运行应用和测试连接时需要,因此使用 runtime 作用域;业务源码通过 JDBC 标准和 Spring JDBC API 编译,不必直接依赖驱动实现类。

版本继续由 Spring Boot Parent 管理,不需要在这两个依赖上手写版本号。可以用依赖树确认最终解析结果:

terminal.bash
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 配置:

src/main/resources/application.yaml
1
spring:
2
datasource:
3
url: ${DB_URL:jdbc:postgresql://localhost:5432/spring}
4
username: ${DB_USERNAME:spring}
5
password: ${DB_PASSWORD}
6
hikari:
7
maximum-pool-size: 10
8
connection-timeout: 3000

JDBC URL 可以拆成下面几部分:

jdbc-url.txt
1
jdbc:postgresql://localhost:5432/spring
2
│ │ │ │ └─ 数据库名
3
│ │ │ └────── 端口
4
│ │ └──────────────── 主机
5
│ └───────────────────────────── 数据库类型
6
└────────────────────────────────── JDBC 协议前缀

${DB_URL:...} 表示优先读取环境变量 DB_URL,没有设置时使用冒号后的本地默认值。用户名采用相同方式。密码写成 ${DB_PASSWORD},不提供默认值,避免把可直接使用的密码提交到仓库。

本地启动前设置环境变量:

terminal.bash
1
export DB_PASSWORD='spring'
2
./mvnw spring-boot:run

也可以只为当前命令提供变量:

terminal.bash
1
DB_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 会按条件完成下面的组装:

datasource-auto-configuration.txt
1
读取 spring.datasource 配置
2
-> 在 classpath 中找到 PostgreSQL Driver
3
-> 在 classpath 中找到 HikariCP
4
-> 创建 HikariDataSource Bean
5
-> 基于 DataSource 创建 Spring JDBC 组件
6
-> Repository 可以通过构造方法声明依赖

这仍然遵循前面学习的 IoC。业务类只声明自己需要 DataSource 或更上层的 JdbcClient,不负责读取环境变量,也不负责决定连接池实现。

下面的写法虽然能够得到连接,却不适合作为 Repository 的日常实现:

src/main/java/cn/usehook/spring/health/HealthStatusRepository.java
1
Connection connection = DriverManager.getConnection(url, username, password);

它把配置读取、连接创建和数据访问混在同一个类中,还绕过了 Spring 管理的连接池和事务同步。后面的 Repository 会注入 JdbcClient,由 Spring JDBC 从当前事务关联的 DataSource 中取得连接。

还要注意,创建 DataSource Bean 不一定等于应用启动时已经完成数据库通信。连接池可能延迟到第一次借用连接时才真正连接 PostgreSQL,所以仅看到应用启动成功不能完全证明 URL、账号和网络都正确。

6. 连接验证

为了验证完整链路,可以编写一个只负责基础设施检查的集成测试:

src/test/java/cn/usehook/spring/DatabaseConnectionTests.java
01
package cn.usehook.spring;
02
03
import java.sql.Connection;
04
import java.sql.SQLException;
05
import javax.sql.DataSource;
06
import org.junit.jupiter.api.Test;
07
import org.springframework.beans.factory.annotation.Autowired;
08
import org.springframework.boot.test.context.SpringBootTest;
09
10
import static org.assertj.core.api.Assertions.assertThat;
11
12
@SpringBootTest
13
class DatabaseConnectionTests {
14
15
private final DataSource dataSource;
16
17
DatabaseConnectionTests(@Autowired DataSource dataSource) {
18
this.dataSource = dataSource;
19
}
20
21
@Test
22
void connectsToPostgresql() throws SQLException {
23
try (Connection connection = dataSource.getConnection()) {
24
assertThat(connection.isValid(2)).isTrue();
25
assertThat(connection.getMetaData().getDatabaseProductName())
26
.isEqualTo("PostgreSQL");
27
}
28
}
29
}

@SpringBootTest 启动应用上下文,因此测试取得的是 Spring Boot 实际配置的 DataSource。getConnection() 从连接池借出连接,try 语句结束时调用 close();对于池化连接,close() 通常表示归还连接,而不是直接关闭底层网络连接。

确保 PostgreSQL 容器已经就绪,再运行测试:

terminal.bash
1
DB_PASSWORD='spring' ./mvnw -Dtest=DatabaseConnectionTests test

连接失败时,可以按错误出现的位置排查:

现象常见原因检查方向
找不到 JDBC DriverPostgreSQL 依赖没有进入运行时 classpath检查 pom.xml 和依赖树
Connection refused数据库未启动、主机或端口错误检查 docker compose ps 和 JDBC URL
认证失败用户名或密码不一致检查 Compose 与环境变量
数据库不存在URL 最后一段名称错误检查 POSTGRES_DB 和 JDBC URL
获取连接超时连接池耗尽或数据库响应过慢检查活跃请求、池大小和数据库状态
配置占位符无法解析未设置 DB_PASSWORD在启动或测试命令中提供环境变量

这个测试确认了 PostgreSQL、驱动、配置、连接池和 DataSource 已经能够协作,但还没有执行应用自己的查询。下一章会在这条连接之上使用 JdbcClient,学习如何绑定参数并读取 SQL 结果。