一、前言

在使用 Room 库的过程中,定义数据实体类来表示需要存储的数据对象,每一个数据实体类与关联的数据库中的表相对应,数据实体类的每一个字段对应表中的列,每一个数据实体类对象都对应表中的一行数据(如果不了解 Room 库,请先阅读:Android Room 库基础入门)。这意味着使用 Room 数据实体类,可以不需要任何的 SQL 语句即可定义数据库架构。

二、Room 数据实体类详解

2.1 Room 数据实体类定义

定义 Room 数据实体类,使用 data class 关键字,并使用 @Entity 注解标注。如下代码所示:

@Entity
class User(@PrimaryKey val uid: Int, val name: String, val age: Int)

注意事项:
1. 为了保留实体类中的属性作为表的一列,Room 必须有权限访问这个属性,如果 Room 无法访问属性,该属性不会成为对应数据库表中的一列。可以将属性访问修饰设置为 public,或者为属性的提供 getter()setter() 方法,来确保 Room 可以访问数据实体类的属性。
2. 若使用 Kotlin开发,在构造函数中定义的属性,不为空的字段表结构中对应的列为 NOT NULL(不可为空),如果需要列为可为空,那么在定义属性可为空即可,如下所示。

// 该数据实体类对应的表结构为 CREATE TABLE teacher(tid INTEGER NOT NULL PRIMARY KEY, name TEXT NOT NULL, subject TEXT);
@Entity
// subject 属性定义为可为空,这样生成的表结构中,对应的列也是可为空
data class Teacher(@PrimaryKey val tid: Int, @ColumnInfo val name: String,  @ColumnInfo  var subject: String?)

2.2 指定数据实体类对应的表名

默认情况下,Room 会根据实体类的类为表名(在数据库中表名其实不区分大小写),开发者也可以在 @Entity 注解通过 tableName 参数指定表名,这样 Room 在创建表的时候就是以指定的名称命名。如下示例代码所示:

@Entity(tableName = "users")
data class User(@PrimaryKey val uid: Int, val name: String, val age: Int)

注意事项:数据库表的名称是不区分大小写的。

2.3 设定数据表主键

每一个数据实体类必须定义一个主键,确保在数据表中每一行数据唯一。可以指定一个或者多个列作为表主键,设置主键可以在定义数据实体类属性时使用 @PrimaryKey 注解标示(指定单个列为主键建议使用此方法),也可以在定义数据实体类时在 @Entity 注解中通过 primaryKeys 属性声明(指定多个列组合为主键建议使用此方法, @Entity 注解的 primaryKeys 属性是一个数组)。如下代码示例所示:

// 在数据实体类的属性使用 @PrimaryKey 注解声明主键
@Entity(tableName = "users")
data class User(@PrimaryKey val uid: Int, val name: String, age: Int)// 在 `@Entity` 注解中通过 `primaryKeys` 属性声明主键
@Entity(tableName = "users", primaryKeys = ["uid"])
data class User(val uid: Int, val name: String, val age: Int)

如果指定单个列为主键,还可以设置主键值自动递增,这样一来在实例化数据实体类对象时就无需指定数据实体类中主键的值。通过将 @PrimaryKey 注解的 autoGenerate 属性值设置为 true 即可,但是要注意的是,如果设置为主键值递增,那么在实例化时主键属性就不要赋值,在构造方法中将主键参数设置可为空即可(这样在实例化时主键参数传入null,由数据库实现自增)。如下代码示例所示:

@Entity(tableName = "users")
data class User(@PrimaryKey(autoGenerate = true) var uid: Int?, @ColumnInfo(name = "name") val name: String, val age: Int)// 实例化时,主键参数传入null即可
userDao.insertAll(User(null, "Student1", 18, null),User(null, "Student2", 18, null),User(null, "Student3", 17, null),User(null, "Student4", 19, null))

2.4 指定表的列名称

默认情况下,Room 会根据数据实体类的属性名作为对应列的名称,如果开发者想指定不同的名称,可以使用 @ColumnInfo 注解的 name 属性指定列名称。如下示例代码所示:

