Android Room:插入一个具有ViewModel、Repository、Database的多对多关系实体



我正在创建一个简单的应用程序来练习使用数据库。该应用程序有播放列表和歌曲。每个播放列表包含许多歌曲,并且每首歌曲都可以在多个播放列表中。因此,它将需要一种多对多的关系。

我正在努力坚持Android的活动->ViewModel->存储库->使用LiveData和Room的数据库体系结构。

该应用程序的MainActivity从用户那里收集两个歌曲名称和一个播放列表名称,然后在单击按钮时将它们添加到Room数据库中。

这是播放列表对象,Song对象,加上一个用作交叉引用的额外对象,CrossRef:

播放列表.class:

@Entity(tableName="playlist_table")
public class Playlist {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name="playlist_id")
private int playlistId;
private String name = "Default Playlist";
}

Song.class:

@Entity(tableName="song_table")
public class Song {
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name="song_id")
private int songId;
private String name;
}

CrossRef.class:

@Entity(tableName="cross_ref_table", primaryKeys = {"playlist_id", "song_id"})
public class CrossRef {
@ColumnInfo(index = true, name = "playlist_id")
public int playlistId;
@ColumnInfo(index = true, name = "song_id")
public int songId;
public CrossRef(int playlistId, int songId) {
this.playlistId = playlistId;
this.songId = songId;
}
}

MainActivity从用户那里获取数据,调用MyViewModel插入数据

MainActivity.class:

myViewModel.insert(playlist, songs);

然后MyViewModel使用其存储库:

  1. 将播放列表插入到playlist_table并保存其自动生成的playlistId
  2. 将每首歌曲插入保存每首歌曲Id的歌曲表中
  3. 在cross_ref_table中插入新行

MyViewModel.class:

public void insert(Playlist playlist, List<Song> newSongs) {
int playlistId = (int)localRepository.insert(playlist);
int songId = 0;
for (Song song: newSongs) {
if(!songExists(song)) {
songId = (int)localRepository.insert(song);
} else {
songId = song.getSongId();
}
CrossRef crossRef = new CrossRef(playlistId, songId);
localRepository.insert(crossRef);
}
}

然后,知识库调用Dao来完成实际工作。LocalRepository.class:

public long insert(Playlist playlist){
new InsertPlaylistAsyncTask(myDao).execute(playlist);
return resultId; // Once the async task is done, return the new playlistId.
}
public long insert(Song song){
new InsertSongAsyncTask(myDao).execute(song);
return resultId; // Once the async task is done, return the new songId.
}
public void insert(CrossRef crossRef){
new InsertCrossRefAsyncTask(myDao).execute(crossRef);
}

MyDao:

@Insert                   
long insert(Playlist playlist);   // returns a long value of the newly inserted playlistId.
@Insert
long insert(Song song);           // returns a long value of the newly inserted songId.
@Insert
void insert(CrossRef crossRef);

我遇到的问题是自动生成身份证。它们总是返回为0!在MyDao中,这一行应该为新插入的播放列表ID分配播放列表ID,对吗?int playlistId = (int)localRepository.insert(playlist);

但不,它总是零。这是存储库中的InsertPlaylistAsyncTask,新id应该在PostExecute:上传递回来

private class InsertPlaylistAsyncTask extends AsyncTask<Playlist, Void, Long> {
private MyDao myDao;
private InsertPlaylistAsyncTask(MyDao myDao){
this.myDao = myDao;
}
@Override
protected Long doInBackground(Playlist... playlists) {
long id = 0; // TODO: make this an array and return an ArrayList<Long>
for (Playlist r:playlists) {
id = myDao.insert(r);
}
return id;
}
@Override
protected void onPostExecute(Long playlistId) {
resultId = playlistId;
}
}

如果有人有一个很好的资源来了解更多关于插入到一个具有多对多关系的数据库的信息,我会洗耳恭听的!谢谢大家。

我想我找到了自己的解决方案。使用处理程序!

创建一个对象PlaylistWithSongs。调用存储库以插入PlaylistWithSongs。

现在将播放列表和歌曲插入它们的表中:存储库在第二个AsyncTask中插入播放列表,该任务调用主线程上的处理程序,该处理程序手头有播放列表ID。存储库在更多的AsyncTasks中插入列表中的每首歌曲,使用生成的每首新歌曲ID调用相同的处理程序。

处理程序在主线程上,等待id进入。每次它看到有效的playlistId和有效的songId时,它都会将匹配项插入到crossRef表中。

播放列表WithSongs.java:

public class PlaylistWithSongs {
@Embedded
public Playlist playlist;
// The songs variable will contain all songs related by the playlistId and songId.
@Relation(
parentColumn = "playlist_id",
entityColumn = "song_id",
associateBy = @Junction(CrossRef.class)
)
public List<Song> songs;
}

LocalRepository.java:

public void insertPlaylistWithSongs(PlaylistWithSongs playlistWithSongs) {
MyDao myDao;
insertPlaylist(playlistWithSongs.getPlaylist());
for (Song song : playlistWithSongs.getSongs()) {
insertSong(song);
}
tempPlaylistId = 0;
tempSongId = 0;
}

insertPlaylist在表中插入播放列表并保存新的idid = (int)myDao.insert(playlist);。它打包一条包含新idids.putInt(PLAYLISTID, id);的消息,发送到主线程handler.sendMessage(msg);:上的处理程序

public void insertPlaylist(Playlist playlist){
new InsertPlaylistAsyncTask(myDao).execute(playlist);
}
private class InsertPlaylistAsyncTask extends AsyncTask<Playlist, Void, Void> {
private MyDao myDao;
private InsertPlaylistAsyncTask(MyDao myDao){
this.myDao = myDao;
}
@Override        
protected Void doInBackground(Playlist... playlists) {
int id = 0;
for (Playlist playlist:playlists) {
Playlist retrievedFromDatabase = myDao.getPlaylistByName(playlist.getName());
if(retrievedFromDatabase == null){
// If playlist name doesn't exist
id = (int)myDao.insert(playlist);
} else {
// Else set the id to the existing playlist id
id = retrievedFromDatabase.getPlaylistId();
}
// Pass the new id to the main thread once the background insert is complete.
Bundle ids = new Bundle();
ids.putInt(PLAYLISTID, id);
Message msg = new Message();
msg.setData(ids);
msg.setTarget(handler);
handler.sendMessage(msg);
}
return null;
}
}

每首歌都以完全相同的方式处理,所以我不会重复代码。

每次处理程序接收到新的id时,它都会更新Repository的静态变量private static int tempSongId = 0;private static int tempPlaylistId = 0;,然后检查是否有足够的有效信息来插入crossRef:

Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
Bundle ids = msg.getData();
if(ids.getInt(PLAYLISTID) != 0){
tempPlaylistId = msg.getData().getInt(PLAYLISTID);
}
if(ids.getInt(SONGID) != 0){
tempSongId = msg.getData().getInt(SONGID);
}
if(tempPlaylistId!=0 && tempSongId!=0) {
// if both ids were retrieved
CrossRef crossRef = new CrossRef(tempPlaylistId, tempSongId);
Log.d(TAG, "handler insert(crossRef): " + tempPlaylistId + "," + tempSongId);
insertCrossRef(crossRef);
}else{
// if not, do nothing. We need both ids to insert a crossRef entry.
}
}
};

此代码的一个问题是:如果主活动在AsyncTasks完成之前停止,则可能发生内存泄漏。也许添加一个弱引用可以解决这个问题。

相关内容

最新更新