【问题标题】:Get data from 4 tables in Android Room从 Android Room 中的 4 个表中获取数据
【发布时间】:2022-09-27 12:40:59
【问题描述】:

我正在尝试使用 Room 从数据库中获取数据,我想以 {registration_number, List, List} 格式获取数据,但出现错误: \"在...中找不到父实体列area_name还有我的中级\" 实际上我隐藏了也许我采取了错误的方法,请指导我,因为我是这个领域的新手

提取数据我使用中间类 我的课是:

data class LastConfiscats(
@ColumnInfo(name = \"registration_number\")
var slaugh_num: String,
//    @ColumnInfo(name = \"area_name\",
@Relation(entity = Area::class, parentColumn = \"area_name\", entityColumn = \"name\")
var areaName: List<String>,
//    @ColumnInfo(name = \"confiscation_name\")
@Relation(entity = Confiscation::class, parentColumn = \"confiscation_name\", entityColumn = \"name\")
var confiscationName: List<String>

和DAO方法来选择数据:

    @Query(\"SELECT registration_number,  area.[name] AS area_name, confiscations.[name] AS confiscation_name \" +
        \"FROM car_body, car_body_confiscations\" +
        \"INNER JOIN area ON car_body_confiscations.area_id == area.id \" +
        \"INNER JOIN confiscations ON car_body_confiscations.confiscation_id == confiscations.id \" +
        \"WHERE car_body.id == car_body_confiscations.car_body_id ORDER BY car_body.id DESC LIMIT :row_count\")
fun getLastConfiscats(row_count: Int): LiveData<List<LastConfiscats>>

我试图实现的表之间的链接方案如下:

互联网上有示例如何在 2 个表之间建立关系,但我需要在 4 个表之间建立关系。

请帮助我以正确的方式获取数据

更新 :

我的区域实体是:

 @Entity(tableName = \"area\")
 data class Area( @PrimaryKey(autoGenerate = true) var id: Int?, var name: String? )

但在我的没收实体中,我也有 \"name\" 列:

@Entity(tableName = \"confiscations\")
data class Confiscation( @PrimaryKey(autoGenerate = true) var id: Int?, var name: String? )
  • 根据你的图表没有area_name字段/列也许你的意思area.name
  • 在我的查询中,我使用别名 \"@Query(\"SELECT registration_number, area.[name] AS area_name, ...\"
  • 但这在查询范围内。 Room 不会使用您的连接,它使用 @Relation 来模拟连接。所以别名不会起作用。重要的是 Area 类中定义的内容。所以 Room 是说 Area 中没有名为 area_name 的字段(该消息应该为您提供可能字段的列表,其中之一将是名称)。
  • 我的区域实体是:@Entity(tableName = \"area\") data class Area(@PrimaryKey(autoGenerate = true) var id: Int?, var name: String?) 但在我的没收实体中我也有\"name \" 列:@Entity(tableName = \"confiscations\") 数据类没收(@PrimaryKey(autoGenerate = true) var id: Int?, var name: String?)
  • 我已经完成了主要描述以提高可读性

标签: android-room


【解决方案1】:

您收到的实际消息是因为当您使用@Relation 时,父级必须存在并使用@Embedded 进行注释。

父列和实体列必须是各自类中的列。

例如,以下内容将使您能够获得一份没收清单,以及相关的 CarBody 和相应的区域(注意基于屏幕截图的列名):-

data class LastConfiscats(
    @Embedded
    var carBodyConfiscations: Car_Body_Confiscations,
    @Relation(entity = CarBody::class, parentColumn = "car_body_id", entityColumn = "id")
    var carBody: CarBody,
    @Relation(entity = Area::class, parentColumn = "areaId", entityColumn = "id")
    var area: List<Area>
)

您可以将上述内容与查询一起使用,例如:-

@Query("SELECT * FROM Car_Body_Confiscations")
fun getCardBodyJoinedWithStuff(): List<LastConfiscats>

无需加入。那是因为 Room 构建了底层 SQL。首先是提供的查询的副本。在检索 Car_Body_Confiscations 后,它使用基于字段名称/@ColumnInfo 的查询并针对每个 Car_Body_Connfiscation 运行查询。

对于每个@Relationship,它使用它构建的查询填充各自的字段(1 carBody 和区域列表)。以下是部分代码的示例,来自上述查询的java(生成): -

主要(父查询)

@Override
  public List<LastConfiscats> getCardBodyJoinedWithStuff() {
    final String _sql = "SELECT * FROM Car_Body_Confiscations";
    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
    ....

后来 Nn (得到 CarBody(s) 将只有 1)

StringBuilder _stringBuilder = StringUtil.newStringBuilder();
_stringBuilder.append("SELECT `id`,`registrationNumber`,`datetime`,`userId`,`testId` FROM `CarBody` WHERE `id` IN (");
final int _inputSize = _map.size();

甚至后来(地区)

StringBuilder _stringBuilder = StringUtil.newStringBuilder();
_stringBuilder.append("SELECT `id`,`name` FROM `Area` WHERE `id` IN (");

现在,如果您想编写自己的 JOINS 等和别名列,那么您将不得不考虑一些事情。

接收类必须能够从结果集中构建,因此列名必须与 POJO 中的字段匹配(除非使用 @Prefix 注释)。

您还需要注意,结果集将是笛卡尔积,因此在执行上述操作的情况下,绕过 Room 是如何做到的,对于没收/车身/区域的每个组合/排列,您会得到一行(除非分组/ 被 where 子句排除)。因此,如果您将 1 辆没收车与 1 辆汽车相连,但有 10 个区域,那么您将获得 10 行都具有相同的没收车和车身。

您不妨考虑看看Room @Relation annotation with a One To Many relationship。这进一步解释了这一点,并包括一个使用 JOIN 的示例

附加 - 用户和测试列表

您可能希望包含 CarBody 的 User 和 Test_Lists,以便获得包含所有相关数据的结果。

这需要从层次的角度来看待。也就是说,没收具有到 CarBody 的直接链接/引用/映射,但在其下方是从 CarBody 到用户的链接/引用/映射和到 Test_Lists。

所以要合并这个,你需要一个用于 CarBody 的 POJO,它是 User 和 Test_Lists。因此,例如:-

data class CarBodyWithUserAndWithTestList(
    @Embedded
    var carBody: CarBody,
    @Relation(
        entity = Users::class,
        parentColumn = "userId",
        entityColumn = "id"
    )
    var users: Users,
    @Relation(
        entity = Test_List::class,
        parentColumn = "testId",
        entityColumn = "id"
    )
    var testList: List<Test_List>
)

有了这个,你就可以修改最后没收包括一个CarBodyWithUserAndWithTestList而不仅仅是一个车身例如。:

data class LastConfiscats(
    @Embedded
    var carBodyConfiscations: Car_Body_Confiscations,
    @Relation(entity = CarBody::class, parentColumn = "car_body_id", entityColumn = "id")
    //var carBody: CarBody, /* REMOVED */
    var carBodyWithUserAndWithTestList: CarBodyWithUserAndWithTestList, /* ADDED */
    @Relation(entity = Area::class, parentColumn = "areaId", entityColumn = "id")
    var area: List<Area>
)
  • 笔记@Relation 将 CarBody 类作为实体。这是因为 CarBody 是需要检查的类,以便 Room 确定用于链接/引用/映射的列。

*工作示例/演示


这是一个工作示例的完整代码,该示例将一些数据插入到所有表中,然后使用getCardBodyJoinedWithStuff查询,然后将数据写入日志。

  • 代码包含 ForeignKey 约束,它强制执行并帮助维护参考的正直。
  • IDLong 而不是 Int 已被用作 Long 正确反映了字段/值的潜在大小。
  • autoGenerate = true 没有被使用,因为这是低效且不需要的,请参阅 https://sqlite.org/autoinc.html,其中包括作为第一条语句AUTOINCREMENT 关键字强加了额外的 CPU、内存、磁盘空间和磁盘 I/O 开销,如果不是严格需要,应避免使用。通常不需要它。(autoGenerate = true 结果为 AUTOINCREMENT)

所以所有的类/接口:-

@Entity(
    foreignKeys = [
        ForeignKey(
            Users::class,
            parentColumns = ["id"],
            childColumns = ["userId"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        ),
        ForeignKey(
            Test_List::class,
            parentColumns = ["id"],
            childColumns = ["testId"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        )
    ]
)
data class CarBody(
    @PrimaryKey
    var id: Long?=null,
    var registrationNumber: Int,
    var datetime: String,
    @ColumnInfo(index = true)
    var userId: Long,
    @ColumnInfo(index = true)
    var testId: Long
)
@Entity
data class Users(
    @PrimaryKey
    var  id:Long?=null,
    var name: String,
    var lastName: String,
    var email: String,
    var password: String
)
@Entity
data class Test_List(
    @PrimaryKey
    var id: Long?=null,
    var date: String,
    var is_saved: Boolean
)
@Entity(
    foreignKeys = [
        ForeignKey(
            entity = CarBody::class,
            parentColumns = ["id"],
            childColumns = ["car_body_id"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        ),
        ForeignKey(
            entity = Confiscation::class,
            parentColumns = ["id"],
            childColumns = ["confiscation_id"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        ),
        ForeignKey(
            entity = Area::class,
            parentColumns = ["id"],
            childColumns = ["areaId"],
            onDelete = ForeignKey.CASCADE,
            onUpdate = ForeignKey.CASCADE
        )
    ]
)
data class Car_Body_Confiscations(
    @PrimaryKey
    var id: Long?=null,
    @ColumnInfo(index = true)
    var car_body_id: Long,
    @ColumnInfo(index = true)
    var confiscation_id: Long,
    @ColumnInfo(index = true)
    var areaId: Long
)
@Entity
data class Area(
    @PrimaryKey
    var id: Long?=null,
    var name: String
)
@Entity
data class Confiscation(
    @PrimaryKey
    var id: Long?=null,
    var name: String
)
@Dao
interface AllDao {

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(area: Area): Long
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(carBodyConfiscations: Car_Body_Confiscations): Long
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(carBody: CarBody): Long
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(confiscation: Confiscation): Long
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(users: Users): Long
    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun insert(testList: Test_List): Long

    @Transaction
    @Query("SELECT * FROM Car_Body_Confiscations")
    fun getCardBodyJoinedWithStuff(): List<LastConfiscats>

}
@Database(entities = [
    Area::class,
    Car_Body_Confiscations::class,
    CarBody::class,
    Confiscation::class,
    Users::class,
    Test_List::class
                     ],
    exportSchema = false, version = 1)
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
        }
    }
}

data class LastConfiscats(
    @Embedded
    var carBodyConfiscations: Car_Body_Confiscations,
    @Relation(entity = Confiscation::class, parentColumn = "confiscation_id", entityColumn = "id")
    var confiscation: Confiscation,
    @Relation(entity = CarBody::class, parentColumn = "car_body_id", entityColumn = "id")
    //var carBody: CarBody, /* REMOVED */
    var carBodyWithUserAndWithTestList: CarBodyWithUserAndWithTestList, /* ADDED */
    @Relation(entity = Area::class, parentColumn = "areaId", entityColumn = "id")
    var area: List<Area>
)

data class CarBodyWithUserAndWithTestList(
    @Embedded
    var carBody: CarBody,
    @Relation(
        entity = Users::class,
        parentColumn = "userId",
        entityColumn = "id"
    )
    var users: Users,
    @Relation(
        entity = Test_List::class,
        parentColumn = "testId",
        entityColumn = "id"
    )
    var testList: List<Test_List>
)

以下活动代码(注意主线程用于简洁和方便): -

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()


        dao.insert(Users(100,"Fred","Bloggs","fredBloggs@mail.com","password"))
        dao.insert(Users(200,"Jane","Doe","janeDoe@email.org","password"))
        /* example where id is autogenerated */
        val marySmithId = dao.insert(Users(name = "Mary", lastName = "Smith", email = "marysmith@mailit.co.uk", password = "1234567890"))

        dao.insert(Test_List(1,"2022-01-01",false))
        dao.insert(Test_List(2,"2022-02-02",true))

        dao.insert(CarBody(1000,1234,"2022-01-01",100 /* Fred Bloggs*/,2 ))
        dao.insert(CarBody(2000,4321,"2021-12-05",100,1))
        dao.insert(CarBody(3000,1111,"2021-09-10",200,2))

        dao.insert(Area(100,"Area100"))
        dao.insert(Area(200,"Area200"))
        dao.insert(Area(300,"Area300"))
        dao.insert(Area(400,"Area400"))

        dao.insert(Confiscation(901,"C1"))
        dao.insert(Confiscation(902,"C2"))
        dao.insert(Confiscation(903,"C3"))
        dao.insert(Confiscation(904,"C4"))

        dao.insert(Car_Body_Confiscations(500,1000,901,100))
        dao.insert(Car_Body_Confiscations(510,2000,904,400))
        dao.insert(Car_Body_Confiscations(520,3000,902,300))

        /* Extract the data and output to the Log */
        for(cbc in dao.getCardBodyJoinedWithStuff()) {
            val areaList = StringBuilder()
            for (a in cbc.area) {
                areaList.append("\n\t\tArea is ${a.name} ID is ${a.id}")
            }
            val testList = StringBuilder()
            testList.append("\n\t\tThere are ${cbc.carBodyWithUserAndWithTestList.testList.size} TestLists, they are:")
            for (t in cbc.carBodyWithUserAndWithTestList.testList) {
                testList.append("\n\t\t\t${t.date} Save is ${t.is_saved} ID is ${t.id}")
            }
            Log.d(
                "DBINFO",
                "CBC ID =${cbc.carBodyConfiscations.id}" +
                        "\n\tConfiscation Name is ${cbc.confiscation.name}" +
                        "\n\tAreas (there is/are ${cbc.area.size}) they are $areaList}" +
                        "\n\tCarBody Reg is ${cbc.carBodyWithUserAndWithTestList.carBody.registrationNumber}  " +
                        "Date is ${cbc.carBodyWithUserAndWithTestList.carBody.datetime}" +
                        "\n\t\tUser is ${cbc.carBodyWithUserAndWithTestList.users.name}" +
                        ",${cbc.carBodyWithUserAndWithTestList.users.lastName} " +
                        "email is ${cbc.carBodyWithUserAndWithTestList.users.email}" +
                        "$testList"
            )
        }
    }
}

结果

运行后的日志:-

 D/DBINFO: CBC ID =500
        Confiscation Name is C1
        Areas (there is/are 1) they are 
            Area is Area100 ID is 100}
        CarBody Reg is 1234  Date is 2022-01-01
            User is Fred,Bloggs email is fredBloggs@mail.com
            There are 1 TestLists, they are:
                2022-02-02 Save is true ID is 2
                
                
 D/DBINFO: CBC ID =510
        Confiscation Name is C4
        Areas (there is/are 1) they are 
            Area is Area400 ID is 400}
        CarBody Reg is 4321  Date is 2021-12-05
            User is Fred,Bloggs email is fredBloggs@mail.com
            There are 1 TestLists, they are:
                2022-01-01 Save is false ID is 1
                
                
 D/DBINFO: CBC ID =520
        Confiscation Name is C2
        Areas (there is/are 1) they are 
            Area is Area300 ID is 300}
        CarBody Reg is 1111  Date is 2021-09-10
            User is Jane,Doe email is janeDoe@email.org
            There are 1 TestLists, they are:
                2022-02-02 Save is true ID is 2

重新评论

我实际上有一个笛卡尔积,我必须以某种方式处理它,尽管我还不知道如何处理。

您可能会发现上述方法很好,并且很容易处理产品。

如果您想有选择地检索相关数据,Room 的关系处理可能会受到限制。 Room 处理 @Relation 的方式意味着它检索全部无论任何 JOINS 和 WHERE 子句如何。它们仅在影响最顶层父级的结果时才有效。

在您的情况下,如果您实际上并不满足列表(例如每个车身多个用户),那么 Room 就足够了。


原始查询 - 重新访问


将您的查询稍微更改为(主要是为了适应以前的课程):-

@Query("SELECT " +
        "registrationNumber,  " +
        "area.[name] AS area_name, " +
        "confiscation.[name] AS confiscation_name " +
        "FROM carbody, car_body_confiscations " +
        "INNER JOIN area ON car_body_confiscations.areaId == area.id " +
        "INNER JOIN confiscation ON car_body_confiscations.confiscation_id == confiscation.id " +
        "WHERE carbody.id == car_body_confiscations.car_body_id " +
        "ORDER BY carbody.id DESC " +
        "LIMIT :row_count"
)
fun getLastConfiscats(row_count: Int): /*LiveData<*/List<MyQueryPOJO>/*>*/
  • 请参阅以下内容MyQueryPOJO

并添加一个合适的类(不需要@Embeddeds 或@Relations,所以 Room 不会与列名混淆):-

data class MyQueryPOJO(
    /* The output columns of the query */
    var registrationNumber: Int,
    @ColumnInfo(name = "area_name")
    var not_the_area_name: String,
    var confiscation_name: String
)
  • 注意如何not_the_area_name字段有 @ColumnInfo 注释告诉它使用area_name输出列

在活动中,使用:-

    for (mqo in dao.getLastConfiscats(10)) {
        Log.d("DBINFO","Reg = ${mqo.registrationNumber} Confiscation = ${mqo.confiscation_name} Area Name = ${mqo.not_the_area_name}")
    }

结果(使用相同的数据):-

D/DBINFO: Reg = 1111 Confiscation = C2 Area Name = Area300
D/DBINFO: Reg = 4321 Confiscation = C4 Area Name = Area400
D/DBINFO: Reg = 1234 Confiscation = C1 Area Name = Area100
  • 因为关系基本上都是 1-1(对于 1-many,引用是从前到后的),笛卡尔乘积很好,因为不会有任何重复。

【讨论】:

  • 非常感谢您提供如此美丽且易于理解的答案!!!!我实际上有一个笛卡尔积,我必须以某种方式处理它,尽管我还不知道如何处理。非常感谢 !
  • @АндрійХ 我在答案中添加了更多内容。一个可行的例子,我相信它迎合了我不得不以某种方式处理它(即笛卡尔列表由 Room 处理)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-04-20
  • 1970-01-01
  • 2021-02-04
  • 2023-04-01
  • 1970-01-01
相关资源
最近更新 更多