@Entity(tableName = "users", primaryKeys = ["uid"])
data class User(val uid: Int, @ColumnInfo(name = "name") val name: String, val age: Int)

注意事项:数据库表的列名称是不区分大小写的。

小窍门:在定义数据实体类的时候,可能会有疑问,如果要定义一个字段为主键,又想指定主键列在表中的列名,该怎么办?其中一种办法就是通过 @Entity 指定主键,然后在主键属性上使用 @ColumnInfo 注解指定主键列的列名。另一种方法就是注解叠加使用,那就是在主键属性上同时使用 @PrimaryKey 注解和 @ColumnInfo 注解。如下示例:

@Entity(tableName = "users")
data class User(@PrimaryKey @ColumnInfo(name = "id") val uid: Int, @ColumnInfo(name = "name") val name: String, val age: Int)

2.5 忽略属性

前面提到,为了保留实体类中的属性作为表的一列,Room 必须有权限访问这个属性,如果数据实体类的属性能够被 Room 访问,但是又不想作为数据表中的一列,该怎么办?其实很简单,只要使用 @Ignore 注解标示该属性即可,这样一来,即使 Room 能够访问该属性,也会忽略该属性,不会作为数据表中的一列。如下示例所示:

@Entity(tableName = "users")
data class User(@ColumnInfo(name = "name") val name: String, val age: Int) {// 自增的主键,在类内部定义,不要出现在构造函数的参数中,这样在实例化时才不需要设置改值,@PrimaryKey(autoGenerate = true) var uid: Int = 0@Ignore var avatar: String = ""
}

通过查询数据库的表结构,可以看到效果。如下图:

2.6 提供表搜索支持

Room 提供多种类型的注解,让你更容易地对数据库中表内容进行搜索。除非应用的 miniSdkVersion 小于 16,否则请使用全文搜索(FTS)。

2.6.1 全文搜索(full-text search,FTS)支持

如果你的应用需要通过全文搜索(FTS)快速访问数据库信息,请使用虚拟表(使用 FTS3 或者 FTS4 SQLite扩展模块)为你的数据实体类提供支持。如果需要在 2.1.0 及以上版本的 Room 中使用这项功能,在声明数据实体类的时候添加 @Fts3 或者 @Fts4 注解。如下示例代码所示

@Fts4
@Entity(tableName = "users")
// 数据实体类定义了主键,主键列名必须以 rowid 为列名,数据类型为 INTEGER
data class User(@PrimaryKey @ColumnInfo(name = "rowid") val uid: Int, @ColumnInfo(name = "name") val name: String, val age: Int)

注意事项:
1. 启用全文搜索(FTS)的表,必须是以 rowid 为名称、INTEGER 格式数据的列作为主键,如果启用 FTS 的数据表对应的数据实体类定义类了主键,那么这个主键必须是指定的列名和数据类型;
2. 如果你的应用有严格的磁盘空间要求或者需要支持较低版本的 SQLite 数据库,请使用 @Fts3

如果表中存储的内容支持多语言,那么使用 @Fts4 注解的 languageId 属性指定表示语言类别的属性(仅对于 FTS4),如下示例代码所示:

@Fts4(languageId = "motherLang")
@Entity(tableName = "users")
data class User(@PrimaryKey @ColumnInfo(name = "rowid") val uid: Int, @ColumnInfo(name = "name") val name: String, val age: Int, val motherLang: String)

说明:Room 为定义支持 FTS 的实体类提了多种选项,这些选项包括结果排序、语法分析器类型以及作为外部内容管理的表,更多详细信息,请参阅: FtsOptions参考文档

2.6.2 将特定列编入索引

如果你的应用支持的 SDK 版本不允许使用 FTS3 或者 FTS4 支持的数据实体类(miniSDKVersion 小于 16),你依旧可以将某些列编入索引,用来加快查询速度。需要在定义数据实体类时添到索引,可在 @Entity 注解的 indices 属性指定需要添加到索引(或复合索引)的列名。如下示例所示:

