如何在单数据库表中存储Kotlin泛型类型的应用配置
问题分析与修复方案
错误原因
Hibernate无法处理泛型实体中未绑定的类型参数T——运行时类型擦除后,Hibernate不知道该将value字段映射到数据库的哪种数据类型,也没有明确的目标实体信息,因此抛出AnnotationException。
修复方案:用JSON序列化实现多类型配置存储(推荐)
这种方案通过将任意类型的配置值序列化为JSON字符串存储,读取时反序列化为对应类型,同时新增字段记录值的类型,确保类型安全。
1. 调整实体类
引入hibernate-types依赖(适配你的Hibernate版本,比如com.vladmihalcea:hibernate-types-55),然后修改实体类:
@Entity @Table(name = "table_application_settings") data class ApplicationSetting( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(name = "application_setting_id") val id: Long? = null, @Column(name = "application_setting_name", unique = true) var name: String = "", // 存储JSON格式的配置值,用TEXT类型避免长度限制 @Column(name = "application_setting_value", columnDefinition = "TEXT") @Type(JsonType::class) var value: Any? = null, // 记录值的全类名,用于反序列化时确定类型 @Column(name = "application_setting_value_type") var valueType: String = "" )
2. 简化Repository
去掉泛型声明,直接使用调整后的实体类:
@Repository interface ApplicationSettingsRepository : JpaRepository<ApplicationSetting, Long> { fun findByName(name: String): ApplicationSetting? }
3. 优化Service层逻辑
在Service中处理类型转换和类型标识的维护:
@Service @Transactional class ApplicationSettingsServiceImpl( private val applicationSettingsRepository: ApplicationSettingsRepository, ) : ApplicationSettingsService { override fun saveBooleanApplicationSetting(name: String, value: Boolean): ApplicationSetting { return saveOrUpdateSetting(name, value, Boolean::class.java.name) } override fun saveIntegerApplicationSetting(name: String, value: Int): ApplicationSetting { return saveOrUpdateSetting(name, value, Int::class.java.name) } override fun getBooleanSetting(name: String): Boolean? { return getSettingValue(name) as? Boolean } override fun getIntegerSetting(name: String): Int? { return getSettingValue(name) as? Int } override fun getAllApplicationSettings(): MutableList<ApplicationSetting> { return applicationSettingsRepository.findAll() } // 通用保存/更新方法 private fun saveOrUpdateSetting(name: String, value: Any, type: String): ApplicationSetting { val setting = applicationSettingsRepository.findByName(name) ?: ApplicationSetting() setting.name = name setting.value = value setting.valueType = type return applicationSettingsRepository.save(setting) } // 通用获取值方法 private fun getSettingValue(name: String): Any? { return applicationSettingsRepository.findByName(name)?.value } }
替代方案:针对特定类型使用AttributeConverter
如果只需要支持有限的基础类型(如Boolean、Int),可以为每种类型编写AttributeConverter,将值转换为数据库字符串存储:
- 编写Boolean转换器:
@Converter(autoApply = false) class BooleanToStringConverter : AttributeConverter<Boolean, String> { override fun convertToDatabaseColumn(attribute: Boolean?): String? = attribute?.toString() override fun convertToEntityAttribute(dbData: String?): Boolean? = dbData?.toBoolean() }
- 但这种方式需要实体类放弃泛型,通过类型字段配合
@Convert注解指定转换器,灵活性远不如JSON序列化方案,因此仅推荐在类型极少的场景使用。
内容的提问来源于stack exchange,提问作者JamieRhys
相关产品推荐
相关产品推荐

