如何编写支持多数据库驱动的通用持久层测试

1次阅读

测试多驱动需隔离连接池、显式指定方言、避免uuid自动生成、断言sql行为而非结果、锁定驱动版本并校验。

如何编写支持多数据库驱动的通用持久层测试

测试前必须隔离数据库连接池

多驱动测试失败,八成是因为连接池复用导致状态污染。比如 postgresql 测试跑完,HikariCP 连接还挂着事务没清理,切到 mysql 时直接报 Connection is closed 或锁表异常。

实操建议:

  • 每个测试类启动前,调用 DataSourceUtils.doCloseConnection() 强制释放当前线程绑定的连接(spring 环境)
  • 不用 @DataJpaTest 这类默认共享 DataSource 的注解;改用 @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) + 手动配置 spring.datasource.url
  • @BeforeEach 里执行 jdbcTemplate.update("TRUNCATE table xxx"),别依赖 @Sql —— 它不保证跨驱动兼容

驱动切换不能只改 URL,还要换方言和类型映射

spring.datasource.url=jdbc:h2:mem:testdbjdbc:postgresql://localhost/test 看似只差协议,但实际触发的是完全不同的 JdbcOperations 行为:H2 默认允许大小写不敏感字段名,PostgreSQL 要双引号;SQL Server 的 DATETIME2 在 MySQL 里得映射成 DATETIME(3)

实操建议:

  • 测试配置中显式指定 spring.jpa.database-platform,例如 org.hibernate.dialect.PostgreSQLDialect,别让 Hibernate 自动探测
  • 实体字段用 @column(columnDefinition = "VARCHAR(64)") 替代 @Column(Length = 64) —— 后者在 H2 和 oracle 下生成的 DDL 可能不一致
  • 避免使用 UUID 主键自动生成:PostgreSQL 用 gen_random_uuid(),MySQL 得靠 UUID() 函数,H2 则不支持函数默认值

断言 SQL 行为比断言结果更容易翻车

写个 assertThat(userRepository.count()).isEqualTo(1) 看似稳妥,但一旦驱动差异影响了 COUNT(*) 的事务可见性(比如 MySQL 的 READ_COMMITTED vs PostgreSQL 的 REPEATABLE_READ),结果就不可靠。

实操建议:

  • Mockito.spy(JdbcTemplate.class) 拦截实际执行的 SQL,断言 sql.contains("INSERT INTO user") 而非结果数量
  • 对批量操作,检查是否用了驱动原生批量支持:jdbcTemplate.batchUpdate(...) 在 H2 下是模拟的,在 PostgreSQL 下才真正走 addBatch()
  • 禁用二级缓存测试:在测试配置里加 spring.jpa.properties.hibernate.cache.use_second_level_cache=false,否则 Oracle 驱动缓存行为和 H2 完全不同

CI 环境下驱动版本必须锁定

本地用 postgresql-42.6.0.jar 测试通过,CI 机器上 maven 仓库拉的是 42.2.25,结果 PGobject 序列化方式变了,JSONB 字段读出来是空字符串 —— 这种问题不会报错,只会静默丢数据。

实操建议:

  • 所有驱动依赖用 version 硬编码,别用 runtimeClasspathbom 统一管理 —— 不同驱动更新节奏完全不同
  • 在测试启动时加校验逻辑:Class.forName("org.postgresql.Driver").getPackage().getImplementationVersion(),不匹配就 fail()
  • sqlite 驱动慎用:它不支持 SAVEPOINT,导致 @Transactional 测试在其他驱动下正常,在 SQLite 下直接抛 SQLException

最麻烦的不是语法差异,而是各驱动对“空事务”“自动提交边界”“连接关闭时机”的实现细节——这些几乎不会写在文档里,只能靠日志里扒 Connection.open()/close() 调用来确认。

text=ZqhQzanResources