@Entity(tableName = "users", indices = [Index("rowid", "name")])
data class User(@PrimaryKey @ColumnInfo(name = "rowid") val uid: Int, @ColumnInfo(name = "name") val name: String, val age: Int)

有时候,在数据库中的某些字段或字段组必须是惟一的,你可以通过将 @Index 注解的 unique 属性值设置为 true 来强行设置属性或属性组唯一。如下示例所示:

@Entity(tableName = "users", indices = [Index(value = ["rowid", "name"], unique = true)])
data class User(@PrimaryKey @ColumnInfo(name = "rowid") val uid: Int, @ColumnInfo(name = "name") val name: String, val age: Int)

2.7 添加基于 AutoValue 的对象

注意事项:这个功能是专为基于 Java 的实体类设计的,基于 Kotlin 的实体类想要达到相同的目的,最好使用数据类。

在 Room 2.1.0 之后的版本,你可以使用基于 Java 的不可变值类,不可变值类是在应用数据库中使用用 @AutoValue 标注的实体类。这个功能在用来比较两个实体类对象是否相等(每列的值是否完全一致)时非常有用。

当使用 @AutoValue 注解标注的类作为实体类时,你可以使用 @PrimaryKey@ColumnInfo@Embedded,@Relation 对类的抽象方法进行标注,在使用这些注解时,必须每次同时添加 @CopyAnnotations 注解,以便 Room 可以正确解释这些方法的自动生成实现。如下代码所示:

  • User.java
@AutoValue
@Entity
public abstract class User {// 支持的注解中必须每次都包含 `@CopyAnnotations` 注解@CopyAnnotations@PrimaryKeypublic abstract long getId();public abstract String getFirstName();public abstract String getLastName();// Room 使用这个工厂类生成 User 对象.public static User create(long id, String firstName, String lastName) {return new AutoValue_User(id, firstName, lastName);}
}

三、数据实体类定义中常用注解详解

3.1 @Entity 注解

用来标注数据类,以下是该注解包含的属性。

字段名称 数据类型 默认值 说明
tableName String - 指定表名称,没有指定时使用数据类类名作为表名
indices @Index - 用来指定索引列表
inheritSuperIndices Boolean false 如果设置为 true,会继承所有父类中定义的索引
primaryKeys String [] - 用来指定主键
foreignKeys @ForeignKey[] - 用来指定外键
ignoredColumns String [] - 用来指定被 Room 忽略的属性,被 Room 忽略的属性不会出现在表中

3.2 @PrimaryKey 注解

用来标注数据类的属性为主键,以下是该注解包含的属性。

字段名称 数据类型 默认值 说明
autoGenerate Boolean false 如果设置为 true,在定义数据实体类对象时,主键无需赋值,值会自动递增

3.3 @ColumnInfo 注解

用来标注数据类的属性详情(数据库表中列的属性),以下是该注解包含的属性。

字段名称 数据类型 默认值 说明
name String - 指定列名称,没有指定时使用数据类的属性名作为列名
index Boolean false 指定该列为索引
collate Int UNSPECIFIED(1) 用来指定列的数据校对类型
defaultValue String VALUE_UNSPECIFIED([value-unspecified]) 用来指定列的默认值

四、定义对象之间的关系

因为SQLite 数据库是一个关系型数据库,所以开发者可以在数据实体类之间指定相互关系。虽然许多对象关系映射库允许实体对象相互引用,但是在 Room 中却是禁止这么做的。Room 的数据实体类跟数据库表是对应的,所以,有些表关系可以直接通过定义数据实体类之间的关系来实现,这样就可以无需编写 SQL 语句实现插入和查询过操作。

4.1 创建内嵌的对象

有时候,开发者希望将一个实体或者数据对象在数据库逻辑中表示为一个紧密的整体,即使该实体包含了多个字段也要这么做。在这种情形下,开发者可以使用 @Embedded 注解标注对象,表示要将此对象分解成为数据表的子字段。这样就可以跟查询其他字段一样查询这些内嵌的字段。如下示例所示:

// 内嵌类
data class Address(val province: String, val city: String, val zone: String, val details: String?)// 数据实体类
@Entity(tableName = "users")
data class User(@PrimaryKey(autoGenerate = true) val uid: Int?, @ColumnInfo(name = "name") val name: String, val age: Int, @Embedded val address: Address)

讲解:以上的示例中,数据实体类 User 内嵌了 Address 类,在数据库中对应的表中,会将 Address 类的所有字段都分解称为表的列,数据库的表结构为 CREATE TABLEusers(uidINTEGER PRIMARY KEY AUTOINCREMENT,nameTEXT NOT NULL,ageINTEGER NOT NULL,provinceTEXT NOT NULL,cityTEXT NOT NULL,zoneTEXT NOT NULL,detailsTEXT);

注意事项:
1. 内嵌类内部可以内嵌其他类;
2. 内嵌类可以不是数据实体类(换句话说,可以无需 @Entity 注解标注);
3. 如果内嵌类跟数据实体属性名有冲突,必须通过 @ColumnInfo 注解重命名数据库表中的列名称(不是数据实体类也可以使用 @ColumnInfo 注解标注字段)
4. 如果有多个内嵌类,必须保证内嵌类之间、内嵌类和数据实体类之间对应列名均唯一。

4.2 定义一对一关系

两个实体之间的一对一关系是这样一种关系,其中父实体的每个实例都对应于子实体的一个实例,反之亦然(注意:这里所提到的父实体跟子实体并不是父类与子类的关系)。举个例子:一个学生对应一份学生档案,一份学生档案对应一个学生,这种对应关系就是一一对应的关系了。下面将详细介绍如何定义实体之间的一对一关系。

第一步:创建两个数据实体类(父实体和子实体)

首先,需要创建两个数据实体类,其中一个数据实体类必须包含另一个数据实体类中标识为主键的属性变量的引用。

@Entity(tableName = "students")
data class Student(@PrimaryKey val sid: Int, val name: String, val age: Int, val addr: String?)// Profile 类的ownerSid属性变量对应 Student 类的 sid 属性变量
@Entity(tableName = "profiles")
data class Profile(@PrimaryKey val fid: Int, @ColumnInfo(name = "owner_sid") val ownerSid: Int, val content: String)

注意事项:定义一一对应关系的两个数据实体类,其中一个数据实体类(子实体)中必须包含引用另个一数据实体类(父实体)主键的属性变量。

第二步:定义一对一对应关系

定义好两个数据实体类之后,为了能够查询两个对应实体的清单数据,必须定义这两个实体类之间的一对一对应关系。首先,创建一个新的数据类,这个数据类的每一个实例都保存一个父实体和它对应的子实体。父实体采用内嵌的方式,使用 @Embedded 注解标注,子实体使用 @Relation 注解标注,并设置 @Relation 注解的 parentColumn 属性值为父实体的主键列名、entityColumn 属性值为子实体中父实体主键引用的列名称。

data class StudentProfile(@Embedded val student: Student, @Relation(parentColumn = "sid", entityColumn = "owner_sid") val profile: Profile)

第三步:在 DAO 类中添加查询方法

最终,在 DAO 类中添加方法,这个方法查询结果返回数据类型为第二步创建的数据类(即定义一一对应关系的数据类),这个方法需要 Room 进行多个查询,所以需要在这个方法中添加 @Transaction 注解,确保整个操作自动执行。

interface StudentDao {@Transaction@Query("SELECT * from students") // 只需要查询父实体数据表,Room 会自动关联fun findStudentsWithProfile(): List<StudentProfile>
}

经过以上步骤,就可以调用 DAO 中对应的接口进行连表查询结果了,查询返回的结果中包含一一对应的两个实体对象。

