首页 > 解决方案 > POJO 中的双向关系(父 <=> 子)

问题描述

房间里的 POJO 是否可以有双向关系?

就像在我写的以下测试中一样:

data class TestChildWithRefs(
    @Embedded val child: TestChild,
    // REMOVING following reverse relation solves the problem; only referenceing TestParent instead of TestParentWithRefs also works but is not what I need
    @Relation(parentColumn = "fk_parent", entityColumn = "parent_id", entity = TestParent::class)
    val parent: TestParentWithRefs
)

data class TestParentWithRefs(
    @Embedded val parent: TestParent,
    @Relation(parentColumn = "parent_id", entityColumn = "fk_parent", entity = TestChild::class)
    val children: MutableList<TestChildWithRefs>
)

一旦我添加了双向关系,房间就会StackOverflowError因为无休止的递归而停止构建。我能以某种方式解决这个问题吗?

我需要这个,因为我的数据层将通过已经定义的接口公开父母和孩子,并且允许检索从父母到孩子的导航以及相反的方式......

附加代码:

// ------
// Child entity + dao
// -----

@Entity(tableName = "child")
data class TestChild(
        @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "child_id") var id: Long = 0L,
        @ColumnInfo(name = "name") var name: String,
        @ColumnInfo(name = "fk_parent") var fkParent: Long
)

@Dao
abstract class TestChildDao {
    @Transaction
    @Query("SELECT * FROM child")
    abstract fun loadAll(): List<TestChildWithRefs>
}

// ------
// Parent entity + dao
// -----

@Entity(tableName = "parent")
data class TestParent(
        @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "parent_id") var id: Long = 0L,
        @ColumnInfo(name = "name") var name: String
)
@Dao
abstract class TestParentDao {
    @Transaction
    @Query("SELECT * FROM parent")
    abstract fun loadAll(): List<TestParentWithRefs>
}

标签: androidandroid-room

解决方案


我找到了一种在房间中建模双向关系的方法,只需少量(但可以接受)手动交互。

这是基于我的真实解决方案的示例:

  • 1 天(父母)=> 有很多锻炼
  • 1 次锻炼(儿童)=> 有很多锻炼
  • 1个练习(孩子的孩子)=>我会在这个级别停下来,这个例子解释了这个想法

具有工作双向关系的实体包装器

 class FullDay(
    @Embedded
    val day: WDay,

    @Relation(parentColumn = "w_day_id", entityColumn = "fk_day", entity = WWorkout::class)
    val workouts: MutableList<FullWorkout> = ArrayList()
) {
     fun setRefs() {
        workouts.forEach { it.setRefs(this) }
    }
}

class FullWorkout(
    @Embedded
    val workout: WWorkout,

    @Relation(parentColumn = "w_workout_id", entityColumn = "fk_workout", entity = WExercise::class)
    val internalExercises: MutableList<FullExercise> = ArrayList()
) {

    @Ignore
    var day: FullDay? = null

    fun setRefs(day: FullDay) {
        this.day = day
        internalExercises.forEach { it.setRefs(this) }
    }
}

class FullExercise(

    @Embedded
    val exercise: WExercise

    // ...

) {
    @Ignore
    var workout: FullWorkout? = null

    fun setRefs(fullWorkout: FullWorkout) {
        this.workout = fullWorkout
    }
}

工作道

@Dao
abstract class WDao : BaseDao {

    companion object {
        const val BASE_QUERY = "SELECT * FROM w_day " +
                "left join w_workout on w_day_id = fk_day " +
                "left join w_exercise on w_exercise_id = fk_exercise " +
                "left join w_set on w_exercise_id = w_set_fk_exercise " +
                "left join w_target_set on w_exercise_id = w_target_set_fk_exercise"
    }

    // ------------------
    // internal functions
    // ------------------

    @Transaction
    @Query("$BASE_QUERY where w_day_id = :id")
    abstract fun _loadFullDay(id: Long): FullDay

    @Transaction
    @Query("$BASE_QUERY where date = :date")
    abstract fun _loadFullDayOfDate(date: Date): FullDay?

    @Transaction
    @Query("$BASE_QUERY where w_workout_id = :id")
    abstract fun _loadFullWorkout(id: Long): FullDay

    @Transaction
    @Query("$BASE_QUERY where date >= :date")
    abstract fun _loadFullWorkoutAfterDate(date: Date): List<FullDay>

    @Transaction
    @Query("$BASE_QUERY where w_exercise_id = :id")
    abstract fun _loadFullExercise(id: Long): FullDay

    // ------------------
    // external functions
    // ------------------

    fun loadFullDay(id: Long): FullDay = _loadFullDay(id).apply { setRefs() }
    fun loadFullDayOfDate(date: Date): FullDay? = _loadFullDayOfDate(date)?.apply { setRefs() }
    fun loadFullWorkout(id: Long): FullWorkout = _loadFullWorkout(id).apply { setRefs() }.workouts.first()
    fun loadFullWorkoutAfterDate(date: Date): List<FullWorkout> = _loadFullWorkoutAfterDate(date).onEach { it.setRefs() }.map { it.workouts }.flatten()
    fun loadFullExercise(id: Long): FullExercise = _loadFullExercise(id).apply { setRefs() }.workouts.first().internalExercises.first()

}

推荐阅读