POJO 中的 POJO,实体关系问题

POJO within POJO, Entity-Relation problem

我的数据库中有五个 table:AREA、AREA_TYPE、SAMPLE、PACK、UNIT

    @Entity(tableName = "AREA")
    data class AreaEntity(
       @PrimaryKey val id:String,
       val title:String,
       @ColumnInfo(name = "area_type_id") val areaTypeId:Int,
       @ColumnInfo(name = "is_active") val isActive:Boolean
    )

    @Entity(tableName = "AREA_TYPE")
    data class AreaTypeEntity(
       @PrimaryKey val id:String,
       val title:String,
       @ColumnInfo(name = "title") val parentAreaId : String
    )

    @Entity(tableName = "SAMPLE")
    data class SampleEntity(
       @PrimaryKey val id:String,
       val title:String,
    )

    @Entity(tableName = "PACK")
    data class PackEntity(
       @PrimaryKey val id:String,
       val title:String,
    )

    @Entity(tableName = "UNIT")
    data class UnitEntity(
       @PrimaryKey val id:String,
       @ColumnInfo(name = "sample_id") val parentAreaId : String,
       @ColumnInfo(name = "area_id") val areaId:Int,
       @ColumnInfo(name = "pack_type_id") val packTypeId: Int,
       @ColumnInfo(name = "is_active") val isActive:Boolean
    )

我有一个 AreaPOJO 用于 Area-AreaType 关系:

     data class AreaPOJO (
           @Embedded val areaEntity : AreaEntity

           @Relation (
                   parentColumn = "area_id",
                   entityColumn = "id"
           )
           val areaTypeEntity : AreaTypeEntity
     )

tables (https://i.stack.imgur.com/bXzl5.png)

的视觉视图

所以我假设我将有一个 POJO 用于 UNIT 的关系如下:

     data class UnitPOJO (
           @Embedded val unitEntity : UnitEntity

           @Relation (
                   parentColumn = "area_id",
                   entityColumn = "id"
           )
           val areaEntity : AreaEntity
           
           @Relation (
                   parentColumn = "pack_id",
                   entityColumn = "id"
           )
           val packEntity : PackEntity
           
           @Relation (
                   parentColumn = "sample_id",
                   entityColumn = "id"
           )
           val sampleEntity : SampleEntity
      )

使用此 POJO,我可以获得 AreaEntity、SampleEntity、UnitEntity,但我无法获得 UnitPOJO 的 AreaTypeEntity。 当我使用 AreaPOJO 而不是 AreaEntity 时,出现编译错误,提示我对 AreaPOJO 使用“前缀”。当我使用前缀时,这次 AreaPOJO 报错说它找不到关系的列名。

所以我卡住了:) 简而言之,我需要所有五个 table 的所有字段来进行此查询:

    "SELECT * FROM UNIT 
              INNER JOIN AREA ON UNIT.AREA_ID = AREA.ID
              INNER JOIN AREA_TYPE ON AREA.AREA_TYPE_ID = AREA_TYPE.ID 
              INNER JOIN SAMPLE ON UNIT.SAMPLE_ID = SAMPLE.ID 
              INNER JOIN PACK ON UNIT.PACK_ID = PACK.ID"  

首先使用 prefix,这是一个避免歧义列名的选项(例如 哪个 id 列是正确的使用?(修辞)) 但是您随后必须尝试使用​​查询来包含 AS(隐式或显式)以重命名提取的列。

我建议使用唯一的列名是避免此类歧义的方法。

进入 grandparent/grandchild。

简而言之,您接近了,但是您检索到的是 AreaPOJO(带类型的区域)而不是 AreaEntity,但是您必须告诉 Room 使用 AreaEntity class(因为那是 class用于确定 AreaEntity 的列,然后 AreaPOJO 中的@relation 知道获取 inderlying AreaType)。

因此,尽管未经测试但已成功编译,请考虑以下事项:-

@Entity(tableName = "UNIT")
data class UnitEntity(
    @PrimaryKey val id:String,
    @ColumnInfo(name = "sample_id") val parentAreaId : String,
    @ColumnInfo(name = "area_id") val areaId:Int,
    @ColumnInfo(name = "pack_type_id") val packTypeId: Int,
    @ColumnInfo(name = "is_active") val isActive:Boolean
)
@Entity(tableName = "AREA_TYPE")
data class AreaTypeEntity(
    @PrimaryKey @ColumnInfo(name = "area_type_id") val id:String, //<<<<< unique name
    val title:String,
    @ColumnInfo(name = "area_type_title") val parentAreaId : String
)

