测试多驱动需隔离连接池、显式指定方言、避免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:testdb 和 jdbc: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硬编码,别用runtimeClasspath或bom统一管理 —— 不同驱动更新节奏完全不同 - 在测试启动时加校验逻辑:
Class.forName("org.postgresql.Driver").getPackage().getImplementationVersion(),不匹配就fail() - sqlite 驱动慎用:它不支持
SAVEPOINT,导致@Transactional测试在其他驱动下正常,在 SQLite 下直接抛SQLException
最麻烦的不是语法差异,而是各驱动对“空事务”“自动提交边界”“连接关闭时机”的实现细节——这些几乎不会写在文档里,只能靠日志里扒 Connection.open()/close() 调用栈来确认。