注意事项:
1. 使用一一对应关系查询时,必须法保证父实体表中的数据在子实体表中能找到对应的数据,否则会因查询不到对应关系的数据导致异常(对应关系结果数据构建时,子实体参数为空),如果无法保证,请使用一对多关系而不是一对一关系,参考 4.3 定义一对多关系;
2. 编写 DAO 查询方法时, SQL 语句只需要查询父实体数据表,Room 会自动根据定义的关系查询子实体表。

4.3 定义一对多关系

两个实体之间的一对多关系,是指父实体表的一条数据在子实体表中对应 0 条或者多条数据,但是子实体表中的数据最多只能在父实体表中对应一条数据。举个例子:学校和学生,一个学校对应多个学生,但是每一个学生只能对应一个学校。以下将详细介绍如何定义一对多关系。

第一步:创建两个数据实体类(父实体和子实体)

首先,需要创建两个数据实体类,其中一个数据实体类必须包含另一个数据实体类中标识为主键的属性变量的引用(这一点跟创建一一对应关系类似),这个类被称之为子实体类。

@Entity(tableName = "schools")
data class School(@PrimaryKey val sid: Int, val name: String, val addr: String)@Entity(tableName = "students")
data class Student(@PrimaryKey val sid: Int, val name: String, val age: Int, val schoolId: Int, val addr: String?)

第二步:定义一对多对应关系

定义好两个数据实体类之后,为了能够查询两个对应实体的清单数据,必须定义这两个实体类之间的一对一对应关系。首先,创建一个新的数据类,这个数据类的每一个实例都保存一个父实体和它对应的子实体。父实体采用内嵌的方式,使用 @Embedded 注解标注,子实体使用 @Relation 注解标注,并设置 @Relation 注解的 parentColumn 属性值为父实体的主键列名、entityColumn 属性值为子实体中父实体主键引用的列名称(这一点跟创建一一对应关系类似)。

data class SchoolStudent(@Embedded val school: School,@Relation(parentColumn = "sid", entityColumn = "schoolId") val students: List<Student>
)

第三步:在 DAO 中添加查询方法

最终,在 DAO 类中添加方法,这个方法查询结果返回数据类型为第二步创建的数据类(即定义一多多关系的数据类),这个方法需要 Room 进行多个查询,所以需要在这个方法中添加 @Transaction 注解,确保整个操作自动执行。

interface StudentDao {@Transaction@Query("SELECT * from schools")fun findSchoolStudents(): List<SchoolStudent>
}

注意事项:编写 DAO 查询方法时, SQL 语句只需要查询父实体数据表,Room 会自动根据定义的关系查询子实体表。

4.4 定义多对多关系

两个实体之间的多对多关系,是指父实体表的一条数据在子实体表中对应 0 条或者多数据,反过来也成立(子实体表中的数据在父实体表中对应 0 条或者多条数据)。举个例子,一个老师可以教许多个学生,一个学生也可以有多个老师教(不同科目)。

多对多关系与其他类型的关系是有区别的,多对多关系在子实体中通常是没有父实体主键列的引用,而是创建一个第三方关联实体类(称之为交叉关系表)来表示两个实体之间的关联关系。交叉关系表中必须包含多对多关系中的每个实体类对应的数据表的主键列(即通过主键列进行关联)。

第一步:创建两个实体类

首先,需要创建两个数据实体类,这两个数据实体类之间是多对多关系。

@Entity(tableName = "students")
data class Student(@PrimaryKey val sid: Int, val name: String, val age: Int, val schoolId: Int, val addr: String?)@Entity(tableName = "teachers")
data class Teacher(@PrimaryKey val tid: Int, val name: String, val age: Int, val subject: String)

说明:多对多关系的两个实体,一般不需要在子实体中引用父实体的主键列。

第二步:创建第三方类(交叉关系表)

创建一个第三方类,这个类也是数据实体类(即在数据库中对应数据表,也需要使用 @Entity 注解标注),这个第三方类是用来记录多对多关系的实体之间的相互关系的类,必须包含拥有多对多关系的每个数据实体的的主键列,并且将这些主键列设置为交叉数据表的组合主键(每一个对应关系都是唯一)。

