You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

AbstractRoutingDataSource自定义Jdbc配置仅默认数据源生效,如何全局生效?

问题分析与解决方案:AbstractRoutingDataSource路由时自定义Jdbc配置仅默认数据源生效

问题原因

核心问题在于JdbcConverter依赖的DefaultJdbcTypeFactory会缓存首次获取的数据源连接元数据,而AbstractRoutingDataSource动态切换数据源时,JdbcConverter无法自动适配新的数据源上下文,导致自定义的jsonb转换器仅在首次初始化的默认数据源(fallback场景)中生效,路由到其他数据源时转换器失效,最终出现类型不匹配错误。

另外,你的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
}

验证步骤

  1. 启动应用后,先触发路由到副数据源的操作,再触发默认数据源的操作,确认两者都不会出现jsonb类型转换错误。
  2. 交换默认数据源与副数据源的配置,重复上述验证,确保无论哪个作为默认数据源,路由到另一个时转换器都能生效。

内容的提问来源于stack exchange,提问作者Tarmo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.06 17:20:42