本文介绍如何在 junit 测试中安全、可靠地为每个测试方法独立设置数据库连接参数(如 jdbc url、用户名等),避免静态单例污染和跨测试干扰,确保 h2 内存数据库被正确初始化而非误连 postgresql。
本文介绍如何在 junit 测试中安全、可靠地为每个测试方法独立设置数据库连接参数(如 jdbc url、用户名等),避免静态单例污染和跨测试干扰,确保 h2 内存数据库被正确初始化而非误连 postgresql。
在您当前的 DataSource 实现中,ds 和 config 均为 static 字段,且 getDataSource() 采用懒加载 + 静态初始化模式。问题根源在于:@BeforeEach 方法调用时,DataSource 的静态实例可能早已被前一个测试(或类加载过程)初始化完成,导致后续 System.setProperty() 的修改对已创建的 HikariDataSource 无效——因为 HikariConfig 在首次构造后不会自动响应系统属性变更。
✅ 正确做法:解耦配置与实例生命周期
不应依赖 System.setProperty() 动态“热更新”静态数据源,而应将配置与数据源实例彻底分离,并支持按需重建:
public class DataSource {
private static volatile HikariDataSource instance;
private static HikariConfig config;
// 允许外部传入配置(测试专用)
public static void setConfig(HikariConfig customConfig) {
config = customConfig;
// 主动销毁旧实例,确保下次 getDataSource() 创建新连接池
if (instance != null) {
instance.close();
instance = null;
}
}
public static HikariDataSource getDataSource() {
if (instance == null) {
synchronized (DataSource.class) {
if (instance == null) {
HikariConfig cfg = config != null ? config : createDefaultConfig();
instance = new HikariDataSource(cfg);
initDatabase(instance);
}
}
}
return instance;
}
private static HikariConfig createDefaultConfig() {
HikariConfig c = new HikariConfig();
c.setJdbcUrl(System.getProperty("jdbcUrl", "jdbc:postgresql://localhost:5433/postgres"));
c.setUsername(System.getProperty("jdbcUser", "postgres"));
c.setPassword(System.getProperty("jdbcPassword", "postgres"));
return c;
}
private static void initDatabase(HikariDataSource ds) {
try (Connection conn = ds.getConnection()) {
DatabaseConnection connection = new JdbcConnection(conn);
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(connection);
Liquibase liquibase = new Liquibase(
System.getProperty("liquibaseFile", "liquibase.xml"),
new ClassLoaderResourceAccessor(),
database
);
liquibase.update(new Contexts());
} catch (SQLException | LiquibaseException e) {
throw new RuntimeException("Failed to initialize database", e);
}
}
// 供测试清理使用
public static void reset() {
if (instance != null) {
instance.close();
instance = null;
}
config = null;
}
}✅ 测试基类改造:每个测试前重建数据源
public class Base {
@BeforeEach
void beforeEachTest() {
// 1. 清理全局状态
DataSource.reset();
// 2. 设置测试专用配置(H2)
HikariConfig h2Config = new HikariConfig();
String h2Url = "jdbc:h2:mem:test_" + UUID.randomUUID() + ";DB_CLOSE_DELAY=-1";
h2Config.setJdbcUrl(h2Url);
h2Config.setUsername("sa");
h2Config.setPassword("");
h2Config.setDriverClassName("org.h2.Driver");
// 3. 注入配置并触发重建
DataSource.setConfig(h2Config);
System.setProperty("liquibaseFile", "liquibase_test.xml");
// 4. 强制触发初始化(可选,因 getDataSource() 懒加载)
DataSource.getDataSource();
}
}⚠️ 关键注意事项
- 禁止在生产代码中依赖 System.setProperty() 进行运行时配置切换:它属于全局副作用,线程不安全,且难以追踪生效时机;
- @BeforeEach 必须是非静态方法,否则无法访问实例状态,也无法保证执行顺序;
- DataSource.reset() 是核心保障:它确保每次测试开始前旧连接池被关闭、静态引用被清空;
- Liquibase 初始化应在 getDataSource() 之后显式触发(或封装进 initDatabase(ds)),避免因连接复用导致 schema 未更新;
- 若使用 Spring Boot,推荐直接使用 @Testcontainers 或 @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.ANY),更符合现代测试实践。
通过以上重构,每个测试方法均获得隔离、纯净的 H2 数据库环境,彻底规避 PostgreSQL 连接泄漏问题,同时保持代码可测性与生产环境一致性。

















