译自Define relationships between objects | Android Developers
此前一直困惑于 Room 如何实现多对多关系的问题,奈何当时全网搜不到相关的内容,于时迁移到 Room 的计划便被搁置了。直到我最近从 Google Codelab 的 Android Room with a View 中的链接跳转到了官方文档,看到了官方对于多对多关系的示例代码,我又去搜索了一下这个问题,中文站内已经有几篇相关的问答了,但是掘金似乎还没有相关的文章提到(可能是我搜索的方式不对吧),于是我决定将这段文档翻译分享出来。以下是正文。
由于 SQLite 数据库是一种关系型数据库,你可以指定对象间的关系。然而尽管绝大部分的对象关系映射库允许实体对象相互引用,但是 Room 明确禁止这种做法。想要了解这个决定的技术性原因,参见理解为什么 Room 不允许对象引用。
定义一对多关系
尽管你不能使用直接关系,但是 Room 允许你定义外键关联实体类。
例如有一个实体类 User
,另一个实体类 Book
,你可以通过 @ForeignKey
声明,定义 Book
与 User
的关系。
@Entity
public class User {
@PrimaryKey
public int id;
public String firstName;
public String lastName;
}
@Entity(foreignKeys = @ForeignKey(entity = User.class,
parentColumns = "id",
childColumns = "user_id"))
public class Book {
@PrimaryKey public int bookId;
public String title;
@ColumnInfo(name = "user_id") public int userId;
}
复制代码
由于 Book
的 0 或多个实例可以通过外键 user_id
关联到 User
的多个实例上,因此上面的代码模拟了 Book
与 User
之间的一对多关系。
外键非常的强大,甚至允许你指定引用的实体更新后执行的操作。例如,通过在 @ForeignKey
的声明中加入 onDelete = CASCADE
,你可以设置 SQLite 在一个 User
的实例被删除时,删除该实例关联的所有书。
★ 注意: SQLite 通过一系列的 删除 和 替换 操作处理 @Insert(onConflict = REPLACE) 而非单一的 更新 操作。这种替换冲突值得方法可能会影响你的外键约束。多于更多细节,参见 SQLite 文档 的 ON_CONFLICT 部分。
创建嵌套对象
有时,你希望在数据库逻辑中将实体或数据对象表达为一个整体,即使该对象包含多个字段。在这些情况下,你可以使用 @Embedded
注解来表示表中要分解到子成员变量的对象。 然后,你可以像查询其他单个列一样查询嵌入的成员变量。
例如,您的User类可以包含Address类型的字段,该字段表示名为street,city,state和postCode的字段的组合。 要将组合列分别存储在表中,在User类中使用@Embedded注解修饰的Address字段,如以下代码段所示:
public class Address {
public String street;
public String state;
public String city;
@ColumnInfo(name = "post_code") public int postCode;
}
@Entity
public class User {
@PrimaryKey public int id;
public String firstName;
@Embedded public Address address;
}
复制代码
这张表表示包含具有以下名称的列: id
, firstName
, street
, state
, city
和 post_code
的 User
对象。
★ 注意: 嵌套字段也可以包含其他嵌套字段。
如果实体具有多个相同类型的嵌入字段,则可以通过设置 prefix
属性使每个列保持唯一。然后,Room将提供的值添加到嵌入对象中每个列名称的开头。
定义多对多关系
PS:终于到重点了,嘿嘿?
你经常要在关系数据库中建模另一种关系:两个实体之间的多对多关系,其中每个实体可以关联到另一个实体的0或多个实例。例如,考虑一个音乐串流应用程序,用户可以将他们喜欢的歌曲组成播放列表。每个播放列表可以具有任意数量的歌曲,并且每首歌曲可以包括在任意数量的播放列表中。
要模拟此关系,你需要创建三个对象:
- 播放列表的实体类。
- 歌曲的实体类。
- 一个中间类,用于保存每个播放列表中有哪些歌曲的信息。
你可以将实体类定义为独立部分:
@Entity
public class Playlist {
@PrimaryKey public int id;
public String name;
public String description;
}
@Entity
public class Song {
@PrimaryKey public int id;
public String songName;
public String artistName;
}
复制代码
然后,将中间类定义为包含对 Song
和 Playlist
的外键引用的实体:
@Entity(tableName = "playlist_song_join",
primaryKeys = { "playlistId", "songId" },
foreignKeys = {
@ForeignKey(entity = Playlist.class,
parentColumns = "id",
childColumns = "playlistId"),
@ForeignKey(entity = Song.class,
parentColumns = "id",
childColumns = "songId")
})
public class PlaylistSongJoin {
public int playlistId;
public int songId;
}
复制代码
这会生成一个多对多关系模型,允许你使用 DAO 按播放列表按歌曲和歌曲查询播放列表:
@Dao
public interface PlaylistSongJoinDao {
@Insert
void 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")
List<Playlist> getPlaylistsForSong(final int songId);
@Query("SELECT * FROM song " +
"INNER JOIN playlist_song_join " +
"ON song.id=playlist_song_join.songId " +
"WHERE playlist_song_join.playlistId=:playlistId")
List<Song> getSongsForPlaylist(final int playlistId);
}
复制代码