【问题标题】:Android Room: Insert a many-to-many relation entity with ViewModel, Repository, DatabaseAndroid Room:使用 ViewModel、Repository、Database 插入多对多关系实体
【发布时间】:2021-05-07 18:45:35
【问题描述】:

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

我正在尝试使用 LiveData 和 Room 坚持 Android 的 Activity->ViewModel->Repository->Database 架构。

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

这是 Playlist 对象、Song 对象,以及一个用作交叉引用的额外对象 CrossRef:

播放列表类:

@Entity(tableName="playlist_table")
public class Playlist {

    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name="playlist_id")
    private int playlistId;
    private String name = "Default Playlist";
}

歌曲类:

@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 使用它的 Repository 来:

  1. 将播放列表插入到 playlist_table 并保存它的自动生成的 playlistId。
  2. 将每首歌曲插入到保存每个 songId 的 song_table 中。
  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);
        }
    }

Repository 然后调用 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);
}

我的道:

    @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);

我遇到的问题是获取自动生成的 ID。他们总是以 0 的形式返回!在 MyDao 中,这一行应该将 playlistId 分配给新插入的播放列表 ID,对吧? int playlistId = (int)localRepository.insert(playlist);

但不,它始终为零。这是 Repository 中的 InsertPlaylistAsyncTask,新的 id 应该被传回 onPostExecute:

    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;
        }
    }

如果有人有很好的资源来了解更多关于插入到具有多对多关系的数据库的信息,我会全力以赴!谢谢大家。

【问题讨论】:

    标签: java many-to-many android-room android-architecture-components


    【解决方案1】:

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

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

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

    处理程序在主线程上,等待 ids 滚入。每次它看到一个有效的 playlistId 和一个有效的 songId 时,它就会将匹配插入到 crossRef 表中。

    PlaylistWithSongs.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 在表中插入播放列表并保存新的 id id = (int)myDao.insert(playlist);。它将包含新 id ids.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.
                }
            }
    };
    

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

    【讨论】:

      猜你喜欢
      • 2020-05-27
      • 2017-11-23
      • 2019-01-22
      • 2021-10-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多