data class AreaPOJO(
    @Embedded val areaEntity : AreaEntity,
    @Relation(
        parentColumn = "area_type_id", //<<<<< changed accrodingly
        entityColumn = "area_type_id" //<<<<< changed accordingly
    )
    val areaTypeEntity : AreaTypeEntity
)

data class UnitPOJO (
    @Embedded val unitEntity : UnitEntity,

    @Relation (
        entity = AreaEntity::class, //<<<<< ADDED 
        parentColumn = "area_id",
        entityColumn = "area_id"
    )
    val areaWithAreaType : AreaPOJO,
    @Relation (
        parentColumn = "pack_type_id",
        entityColumn = "pack_id"
    )
    val packEntity : PackEntity,
    @Relation (
        parentColumn = "sample_id",
        entityColumn = "sample_id"
    )
    val sampleEntity : SampleEntity
)
  • 请注意 UnitPOJO 中的 pack_type_idpack_id,而不是示例 reference/relationship 中的 sample_idsample_id
    • 我建议考虑使用唯一名称,例如pack_type_id是referencing/mapping单元和pack的关系,或许命名单元中的列pack_id_map。因此,列名更具描述性和唯一性。缺点是编码比较多

使用上面的@Query 可以是:-

@Transaction
@Query("SELECT * FROM UNIT")
fun getUnitsWithRelations(): List<UnitPOJO>
  • 明显根据Flow/Live数据等调整

也就是说,当 Room 处理它构建的 @Relation 时,上面的代码效率低下,并且每个 @Relation 的基础查询从父级获取所有子级(我相信基于每个父级) .在您的情况下,您似乎有一对多关系,因此可以使用 @Embedded 但查询必须更复杂。

工作示例

以下是基于您的代码的工作示例

  • 同时使用 @Relation@Embedded 分辨率
    • @Relation POJO 是 UnitPOJO 和 AreaPOJO
    • @Embedded 版本以 Alternative
    • 为前缀
  • 添加数据(每行 3 行 table,5 个单位除外)
  • 使用两种备选方案提取单位和相关数据
  • 包括强制引用和维护完整性的外键约束,请参阅 https://sqlite.org/foreignkeys.html

应该注意的是,我相信您有一些不寻常的和猜测不必要的关系,因此已经进行了一些更改。例如当只需要一个时,您似乎有 Area 与 AreaType 两种方式相关。也就是说,一个 Area 将有一个 AreaType 作为父级,但是如果一个 AreaType 也有一个 Area 作为父级,那么你就会遇到先有鸡还是先有蛋的情况。

  • 假设 Area 具有许多可用 AreaType 之一作为父级。

首先是 classes(见评论):-

@Entity(
    tableName = "AREA",

    /* Enforces/Maintains referential Integrity */
    /* i.e does not allow orphans */
    foreignKeys = [
        ForeignKey(
            entity =  AreaTypeEntity::class,
            parentColumns =  ["area_type_id"],
            childColumns = ["area_type_id_map" ],
            onDelete = ForeignKey.CASCADE /* ????? */,
            onUpdate = ForeignKey.CASCADE /* ????? */
        )
    ]
)
data class AreaEntity(
    @PrimaryKey @ColumnInfo(name = "area_id")val id:String, //<<<<< unique name
    @ColumnInfo(name = "area_title") val title:String,
    @ColumnInfo(name = "area_type_id_map") val areaTypeId:String, //<<<<< see Area Type
    @ColumnInfo(name = "area_is_active") val isActive:Boolean
)
@Entity(tableName = "SAMPLE")
data class SampleEntity(
    @PrimaryKey @ColumnInfo(name = "sample_id") val id:String, //<<<<< unique name
    @ColumnInfo(name = "sample_title") val title:String,
)
@Entity(tableName = "PACK")
data class PackEntity(
    @PrimaryKey @ColumnInfo(name = "pack_id") val id:String, //<<<<< unique name
    @ColumnInfo(name = "pack_title") val title:String,  //<<<<< unique name
)

