JOOQ自定义RecordMapperProvider未生效,<EmptyString/>未替换为null
我用Spring Boot+JOOQ+Kotlin开发Web应用,自定义了EmptyStringToNullRecordMapperProvider,意图将查询结果中的<EmptyString/>替换为null,但从CONFIG表加载数据时,大量结果未被处理,rawData中仍存在<EmptyString/>,调试未定位到原因,请求排查。
自定义EmptyStringToNullRecordMapperProvider代码
import org.jooq.* import org.jooq.impl.DefaultConfiguration import org.jooq.impl.SQLDataType class EmptyStringToNullRecordMapperProvider : RecordMapperProvider { override fun <R : Record?, E : Any?> provide(recordType: RecordType<R>?, type: Class<out E>?): RecordMapper<R, E> { val originalMapper = DefaultConfiguration().recordMapperProvider().provide(recordType, type) // 自定义映射器,将"EMPTYSTRING"值替换为null return RecordMapper { record -> originalMapper.map(record).apply { recordType?.fields()?.forEach { field -> if (field.dataType.sqlDataType == SQLDataType.VARCHAR) { System.out.println(record); val originalValue = record?.getValue(field) as String? if ("<EmptyString/>" == originalValue) { record?.setValue(field, null) } } } } } } }
Repository代码
import fr.test.model.jooq.generated.tables.records.* import fr.test.model.jooq.generated.tables.references.CONFIG import org.jooq.DSLContext import org.springframework.beans.factory.annotation.Autowired import org.springframework.stereotype.Repository import org.springframework.transaction.annotation.Transactional import java.util.stream.Stream @Repository @Transactional(readOnly = true) class LiveConfigurationRepository( @Autowired private val jooq: DSLContext ) { fun getConfiguration(): Map<String, List<String>> { val rawData: MutableMap<String, List<String>> = mutableMapOf() jooq.dsl().select(CONFIG) .from(CONFIG) .fetch() .stream() .forEach { record -> val mapData = record.intoMap(); val configRecord: ConfigRecord = mapData["config"] as ConfigRecord; val key: String = configRecord.key!! val value: String? = configRecord.value if (!value.isNullOrBlank()) addValueToMap(rawData, key, value); } return rawData; } fun addValueToMap(rawData: MutableMap<String, List<String>>, key: String, value: String) { if (rawData.containsKey(key)) { val existingList = rawData[key]!!.toMutableList() existingList.add(value) rawData[key] = existingList } else { rawData[key] = listOf(value) } } }
排查与解决方案
1. 自定义Provider未注册到JOOQ配置
你自定义的EmptyStringToNullRecordMapperProvider大概率未被注入到Spring管理的JOOQ Configuration中,导致JOOQ仍使用默认映射器。需通过Spring配置将其注册:
方式一:自定义JOOQ Configuration Bean
import org.jooq.Configuration import org.jooq.impl.DefaultConfiguration import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @Configuration class JooqConfig { @Bean fun jooqConfiguration(): Configuration { return DefaultConfiguration() .set(EmptyStringToNullRecordMapperProvider()) // 补充其他必要配置:SQLDialect、DataSource等 } }
方式二:用DefaultConfigurationCustomizer适配Spring Boot自动配置
import org.jooq.impl.DefaultConfigurationCustomizer import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @Configuration class JooqConfig { @Bean fun recordMapperProviderCustomizer(): DefaultConfigurationCustomizer { return DefaultConfigurationCustomizer { config -> config.set(EmptyStringToNullRecordMapperProvider()) } } }
2. 映射逻辑错误:修改了原始Record而非映射后的对象
当前代码中,originalMapper.map(record)已将Record转换为目标对象(如ConfigRecord),但后续修改的是原始Record,而非已经映射好的对象。而Repository中使用的record.intoMap()得到的ConfigRecord是独立的映射结果,修改原始Record不会影响它。
修正逻辑,直接修改映射后的对象:
override fun <R : Record?, E : Any?> provide(recordType: RecordType<R>?, type: Class<out E>?): RecordMapper<R, E> { val originalMapper = DefaultConfiguration().recordMapperProvider().provide(recordType, type) return RecordMapper { record -> val mappedObject = originalMapper.map(record) // 针对ConfigRecord做针对性处理 if (mappedObject is ConfigRecord) { if ("<EmptyString/>" == mappedObject.value) { mappedObject.value = null } } // 通用处理方式:反射遍历所有字符串属性 // mappedObject?.javaClass?.declaredFields?.forEach { field -> // if (field.type == String::class.java) { // field.isAccessible = true // val value = field.get(mappedObject) as String? // if ("<EmptyString/>" == value) { // field.set(mappedObject, null) // } // } // } mappedObject } }
3. Repository中intoMap()的用法问题
record.intoMap()返回的是Record的键值对映射,其中mapData["config"]对应的ConfigRecord可能未经过自定义Mapper处理。建议直接使用into(Class)方法,确保走全局配置的映射器:
// 替换原intoMap()逻辑 val configRecord = record.into(ConfigRecord::class.java)
4. 字段类型判断不全面
field.dataType.sqlDataType == SQLDataType.VARCHAR仅匹配VARCHAR类型,未覆盖CHAR、LONGVARCHAR等其他字符串类型,可改用更通用的判断:
if (field.dataType.isString) { // 处理逻辑 }
内容的提问来源于stack exchange,提问作者abdel

