【譯】使用 Room 定義物件間的關係

NARUTONBM發表於2019-06-08

譯自Define relationships between objects | Android Developers

此前一直困惑於 Room 如何實現多對多關係的問題,奈何當時全網搜不到相關的內容,於時遷移到 Room 的計劃便被擱置了。直到我最近從 Google Codelab 的 Android Room with a View 中的連結跳轉到了官方文件,看到了官方對於多對多關係的示例程式碼,我又去搜尋了一下這個問題,中文站內已經有幾篇相關的問答了,但是掘金似乎還沒有相關的文章提到(可能是我搜尋的方式不對吧),於是我決定將這段文件翻譯分享出來。以下是正文。


由於 SQLite 資料庫是一種關係型資料庫,你可以指定物件間的關係。然而儘管絕大部分的物件關係對映庫允許實體物件相互引用,但是 Room 明確禁止這種做法。想要了解這個決定的技術性原因,參見理解為什麼 Room 不允許物件引用

定義一對多關係

儘管你不能使用直接關係,但是 Room 允許你定義外來鍵關聯實體類。

例如有一個實體類 User ,另一個實體類 Book ,你可以通過 @ForeignKey 宣告,定義 BookUser 的關係。

@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 的多個例項上,因此上面的程式碼模擬了 BookUser 之間的一對多關係。

外來鍵非常的強大,甚至允許你指定引用的實體更新後執行的操作。例如,通過在 @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;
}
複製程式碼

這張表表示包含具有以下名稱的列: idfirstNamestreetstatecitypost_codeUser 物件。

注意: 巢狀欄位也可以包含其他巢狀欄位。

如果實體具有多個相同型別的嵌入欄位,則可以通過設定 prefix 屬性使每個列保持唯一。然後,Room將提供的值新增到嵌入物件中每個列名稱的開頭。

定義多對多關係

PS:終於到重點了,嘿嘿?

你經常要在關聯式資料庫中建模另一種關係:兩個實體之間的多對多關係,其中每個實體可以關聯到另一個實體的0或多個例項。例如,考慮一個音樂串流應用程式,使用者可以將他們喜歡的歌曲組成播放列表。每個播放列表可以具有任意數量的歌曲,並且每首歌曲可以包括在任意數量的播放列表中。

要模擬此關係,你需要建立三個物件:

  1. 播放列表的實體類。
  2. 歌曲的實體類。
  3. 一箇中間類,用於儲存每個播放列表中有哪些歌曲的資訊。

你可以將實體類定義為獨立部分:

@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;
}
複製程式碼

然後,將中間類定義為包含對 SongPlaylist 的外來鍵引用的實體:

@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);
}
複製程式碼

相關文章