@Entity(
    tableName = "UNIT",
    foreignKeys = [
        ForeignKey(
            entity = SampleEntity::class,
            parentColumns = ["sample_id"],
            childColumns = ["sample_id_map"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        ),
        ForeignKey(
            entity = AreaEntity::class,
            parentColumns = ["area_id"],
            childColumns = ["area_id_map"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        ),
        ForeignKey(
            entity = PackEntity::class,
            parentColumns = ["pack_id"],
            childColumns = ["pack_id_map"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        )
    ]
)
data class UnitEntity(
    @PrimaryKey val id:String,
    @ColumnInfo(name = "sample_id_map") val sampleId : String,
    @ColumnInfo(name = "area_id_map") val areaId:String,
    @ColumnInfo(name = "pack_id_map") val packTypeId: String,
    @ColumnInfo(name = "unit_is_active") val isActive:Boolean
)
@Entity(
    tableName = "AREA_TYPE"
)
data class AreaTypeEntity(
    @PrimaryKey @ColumnInfo(name = "area_type_id") val id:String, //<<<<< unique name
    @ColumnInfo(name = "area_type_title") val title:String,

    /* ???? should an area type have an area as a parent? potential issues if so */
    /* commented out
    @ColumnInfo(name = "area_type_title") val parentAreaId : String //<<<<< unique name
    */
)

data class AreaPOJO(
    @Embedded val areaEntity : AreaEntity,
    @Relation(
        parentColumn = "area_type_id_map", //<<<<< changed accordingly
        entityColumn = "area_type_id" //<<<<< changed accordingly
    )
    val areaTypeEntity : AreaTypeEntity
)

data class UnitPOJO (
    @Embedded val unitEntity : UnitEntity,
    @Relation (
        entity = AreaEntity::class, //<<<<< ADDED
        parentColumn = "area_id_map",
        entityColumn = "area_id"
    )
    val areaWithAreaType : AreaPOJO,
    @Relation (
        parentColumn = "pack_id_map",
        entityColumn = "pack_id"
    )
    val packEntity : PackEntity,
    @Relation (
        parentColumn = "sample_id_map",
        entityColumn = "sample_id"
    )
    val sampleEntity : SampleEntity
)
data class AlternativeAreaPOJO (
        @Embedded val areaEntity: AreaEntity,
        @Embedded val areaTypeEntity: AreaTypeEntity
        )

data class AlternativeUnitPOJO (
        @Embedded val unitEntity: UnitEntity,
        @Embedded val alternativeAreaPOJO: AlternativeAreaPOJO,
        @Embedded val packEntity: PackEntity,
        @Embedded val sampleEntity: SampleEntity
        )

@Dao注解接口AllDao :-

@Dao
interface AllDAO {

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(areaEntity: AreaEntity): Long
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(unitEntity: UnitEntity): Long
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(sampleEntity: SampleEntity): Long
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(packEntity: PackEntity): Long
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(areaTypeEntity: AreaTypeEntity)

    @Transaction
    @Query("SELECT * FROM UNIT")
    fun getUnitsWithRelations(): List<UnitPOJO>

    @Query("SELECT * FROM UNIT " +
            "INNER JOIN AREA ON UNIT.area_id_map = AREA.area_id " +
            "INNER JOIN AREA_TYPE ON AREA.area_type_id_map = AREA_TYPE.area_type_id " +
            "INNER JOIN SAMPLE ON UNIT.sample_id_map = SAMPLE.sample_id " +
            "INNER JOIN PACK ON UNIT.pack_id_map = PACK.pack_id")
    fun getAlternativeUnitsWithRelations(): List<AlternativeUnitPOJO>
}

@Database 注解 class TheDatabase :-

@Database(entities = [
    AreaEntity::class,
    SampleEntity::class,
    PackEntity::class,
    UnitEntity::class,
    AreaTypeEntity::class
                     ],
    version = 1,
    exportSchema = false
)
abstract class TheDatabase: RoomDatabase() {
    abstract fun getAllDAO(): AllDAO

    companion object {
        private var instance: TheDatabase? = null
        fun getInstance(context: Context): TheDatabase {
            if (instance == null) {
                instance = Room.databaseBuilder(
                    context,
                    TheDatabase::class.java,
                    "the_database.db"
                )
                    .allowMainThreadQueries()
                    .build()
            }
            return instance as TheDatabase
        }
    }
}
  • 为方便起见,请注意 .allowMainThreadQueries 已被使用。

activity 中的代码(旨在 运行 一次):-

class MainActivity : AppCompatActivity() {

    lateinit var db: TheDatabase
    lateinit var dao: AllDAO
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        db = TheDatabase.getInstance(this)
        dao = db.getAllDAO()

        val TAG = "DBINFO"

        val p1 = PackEntity("P001","Pack1")
        val p2 = PackEntity("P002","Pack2")
        val p3 = PackEntity("P003","Pack3")
        dao.insert(p1)
        dao.insert(p2)
        dao.insert(p3)
        val s1 = SampleEntity("S001","Sample1")
        val s2 = SampleEntity("S002","Sample2")
        val s3 = SampleEntity("S003","Sample3")
        dao.insert(s1)
        dao.insert(s2)
        dao.insert(s3)
        val at1 = AreaTypeEntity("AT001","AreaType1")
        val at2 = AreaTypeEntity("AT002","AreaType2")
        val at3 = AreaTypeEntity("AT003","AreaType3",)
        dao.insert(at1)
        dao.insert(at2)
        dao.insert(at3)
        val a1 = AreaEntity("A001","Area1",at1.id,true)
        val a2 = AreaEntity("A002","Area2",at2.id,false)
        val a3 = AreaEntity("A003","Area3",at1.id,true)
        dao.insert(a1)
        dao.insert(a2)
        dao.insert(a3)

        dao.insert(UnitEntity("U001",s1.id,a1.id,p1.id,true))
        dao.insert(UnitEntity("U002",s2.id,a2.id,p2.id, false))
        dao.insert(UnitEntity("U003",s3.id,a3.id,p3.id,true))
        dao.insert(UnitEntity("U004",s1.id,a2.id,p3.id,false))
        dao.insert(UnitEntity("U005",s3.id,a2.id,p1.id, true))

        for(uwr in dao.getUnitsWithRelations()) {
            Log.d(TAG,
                "Unit is ${uwr.unitEntity.id} " +
                    "Active = ${uwr.unitEntity.isActive} " +
                        "Sample is ${uwr.sampleEntity.title} " +
                        "Area is ${uwr.areaWithAreaType.areaEntity.title} " +
                        "AreaType is ${uwr.areaWithAreaType.areaTypeEntity.title}"
            )
        }

        for (auwr in dao.getAlternativeUnitsWithRelations()) {
            Log.d(TAG,
                "Unit is ${auwr.unitEntity.id} " +
                        "Active is ${auwr.unitEntity.isActive} " +
                        "Sample is ${auwr.sampleEntity.title} " +
                        "Area is ${auwr.alternativeAreaPOJO.areaEntity.title} " +
                        "AreaType is ${auwr.alternativeAreaPOJO.areaTypeEntity.title}"
            )
        }
    }
}

日志的最后结果输出:-

2022-04-05 09:32:40.528 D/DBINFO: Unit is U001 Active = true Sample is Sample1 Area is Area1 AreaType is AreaType1
2022-04-05 09:32:40.528 D/DBINFO: Unit is U002 Active = false Sample is Sample2 Area is Area2 AreaType is AreaType2
2022-04-05 09:32:40.529 D/DBINFO: Unit is U003 Active = true Sample is Sample3 Area is Area3 AreaType is AreaType1
2022-04-05 09:32:40.529 D/DBINFO: Unit is U004 Active = false Sample is Sample1 Area is Area2 AreaType is AreaType2
2022-04-05 09:32:40.529 D/DBINFO: Unit is U005 Active = true Sample is Sample3 Area is Area2 AreaType is AreaType2

2022-04-05 09:32:40.537 D/DBINFO: Unit is U001 Active is true Sample is Sample1 Area is Area1 AreaType is AreaType1
2022-04-05 09:32:40.537 D/DBINFO: Unit is U002 Active is false Sample is Sample2 Area is Area2 AreaType is AreaType2
2022-04-05 09:32:40.537 D/DBINFO: Unit is U003 Active is true Sample is Sample3 Area is Area3 AreaType is AreaType1
2022-04-05 09:32:40.537 D/DBINFO: Unit is U004 Active is false Sample is Sample1 Area is Area2 AreaType is AreaType2
2022-04-05 09:32:40.537 D/DBINFO: Unit is U005 Active is true Sample is Sample3 Area is Area2 AreaType is AreaType2
  • 即两种选择的结果相同,当然关系按预期工作

其实我已经通过这个POJO解决了:

    data class UnitPOJO
         (
         @Embedded val unit: UnitEntity,
         @Relation(
              parentColumn = "sample_id",
              entityColumn = "id"
          )
          val sampleEntity: SampleEntity,

          @Relation(
               parentColumn = "pack_type_id",
               entityColumn = "id"
           )
           val pack: PackEntity,

           @Relation(
              parentColumn = "area_id",
              entityColumn = "id",
              entity = AreaEntity::class
           )
           val area: AreaPOJO

    )

AreaPOJO 是这样的:

     data class AreaPOJO(
         @Embedded val areaEntity: AreaEntity,
         @Relation(
              parentColumn = "area_type_id",
              entityColumn = "id"
          )
          val areaTypeEntity: AreaTypeEntity
       )

但我肯定会考虑你的警告@MikeT 关于在使用分层数据时命名 fields/column 名称。如果出现问题或者我得到意想不到的结果,我一定会使用这种方法来解决问题。