【译】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