【译】Room定义数据关系

作者: dreamruner | 来源:发表于2018-04-22 20:35 被阅读128次

    【译】Room定义数据关系

    原文地址

    在之前的文章中,我对Android Architecture ComponentsRoom persistence 库进行了介绍:

    Room介绍

    今天,我想向您介绍如何使用Room创建与数据库的关系。开始吧!

    前言

    在SQLite数据库中,我们可以指定对象之间的关系,因此我们可以将一个或多个对象与一个或多个其他对象绑定。这就是所谓的一对多和多对多的关系。

    例如,我们可以在数据库中拥有用户。单个用户可以有许多存储库。这就是一(用户)对多(存储库)的关系。但另一方面,每个存储库都可以有它自己的用户 - 贡献者,因此对于每个用户,我们可以拥有许多存储库,并且每个存储库也可以有许多用户。

    一对多关系

    让我们使用上一篇文章中的示例,并假设我们的用户拥有多个存储库。在这种情况下,我们将拥有用户和仓库的实体。首先,我们需要在User和Repo之间建立连接,然后我们将能够从数据库中获得正确的数据。

    在第一步中,我们需要为用户创建单个实体:

    import android.arch.persistence.room.Entity;
    import android.arch.persistence.room.PrimaryKey;
    
    @Entity
    public class User {
        @PrimaryKey public final int id;
        public final String login;
        public final String avatarUrl;
    
        public User(int id, String login, String avatarUrl) {
            this.id = id;
            this.login = login;
            this.avatarUrl = avatarUrl;
        }
    }
    

    如果您不确定@Entity@PrimaryKey注释的用途,您可以查看我以前的帖子

    对于存储库模型,我们将重新使用上一篇文章中的Repo类进行单一但重要的更改:

    import android.arch.persistence.room.Entity;
    import android.arch.persistence.room.ForeignKey;
    import android.arch.persistence.room.PrimaryKey;
    
    import static android.arch.persistence.room.ForeignKey.CASCADE;
    
    @Entity(foreignKeys = @ForeignKey(entity = User.class,
                                      parentColumns = "id",
                                      childColumns = "userId",
                                      onDelete = CASCADE))
    public class Repo {
        @PrimaryKey public final int id;
        public final String name;
        public final String url;
        public final int userId;
    
        public Repo(final int id, String name, String url,
        final int userId) {
            this.id = id;
            this.name = name;
            this.url = url;
            this.userId = userId;
        }
    }
    

    如果您将此Repo模型与前一篇文章的模型进行比较,您会注意到两点不同:

    • @Entity注释中使用foreignKeys参数
    • userId的附加字段

    添加foreignKeys意味着我们在这个实体和其他类之间创建连接。在这个参数中,我们声明了parentColumns,它是来自User类的id列的名称,childColumns它是Repo类中用户ID列的名称。

    创建此连接对于建立关系没有必要,但可以帮助您定义在用户行删除或更新的情况下,数据库中的Repo行应该发生什么情况。这就是最后一个参数:onDelete = CASCADE 。我们特别说明,如果用户行将被删除,我们也将删除该用户的所有的存储库。您还可以使用onUpdate = CASCADE参数定义类似的解决方案。您可以在ForeignKey 文档中阅读其他可能的解决方案。

    现在,在准备好模型之后,我们需要创建适当的SQL查询来为特定用户选择存储库。

    SQL查询

    DAO是创建我们的SQL语句的最佳场所。我们的RepoDao可能如下所示:

    import android.arch.persistence.room.Dao;
    import android.arch.persistence.room.Delete;
    import android.arch.persistence.room.Insert;
    import android.arch.persistence.room.Query;
    import android.arch.persistence.room.Update;
    import java.util.List;
    
    @Dao
    public interface RepoDao {
    
        @Insert
        void insert(Repo repo);
    
        @Update
        void update(Repo... repos);
    
        @Delete
        void delete(Repo... repos);
        @Query("SELECT * FROM repo")
        List<Repo> getAllRepos();    
    
        @Query("SELECT * FROM repo WHERE userId=:userId")
        List<Repo> findRepositoriesForUser(final int userId);
    }
    

    从顶部开始,我们有三种插入,更新和删除Repo的方法(您可以在我之前的文章中阅读更多关于它们的信息)和两种获取数据的方法 - 第一种方法是获取所有存储库,第二个是我们真正想要的 - 获取特定用户的存储库

    我们还需要使用补充的UserDao 插入,更新和删除方法:

    import android.arch.persistence.room.Dao;
    import android.arch.persistence.room.Delete;
    import android.arch.persistence.room.Insert;
    import android.arch.persistence.room.Update;
    
    @Dao
    public interface UserDao {
    
        @Insert
        void insert(User... user);
    
        @Update
        void update(User... user);
    
        @Delete
        void delete(User... user);
    }
    

    我们的数据库类RepoDatabase 还需要使用@Database批注中的适当模型类和获取UserDao 的附加抽象方法进行更新:

    @Database(entities = { Repo.class, User.class },
              version = 1)
    public abstract class RepoDatabase extends RoomDatabase {
        ...    
        public abstract RepoDao getRepoDao();
        public abstract UserDao getUserDao();
    }
    

    就是这样!现在我们可以使用数据库来插入用户和存储库:

    RepoDao repoDao = RepoDatabase
            .getInstance(context)
            .getRepoDao();
    
    UserDao userDao = RepoDatabase
            .getInstance(context)
            .getUserDao();
    
    userDao.insert(new User(1,
            "Jake Wharton",
            "https://avatars0.githubusercontent.com/u/66577"));
    
    repoDao.insert(new Repo(1, 
            "square/retrofit", 
            "https://github.com/square/retrofit", 
            1));
    
    List<Repo> repositoriesForUser = repoDao.
            findRepositoriesForUser(1);
    

    多对多关系

    在SQL中,多对多(或M:N)关系需要将具有外键的连接表返回给其他实体。我们可以更改上一节中的示例,所以现在不仅每个用户都可以拥有多个存储库,而且每个存储库都可以属于多个用户!

    要做到这一点,我们将回到我们模型的最简单版本:

    import android.arch.persistence.room.Entity;
    import android.arch.persistence.room.PrimaryKey;
    
    @Entity
    public class User {
        @PrimaryKey
        public final int id;
        public final String login;
        public final String avatarUrl;
    
        public User(int id, String login, String avatarUrl) {
            this.id = id;
            this.login = login;
            this.avatarUrl = avatarUrl;
        }
    }
    

    对于仓库模型同样如此:

    import android.arch.persistence.room.Entity;
    import android.arch.persistence.room.PrimaryKey;
    
    @Entity
    public class Repo {
        @PrimaryKey 
        public final int id;
        public final String name;
        public final String url;
    
        public Repo(int id, String name, String url) {
            this.id = id;
            this.name = name;
            this.url = url;
        }
    }
    

    在下一步中,我们将创建我们的连接表 - UserRepoJoin

    import android.arch.persistence.room.Entity;
    import android.arch.persistence.room.ForeignKey;
    
    @Entity(tableName = "user_repo_join",
            primaryKeys = { "userId", "repoId" },
            foreignKeys = {
                    @ForeignKey(entity = User.class,
                                parentColumns = "id",
                                childColumns = "userId"),
                    @ForeignKey(entity = Repo.class,
                                parentColumns = "id",
                                childColumns = "repoId")
                    })
    public class UserRepoJoin {
        public final int userId;
        public final int repoId;
    
        public UserRepoJoin(final int userId, final int repoId) {
            this.userId = userId;
            this.repoId = repoId;
        }
    }
    

    乍一看它可能看起来很糟糕,但给它第二次机会🙏

    我们在这里做了啥?

    • tableName参数给我们的表一些特定的名字
    • primaryKeys参数有多个主键 - 在SQL中,我们不仅可以有单个主键,还可以有一组主键!它被称为复合主键,它用于声明连接表中的每一行对于每对userIdrepoId都应该是唯一的。
    • foreignKey参数用于向其他表声明一个外键数组。在这里,我们说从我们的连接表中获取userId是User类的子ID,对于Repo模型也是如此

    现在,当我们声明外键时,我们准备为内连接准备SQL语句:

    import android.arch.persistence.room.Dao;
    import android.arch.persistence.room.Insert;
    import android.arch.persistence.room.Query;
    import java.util.List;
    
    @Dao
    public interface UserRepoJoinDao {
        @Insert
        void insert(UserRepoJoin userRepoJoin);
    
        @Query("SELECT * FROM user INNER JOIN user_repo_join ON
               user.id=user_repo_join.userId WHERE
               user_repo_join.repoId=:repoId")
        List<User> getUsersForRepository(final int repoId);
    
        @Query("SELECT * FROM repo INNER JOIN user_repo_join ON
               repo.id=user_repo_join.repoId WHERE
               user_repo_join.userId=:userId")
        List<Repo> getRepositoriesForUsers(final int userId);
    }
    

    通过这种方式,我们可以将两个用户的资源库和用户的资源库都存储起来。最后一步是更改我们的RepoDatabase:

    @Database(entities = { Repo.class, User.class, UserRepoJoin.class },
              version = 1)
    public abstract class RepoDatabase extends RoomDatabase {
        ...
        public abstract RepoDao getRepoDao();
        public abstract UserDao getUserDao();
        public abstract UserRepoJoinDao getUserRepoJoinDao();
    }
    

    现在我们可以将用户和存储库插入到数据库中:

    RepoDao repoDao = RepoDatabase
        .getInstance(context)
        .getRepoDao();
    
    UserDao userDao = RepoDatabase
        .getInstance(context)
        .getUserDao();
    
    UserRepoJoinDao userRepoJoinDao = RepoDatabase
        .getInstance(context)
        .getUserRepoJoinDao();
    
    userDao.insert(new User(1,
            "Jake Wharton",
            "https://avatars0.githubusercontent.com/u/66577"));
    
    repoDao.insert(new Repo(1, 
            "square/retrofit", 
            "https://github.com/square/retrofit"));
    userRepoJoinDao.insert(new UserRepoJoin(1, 1));
    

    使用@Relation注解

    还有另一种使用Room提供关系的方法 - 带有@Relation 注释。你只能在非实体类中声明这样的关系。我们来看一下这个例子:

    @Entity
    public class User {
        @PrimaryKey public final int id;
        public final String login;
        public final String avatarUrl;
    
        public User(int id, String login, String avatarUrl) {
            this.id = id;
            this.login = login;
            this.avatarUrl = avatarUrl;
        }
    }
    @Entity
    public class Repo {
        @PrimaryKey public final int id;
        public final String name;
        public final String url;
        public final int userId;
    
        public Repo(int id, String name, String url, int userId) {
            this.id = id;
            this.name = name;
            this.url = url;
            this.userId = userId;
        }
    }
    

    上面我们简单介绍了我们的模型类 - User和Repo与userId字段。现在我们需要创建我们的非实体模型类:

    public class UserWithRepos {
        @Embedded public User user;
    
        @Relation(parentColumn = "id",
                  entityColumn = "userId") public List<Repo> repoList;
    }
    

    这里我们有两个新的注释:

    • @Embedded用于嵌套字段 - 这样我们就可以将User类嵌入到我们的UserWithRepos类中
    • @Relation是与其他模型类的关系。这两个参数表示来自User类的parentColumn名称是id,而来自Repo类的entityColumn名称是userId。

    通过这种方式,我们可以在DAO中使用适当的SQL语句来选择具有其所有存储库的用户:

    @Dao
    public interface UserWithReposDao {
    
        @Query("SELECT * from user")
        public List<UserWithRepos> getUsersWithRepos();
    
    }
    

    这是最简单的方法,但是,您不能像删除或更新父母时那样对@ForeignKey注解设置操作。

    总结

    就这样!我希望在这篇文章之后,你获得了一些关于在Room中创建对象关系的宝贵知识如果您有任何建议,请随时发表评论。

    如果你真的喜欢这个帖子,并想让我感到高兴,不要忘记👏!

    在这个系列中还有:

    Android Architecture Components: Room — Introduction

    Android Architecture Components: Room — Custom Types

    Android Architecture Components: Room — Migration

    Android Architecture Components: ViewModel

    Android Architecture Components: LiveData

    Android Architecture Components: How to use LiveData with Data Binding?

    相关文章

      网友评论

        本文标题:【译】Room定义数据关系

        本文链接:https://www.haomeiwen.com/subject/xczclftx.html