您收到的实际消息是因为当您使用@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 约束,它强制执行并帮助维护参考的正直。
- 为ID的
Long 而不是 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>/*>*/
并添加一个合适的类(不需要@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,引用是从前到后的),笛卡尔乘积很好,因为不会有任何重复。