// 交叉关系表的主键列名必须跟对应的数据实体类主键列名一致,如果变量名跟列名不一致,可以使用 @columnInfo 注解指定列名
@Entity(primaryKeys = ["tid", "sid"])
data class TeachStudentCrossRef(val tid: Int, val sid: Int) // Teacher 实体类主键列名是tid, Student 实体类主键列名是sid

注意事项:第三方类(交叉关系表)中必须包含多对多关系的每个数据实体类的主键列,而且必须保证列名跟数据实体的主键列名一致(否则编译会报错)。

第三步:定义对应关系(根据查询需求)

多对多关系跟其他对应关系不一样,需要根据查询需求定义相应的对应关系,其实就是将多对多关系,根据查询需求分解成一对多关系,查询需要的数据。于是就变成了以哪个数据实体类为父实体的问题,例如上面的例子,查询某个老师所教的学生,那么 Teacher 就是父实体, Student 就是子实体;反过来查询学生由哪些老师教,那么 Student 就成了父实体,而 Teacher 就成了子实体。于是,定义对应关系就跟定义一对多关系差不多的操作,唯一不同的就是需要在 @Relation 注解的 associateBy 属性指定第三方类(交叉关系表类)。

// 查询老师教的学生(查询 Teacher 表, 联合获取 Student 信息)
data class TeacherStudent(@Embedded val teacher: Teacher,@Relation(parentColumn = "tid", entityColumn = "sid", associateBy = Junction(TeacherStudentCrossRef::class))val students: List<Student>
)// 查询学生的老师(查询 Student 表, 联合获取 Teacher 信息)
data class StudentTeacher(@Embedded val student: Student,@Relation(parentColumn = "sid", entityColumn = "tid", associateBy = Junction(TeacherStudentCrossRef::class))val teacher: List<Teacher>
)

第四步:在 DAO 中添加查询方法

在前面提到,多对多关系的查询,限定条件之后,跟一对多的查询类似,但是需要注意的是,不同的限定条件,父类和子类会发生变化,查询的表也就不一样(查询父实体所对应的的表)。例如:查询某个老师所教的学生(限定条件为老师), Teacher 就是父实体, Student 就是子实体,此时查询 teachers 表;反过来,查询学生由哪些老师教(限定条件是学生),那么 Student 就成了父实体,而 Teacher 就成了子实体,此时查询 students 表。

// 查询学生由哪些老师教,限定条件是学生,此时 Student 为父实体,查询 students 表@Transaction@Query("SELECT * from students")fun findTeacherByStudent(): List<StudentTeacher>// 查询老师所教的学生,限定条件是老师,此时 Teacher 为父实体,查询 teachers 表@Transaction@Query("SELECT * from teachers")fun findStudentByTeacher(): List<TeacherStudent>

注意事项:
1. 多对多关系的查询中,根据限定条件查询确定父实体类,在 SQL 语句中查询父实体类所对应的表。
2. 多对多关系查询,只能针对父实体类对应的表进行条件查询。
3. 通过数据实体关系定义的多对多关系,不一定使用特定的用例,如果无法满足,可以在 SQL 查询语句中使用 JOIN 关键字进行手动关联确切的关联关系,更多详情请参考: Android Room 数据访问对象(DAO)详解

4.5 定义嵌套关系

有时候,你需要查询三个或者更多相互关联的表,在这种情况下,你需要在这些表之间定义内嵌关系。所谓的嵌套关系,就是一个关系的查询中嵌套另一个关系的查询。

在前面的例子中,学校和学生之间是一对多关系,学生和老师之间是多对多关系。假设要查询所有学校、每个学校的所有学生以及每个学生由哪些老师教的,简单来说就是要同时查询学校、学生、学生关联老师的数据。下面将详细讲解下如何定义嵌套关系。

第一步:定义实体类

定义相关的数据实体类。

