因为 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中创建播放列表,每个列表可以包含多首歌曲,每首歌曲可以包含在多个播放列表中。
为了构建这种关系,你需要创建三个对象:
- 播放列表对应的实体
- 歌曲对应的实体
- 中间类,保存哪首歌曲包含在哪个播放列表中
@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>
}