AbstractRoutingDataSource自定义Jdbc配置仅默认数据源生效,如何全局生效?
问题原因
核心问题在于JdbcConverter依赖的DefaultJdbcTypeFactory会缓存首次获取的数据源连接元数据,而AbstractRoutingDataSource动态切换数据源时,JdbcConverter无法自动适配新的数据源上下文,导致自定义的jsonb
另外,你的RoutingDataSource存在冗余配置:将默认数据源同时注册到targetDataSources和defaultTargetDataSource,导致AbstractRoutingDataSource在处理lookupKey和fallback场景时的数据源实例路径不一致,间接放大了转换器的适配问题。
解决方案
1. 修复RoutingDataSource的冗余配置
移除targetDataSources中对默认数据源的重复注册,让fallback逻辑直接使用defaultTargetDataSource,简化路由逻辑:
class RoutingDatasource( mainDs: DataSource, customDatasources: Map<String, DataSource> ) : AbstractRoutingDataSource() { companion object { public val holder = ThreadLocal<String>() } init { setDefaultTargetDataSource(mainDs) setTargetDataSources(customDatasources) // 仅注册自定义副数据源 } override fun determineCurrentLookupKey() = holder.get() // 为空时自动fallback到默认数据源 }
2. 确保自定义转换器全局生效
检查并确认你的jsonb转换器实现正确,确保String与PGobject的双向转换能被JdbcConverter识别:
// 写入转换器:String -> PGobject(jsonb类型) class JsonbWritingConverter : Converter<String, PGobject> { override fun convert(source: String): PGobject { val pgObject = PGobject() pgObject.type = "jsonb" pgObject.value = source return pgObject } } // 读取转换器:PGobject(jsonb类型) -> String class JsonbReadingConverter : Converter<PGobject, String> { override fun convert(source: PGobject): String { return source.value ?: "" } }
3. 动态适配JdbcTypeFactory的数据源上下文
默认的DefaultJdbcTypeFactory会缓存首次获取的连接元数据,我们需要确保它每次都能获取到当前路由的数据源连接。可以通过自定义JdbcTypeFactory或者调整JdbcConverter的创建逻辑来实现:
@Bean("mainJdbcConverter") @Primary fun jdbcConverter( mappingContext: JdbcMappingContext, @Qualifier("mainJdbcTemplate") operations: NamedParameterJdbcOperations, @Lazy relationResolver: RelationResolver, @Qualifier("mainJdbcConversions") conversions: JdbcCustomConversions, dialect: Dialect ): JdbcConverter { // 自定义JdbcTypeFactory,确保每次获取当前路由的数据源连接 val dynamicTypeFactory = object : DefaultJdbcTypeFactory(operations.jdbcOperations) { override fun createConnectionAccessor(): ConnectionAccessor { // 每次都获取新的连接访问器,避免缓存旧数据源的元数据 return object : ConnectionAccessor { override fun <T> execute(action: (Connection) -> T): T { return operations.jdbcOperations.execute(action) } } } } return BasicJdbcConverter( mappingContext, relationResolver, conversions, dynamicTypeFactory, dialect.identifierProcessing ) }
4. 确认Jdbc组件与RoutingDataSource的绑定
确保所有Jdbc相关组件(JdbcTemplate、NamedParameterJdbcTemplate)都明确绑定到routingDataSource,避免注入错误的数据源:
@Bean @Primary fun jdbcTemplate(@Qualifier("routingDataSource") dataSource: DataSource, properties: JdbcProperties): JdbcTemplate { val jdbcTemplate = JdbcTemplate(dataSource) val template = properties.template jdbcTemplate.fetchSize = template.fetchSize jdbcTemplate.maxRows = template.maxRows template.queryTimeout?.let { jdbcTemplate.queryTimeout = it.seconds.toInt() } return jdbcTemplate }
验证步骤
- 启动应用后,先触发路由到副数据源的操作,再触发默认数据源的操作,确认两者都不会出现jsonb类型转换错误。
- 交换默认数据源与副数据源的配置,重复上述验证,确保无论哪个作为默认数据源,路由到另一个时转换器都能生效。
内容的提问来源于stack exchange,提问作者Tarmo