@Entity(tableName = "schools")
data class School(@PrimaryKey val sid: Int, val name: String, val addr: String)@Entity(tableName = "students")
data class Student(@PrimaryKey val sid: Int, val name: String, val age: Int, val schoolId: Int, val addr: String?)@Entity(tableName = "teachers")
data class Teacher(@PrimaryKey val tid: Int, val name: String, val age: Int, val subject: String)

第二步:定义多对多关系的交叉关系表

定义多对多关系的第三方类,也就是交叉关系表对应的数据实体类。

// 这里必须使用实体的主键一致的列名
@Entity(primaryKeys = ["tid", "sid"])
data class TeacherStudentCrossRef(val tid: Int, val sid: Int)

第三步:定义对应关系

这个示例是查询教学生的老师,所以 Student 为父实体,Teacher 为子实体,定一个 StudentTeacher 两个实体之间的多对多关系 。

// 查询学生的老师(查询 Student 表, 联合获取 Teacher 信息)
data class StudentTeacher(@Embedded val student: Student,@Relation(parentColumn = "sid", entityColumn = "tid", associateBy = Junction(TeacherStudentCrossRef::class))val teacher: List<Teacher>
)

接下来是学校跟学生之间的关系,所以还需要再定义一个实体类,这实体类 School 为父实体,学生与老师对应关系实体为子实体(即上面定义的 StudentTeacher 实体为子实体)。在 @Relation 注解中,子实体与父实体主键对应的列名为 Student 类中父实体的主键对应的列(StudentStudentTeacher 关系类的父实体)。

data class SchoolStudentTeacher(@Embedded val school: School,@Relation(entity = Student::class, parentColumn = "sid", entityColumn = "schoolId")val studentTeacher: List<StudentTeacher>
)

第四步:在 DAO中 添加查询方法

查询结果为 SchoolStudentTeacher 关系实体类,所以 SQL 只需要查询 SchoolStudentTeacher 关系类中的父实体数据类对应的表即可。

@Transaction
@Query("SELECT * from schools")
fun findSchoolStudentTeacher(): List<SchoolStudentTeacher>

以上例子查询结果的数据,数据结构是一个嵌套的,可以描述为下图:

注意事项:
1. 嵌套关系只能对第一层(SQL 所查询的表) 进行条件查询,如果需要更加复杂的条件查询,可以在 SQL 查询语句中使用 JOIN 关键字进行手动关联确切的关联关系,更多详情请参考: Android Room 数据访问对象(DAO)详解;
2. 在定义对应关系时,@Relation 注解中,parentColumn 属性值为对应关系的父实体主键列,entityColumn 必须是内嵌的对应关系主实体的主键列。

五、总结

这一章节中详细介绍了 Room 数据实体类的使用,巧妙地使用注解和类结构,可以达到实现不同的表结构。还有更多的巧妙用法本文可能并没有覆盖到,如果各位读者有新发现,欢迎留言告知。

