Android Room 数据实体类详解

一、前言

    在使用 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
    @PrimaryKey
    public 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 注解

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

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

3.2 @PrimaryKey 注解

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

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

3.3 @ColumnInfo 注解

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

字段名称数据类型默认值说明
nameString-指定列名称,没有指定时使用数据类的属性名作为列名
indexBooleanfalse指定该列为索引
collateIntUNSPECIFIED(1)用来指定列的数据校对类型
defaultValueStringVALUE_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 Studio 中创建一个数据实体类需要进行以下步骤: 1. 打开 Android Studio,创建一个新的 Java 类文件。 2. 在类声明的上面添加 `@Entity` 注解。 3. 使用 `@PrimaryKey` 注解来标识实体类的主键。 4. 使用 `@ColumnInfo` 注解来标识实体类的列名和数据类型。 5. 创建一个无参数的构造函数。 6. 创建一个带参数的构造函数,用于设置实体类的属性。 7. 创建 getter 和 setter 方法,用于获取和设置实体类的属性值。 以下是一个示例代码: ``` @Entity(tableName = "user") public class User { @PrimaryKey(autoGenerate = true) private int id; @ColumnInfo(name = "name") private String name; @ColumnInfo(name = "age") private int age; public User() { } public User(String name, int age) { this.name = name; this.age = age; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } } ``` 注意,在使用实体类之前,你需要在项目中添加 Room Persistence Library 依赖。可以在项目的 build.gradle 中添加以下代码: ``` dependencies { implementation "androidx.room:room-runtime:2.3.0" annotationProcessor "androidx.room:room-compiler:2.3.0" } ``` 然后,在你的数据库类中使用 `@Database` 和 `@Dao` 注解来创建数据库和访问数据的 DAO。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值