【译】Room定义数据关系
在之前的文章中,我对Android Architecture Components
的Room persistence
库进行了介绍:
今天,我想向您介绍如何使用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中,我们不仅可以有单个主键,还可以有一组主键!它被称为复合主键,它用于声明连接表中的每一行对于每对userId
和repoId
都应该是唯一的。 -
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?
网友评论