Android Room 数据实体类详解相关推荐

  1. [NewLife.XCode]实体类详解

    NewLife.XCode是一个有10多年历史的开源数据中间件,由新生命团队(2002~2019)开发完成并维护至今,以下简称XCode. 整个系列教程会大量结合示例代码和运行日志来进行深入分析,蕴含 ...

  2. Android - Vibrator及VibrationEffect类详解

    一.Vibrator类详解 在API级别1(Android1.0)中添加 1.基本概念 在设备上操作振动器的类. 如果您的进程退出,您启动的任何振动都将停止. 必须使用带有Vibrator.class ...

  3. 实体类定义规则以及主要实体类详解

    1.实体类概述 ​ 1.1什么是实体类 众所周知实体类是在java开发中被应运到的,但是什么是实体类? 1.11实体类的定义 百度百科查阅说:实体类主要是作为数据管理和业务逻辑处理层面上存在的类别: ...

  4. android 最新消息滚动,Android 滚动操作Scroller类详解

    Scroller这个类理解起来有一定的困难,刚开始接触Scroller类的程序员可能无法理解Scroller和View系统是怎么样联系起来的.我经过自己的学习和实践,对Scroller的用法和工作原理 ...

  5. Android Log和Slog类详解--Android 12

    Log类的路径在/frameworks/base/core/java/android/util/Log.java Slog类的路径在/frameworks/base/core/java/android ...

  6. android中oncreate方法,android开发之onCreate( )方法详解

    这里我们只关注一句话:This is where you should do all of your normal static set up.其中我们只关注normal static, normal ...

  7. android json mysql_Android通过json向MySQL中读写数据的方法详解【读取篇】

    本文实例讲述了Android通过json向MySQL中读取数据的方法.分享给大家供大家参考,具体如下: 首先 要定义几个解析json的方法parseJsonMulti,代码如下: private vo ...

  8. Android复习14【高级编程:推荐网址、抠图片上的某一角下来、Bitmap引起的OOM问题、三个绘图工具类详解、画线条、Canvas API详解(平移、旋转、缩放、倾斜)、矩阵详解】

    目   录 推荐网址 抠图片上的某一角下来 8.2.2 Bitmap引起的OOM问题 8.3.1 三个绘图工具类详解 画线条 8.3.16 Canvas API详解(Part 1) 1.transla ...

  9. Android基础入门教程——8.3.1 三个绘图工具类详解

    Android基础入门教程--8.3.1 三个绘图工具类详解 标签(空格分隔): Android基础入门教程 本节引言: 上两小节我们学习了Drawable以及Bitmap,都是加载好图片的,而本节我 ...

  10. 超级超级详细的实体关系抽取数据预处理代码详解

    超级超级详细的实体关系抽取数据预处理代码详解 由于本人是代码小白,在学习代码过程中会出现很多的问题,所以需要一直记录自己出现的问题以及解决办法. 废话不多说,直接上代码!!! 一.data_proce ...

最新文章

  1. Spring Boot如何实现在线预览?这个开源项目可以学习一下,支持99%常用文件!...
  2. .git文件夹_将Git存储库中的文件夹转换为全新的存储库
  3. proteus跑马灯仿真_不花费一分钱,实现跑马灯编程实验
  4. 【LeetCode】169. Majority Element 解题小结
  5. AWS亚马逊ssh登录失败 Permissions 0644 for .pem are too open
  6. Python实现读写txt文件的方法
  7. Several ports (8005, 8080, 8009) required by demo are already in use. The server may already be runn
  8. rasp 系统_RASP相关的技术实现和产品介绍
  9. 关于在多重积分以及曲线曲面积分中对称性的应用
  10. C语言程序设计入门教程
  11. 三维体素图绘制实验与教程
  12. ACM 各大OJ平台以及题目分类
  13. 安卓的平板电脑,请问有没有类似Ghost的备份还原软件?(for 安卓)
  14. Go语言基础实战视频教程-欧阳桫-专题视频课程
  15. 一文读懂PQuant与QQuant量化易金工
  16. springboot配置拦截器,在拦截器中获取@RequestBody注解参数和post请求参数以及get请求参数
  17. Ubuntu20.04安装配置tensorflow2深度学习环境
  18. Python3,5行代码,制作Gif动图,太简单了。
  19. Java回调函数异步回调案例
  20. (转...)图像处理与计算机视觉 基础、经典以及最近发展

热门文章

  1. Windows查看电脑ip地址方法(用于连接远程桌面)
  2. Python图像处理库PIL中图像格式转换(二)
  3. github官网进不去解决方案
  4. 瑞士央行2022年巨亏1320亿瑞士法郎
  5. SDPT3 4.0版——半正定二次线性规划的Matlab软件
  6. 如何设置Ubuntu键盘输入法框架为fcitx
  7. [IOI2018]组合动作 题解
  8. 中国现在小学要求计算机课吗,为什么说刚进学校不要买电脑?是用不到吗?
  9. 计算机组成原理和微机原理实验报告,《计算机组成原理》实验指导.doc
  10. 量化敏捷项目管理案例分享