Room官方文档(翻译)2.定义对象间的关系

1,585 阅读4分钟

因为 SQLite 属于关系型数据库,你可以指定对象之间的关系。虽然大多数对象关系映射库允许实体对象之间相互引用,但 Room 却明确地禁止这样使用。 了解这样做背后的技术原因请参看 Understand why Room doesn't allow object references.

定义一对多关联

虽然在 Room 中你不能直接使用关联,但却允许你定义实体间的外键约束。 举个栗子,如果另一个叫Book的实体,你可使用@ForeignKey注解来定义它和User实体的关系

@Entity(foreignKeys = arrayOf(ForeignKey(
            entity = User::class,
            parentColumns = arrayOf("id"),
            childColumns = arrayOf("user_id"))
       )
)
data class Book(
    @PrimaryKey val bookId: Int,
    val title: String?,
    @ColumnInfo(name = "user_id") val userId: Int
)

这里对 User 和 Book 进行一对多建模,通过user_id外键在两个对象之间建立关联。

外键非常强大,当你关心的实体发生变化的时候它可以帮你做些什么。比如,当你在@ForeignKey注解中设置了 onDelete = CASCADE,那么当某个 User 被删除时它会通知 SQLite删除该 User 所属的 Book.

Note:SQLite把处理@Insert(onConflict = REPLACE) 分解为 REMOVE and REPLACE 两个操作,而不是单一的 UPDATE操作。这种替换冲突值的方法可能会影响您的外键约束。 更多详细信息,请参见SQLite documentation

创建嵌套对象

有时,实体内部还包含嵌套实体,如果你想在数据库中将其当成一个内聚整体的话,可以使用@Embedded注解将实体分解为数据表中的字段。然后你就像查询其他字段一样查询嵌入字段。

例如,User类可以包含 Address 类型的成员变量,Address 可以作为 Street,city,state和 postCode的字段组合。要想将这些组合字段分别存储在表中,那么就在 User 类的Address成员变量上使用@Embedded注解。

data class Address(
   val street: String?,
   val state: String?,
   val city: String?,
   @ColumnInfo(name = "post_code") val postCode: Int
)

@Entity
data class User(
   @PrimaryKey val id: Int,
   val firstName: String?,
   @Embedded val address: Address?
)

那么 User 表中就包含以下字段:id, firstName, street, state, city, and post_code.

Note:被@Embedded注解的成员变量内部也可以包含被@Embedded注解,即@Embedded是可嵌套的。

如果某个实体中的多个嵌套实体中含有同名的成员变量,那么可以使用@Embedded注解的 prefix属性来保证字段的唯一性。Room会为嵌套实体的每一个字段增加前缀。

定义多对多关联

这是在关系型数据库中需要经常构建的另一种关系类型,两个实体间的多对多关系, 任意实体的实例都可以对应到另一个实体的零到多个实例.举个栗子,用户需要在音乐流媒体 app中创建播放列表,每个列表可以包含多首歌曲,每首歌曲可以包含在多个播放列表中。

为了构建这种关系,你需要创建三个对象:

  1. 播放列表对应的实体
  2. 歌曲对应的实体
  3. 中间类,保存哪首歌曲包含在哪个播放列表中
@Entity
data class Playlist(
   @PrimaryKey var id: Int,
   val name: String?,
   val description: String?
)

@Entity
data class Song(
   @PrimaryKey var id: Int,
   val songName: String?,
   val artistName: String?
)

然后,将中间类定义为一个实体包含对 Song 和 Playlist 的外键引用。

@Entity(tableName = "playlist_song_join",
        primaryKeys = arrayOf("playlistId","songId"),
        foreignKeys = arrayOf(
                         ForeignKey(entity = Playlist::class,
                                    parentColumns = arrayOf("id"),
                                    childColumns = arrayOf("playlistId")),
                         ForeignKey(entity = Song::class,
                                    parentColumns = arrayOf("id"),
                                    childColumns = arrayOf("songId"))
                              )
        )
data class PlaylistSongJoin(
    val playlistId: Int,
    val songId: Int
)

这就产生了一个多对多关系模型,该模型允许你使用 DAO 来进行查询,既可以通过 song 查询 playlist;也可以使用 playlist 查询 song

@Dao
interface PlaylistSongJoinDao {
    @Insert
    fun insert(playlistSongJoin: PlaylistSongJoin)

    @Query("""
           SELECT * FROM playlist
           INNER JOIN playlist_song_join
           ON playlist.id=playlist_song_join.playlistId
           WHERE playlist_song_join.songId=:songId
           """)
    fun getPlaylistsForSong(songId: Int): Array<Playlist>

    @Query("""
           SELECT * FROM song
           INNER JOIN playlist_song_join
           ON song.id=playlist_song_join.songId
           WHERE playlist_song_join.playlistId=:playlistId
           """)
    fun getSongsForPlaylist(playlistId: Int): Array<Song>
}

Room官方文档(翻译)0.概览

Room官方文档(翻译)1.使用Room实体定义数据

Room官方文档(翻译)2.定义对象间的关系

Room官方文档(翻译)3.在数据库中创建视图

Room官方文档(翻译)4.使用Room DAOs访问数据

Room官方文档(翻译)5.迁移数据库

Room官方文档(翻译)6.测试数据库

Room官方文档(翻译)7.使用Room引用复杂数据