Kotlin集成Realm:如何仅插入不存在的嵌套RealmObject
通用解决方案
1. 核心思路
利用Realm查询API结合反射,封装通用函数自动处理带主键对象的复用/插入:检查数据库中是否存在对应主键的实例,存在则直接复用,不存在则插入新实例。既避免UpdatePolicy.ALL带来的无意义更新,又省去手动逐个查询的繁琐。
2. 通用扩展函数实现
基础版(无缓存)
封装Realm扩展函数,自动处理带主键对象的复用逻辑:
import io.realm.kotlin.Realm import io.realm.kotlin.ext.copyToRealm import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.annotations.PrimaryKey /** * 通用函数:获取或创建带主键的RealmObject实例 * 存在则返回数据库中已有实例,不存在则插入并返回新实例 */ fun <T : RealmObject> Realm.getOrCreate(obj: T): T { // 反射获取主键字段 val primaryKeyField = obj::class.members.firstOrNull { it.findAnnotation<PrimaryKey>() != null } ?: throw IllegalArgumentException("${obj::class.simpleName}必须标注@PrimaryKey字段") val primaryKeyValue = primaryKeyField.call(obj) ?: throw IllegalArgumentException("${obj::class.simpleName}的主键值不能为null") // 查询数据库中是否存在该主键的实例 val existingInstance = this.query(obj::class, "${primaryKeyField.name} == $0", primaryKeyValue) .first() .find() return existingInstance ?: obj.copyToRealm() }
优化版(带事务内缓存)
如果处理数据量极大,重复查询数据库会有性能损耗,可在写事务内添加内存缓存,避免重复查询:
import io.realm.kotlin.Realm import io.realm.kotlin.ext.copyToRealm import io.realm.kotlin.types.RealmObject import io.realm.kotlin.types.annotations.PrimaryKey /** * 带缓存的通用函数:获取或创建带主键的RealmObject实例 * 事务内缓存已查询的实例,减少数据库查询次数 */ @Suppress("UNCHECKED_CAST") fun <T : RealmObject> Realm.getOrCreateWithCache(obj: T, cache: MutableMap<Pair<Class<*>, Any?>, T>): T { val primaryKeyField = obj::class.members.firstOrNull { it.findAnnotation<PrimaryKey>() != null } ?: throw IllegalArgumentException("${obj::class.simpleName}必须标注@PrimaryKey字段") val primaryKeyValue = primaryKeyField.call(obj) ?: throw IllegalArgumentException("${obj::class.simpleName}的主键值不能为null") val cacheKey = obj::class.java to primaryKeyValue // 优先从缓存获取 cache[cacheKey]?.let { return it } // 缓存不存在则查询数据库 val existingInstance = this.query(obj::class, "${primaryKeyField.name} == $0", primaryKeyValue) .first() .find() val result = existingInstance ?: obj.copyToRealm() cache[cacheKey] = result return result }
3. 使用示例
基础版使用
在写事务中调用扩展函数处理嵌套对象:
realm.write { csvDataList.forEach { csvItem -> // 处理Quantity对象,自动复用或插入 val quantity = this@write.getOrCreate(Quantity().apply { id = "${csvItem.value}_${csvItem.unit}" value = csvItem.value unit = csvItem.unit }) // 处理Another对象及其嵌套的Price val anotherPrice = this@write.getOrCreate(Quantity().apply { id = "${csvItem.anotherPriceValue}_${csvItem.anotherPriceUnit}" value = csvItem.anotherPriceValue unit = csvItem.anotherPriceUnit }) val another = this@write.getOrCreate(Another().apply { price = anotherPrice // 赋值其他字段 }) // 创建Example对象,关联复用后的嵌套实例 val example = Example().apply { field1 = csvItem.field1 anotherRealmObjectRef = quantity anotherRealmObjectRef2 = another } this.copyToRealm(example) } }
优化版使用
在写事务内初始化缓存,减少重复查询:
realm.write { // 事务内缓存,键为(对象Class,主键值) val cache = mutableMapOf<Pair<Class<*>, Any?>, RealmObject>() csvDataList.forEach { csvItem -> val quantity = this@write.getOrCreateWithCache( obj = Quantity().apply { id = "${csvItem.value}_${csvItem.unit}" value = csvItem.value unit = csvItem.unit }, cache = cache as MutableMap<Pair<Class<*>, Any?>, Quantity> ) // 其他嵌套对象同理使用缓存处理 val example = Example().apply { field1 = csvItem.field1 anotherRealmObjectRef = quantity // ... } this.copyToRealm(example) } }
4. 关键优势
- 无需手动查询:通用函数自动处理所有带主键的嵌套对象,无需为每个对象写单独的查询逻辑。
- 避免无意义更新:仅插入不存在的实例,不会修改已有实例的字段,减少Realm版本历史占用,缩小数据库体积。
- 性能优化:带缓存的版本可大幅减少数据库查询次数,提升批量插入效率。
内容的提问来源于stack exchange,提问作者Christian
相关产品推荐
相关产品推荐

