Kotlin中如何创建含动态公共成员的类实例?实现表模型自动映射
提问翻译
我需要创建大量数据表列及其对应的模型类,示例如下:
示例1:
object MyTable: IdTable<UUID>("my_table") { override val id = uuid("id").autoGenerate().entityId() val myField = date("my_field") val myAnotherField = date("my_another_field"); // more fields ... } open class MyModel(id: EntityID<UUID>) : UUIDEntity(id) { companion object : UUIDEntityClass<MyModel>(MyTable) { fun doStuff(): MyModel { // shared functionality goes here. } } var myField by MyTable.myField var myAnotherField by MyTable.myAnotherField // more fields ... }
示例2:
object MyTable2: IdTable<UUID>("my_table") { override val id = uuid("id").autoGenerate().entityId() val myField2 = date("my_field_2") val myAnotherField2 = date("my_another_field_2"); // more fields ... } open class MyModel2(id: EntityID<UUID>) : UUIDEntity(id) { companion object : UUIDEntityClass<MyModel>(MyTable2) { fun doStuff(): MyModel { // shared functionality goes here. } } var myField2 by MyTable2.myField2 var myAnotherField2 by MyTable2.myAnotherField2 // more fields ... }
我希望避免手动映射表成员到模型成员的重复工作,因此计划实现一个Factory类,根据传入的Map生成对应的实例:
class Factory(val fields: Fields, val tableName: String) { fun getTable() { // TODO } fun getModel() { // TODO } } val fields: Fields = mapOf( "myField" to date("my_field"), "myAnotherField" to date("my_another_field"), // more fields ... ) val instances = Factory(fields, "my_table")
但深入研究后,我感觉在Kotlin中似乎无法实现该需求。反射可能是一种解决方案,但会破坏代码的类型安全性,因此我对此存疑。我来自TypeScript背景,原本期望借助泛型和联合类型实现,但Kotlin的泛型功能相对有限,特此寻求解决方案。
解决方案
1. 编译期代码生成(注解处理器/KAPT)
这是兼顾类型安全与减少重复代码的最优方案。通过自定义注解标记表和字段的配置,编写注解处理器在编译阶段自动生成IdTable和对应的UUIDEntity子类,完全保留Kotlin的类型检查优势。
实现示例:
- 定义描述表和字段的注解:
@Target(AnnotationTarget.CLASS) @Retention(AnnotationRetention.SOURCE) annotation class GenerateTableModel( val tableName: String, val fields: Array<FieldDef> ) @Target(AnnotationTarget.ANNOTATION_CLASS) @Retention(AnnotationRetention.SOURCE) annotation class FieldDef( val propertyName: String, val columnName: String, val type: FieldType ) enum class FieldType { DATE, UUID, STRING, INT } // 根据业务扩展类型
- 编写KAPT处理器,扫描
GenerateTableModel注解,根据注解参数自动生成与手动编写一致的表和模型代码。生成的代码会在编译期完成,运行时无额外开销。
2. 利用委托与扩展简化重复代码
如果不想编写注解处理器,可以通过封装通用逻辑减少样板代码:
// 通用UUID实体基类 abstract class BaseUUIDEntity(id: EntityID<UUID>) : UUIDEntity(id) // 扩展函数简化表字段定义 fun IdTable<UUID>.dateField(propertyName: String, columnName: String): Column<LocalDate> { return date(columnName) } // 表定义简化 object MyTable : IdTable<UUID>("my_table") { override val id = uuid("id").autoGenerate().entityId() val myField = dateField("myField", "my_field") val myAnotherField = dateField("myAnotherField", "my_another_field") } // 模型类简化 class MyModel(id: EntityID<UUID>) : BaseUUIDEntity(id) { companion object : UUIDEntityClass<MyModel>(MyTable) { fun doStuff(): MyModel { // 业务逻辑实现 } } var myField by MyTable.myField var myAnotherField by MyTable.myAnotherField }
这种方式虽然仍需声明字段,但通过封装减少了重复的字段定义逻辑。
3. 反射方案(谨慎使用)
反射可以实现动态生成,但会牺牲类型安全,仅适合极端动态场景:
class TableFactory(private val tableName: String, private val fields: Map<String, Column<*>>) { fun createTable(): IdTable<UUID> { return object : IdTable<UUID>(tableName) { override val id = uuid("id").autoGenerate().entityId() init { fields.forEach { (propName, column) -> this::class.members.find { it.name == propName }?.let { member -> (member as? KMutableProperty<*>)?.setter?.call(this, column) } } } } } }
模型类的动态反射实现会更繁琐,且运行时容易出现类型错误,非必要不推荐。
4. 第三方ORM扩展工具
Exposed本身提供了exposed-codegen模块,可以根据数据库结构反向生成表和实体类;其他Kotlin ORM框架也大多内置代码生成能力,若从数据库反向构建代码,这是最便捷的选择。
内容的提问来源于stack exchange,提问作者wube
相关产品推荐
相关产品推荐

