从 SQLite 逐步迁移到 Room (#3280)

* 从 SQLite 逐步迁移到 Room

* 根据校对意见修改

* Update incrementally-migrate-from-sqlite-to-room.md
This commit is contained in:
IllllllIIl
2018-02-20 17:26:54 +08:00
committed by LeviDing
parent 6b7c02b733
commit 7abecad641
@@ -2,43 +2,43 @@
> * 原文作者:[Florina Muntenescu](https://medium.com/@florina.muntenescu?source=post_header_lockup)
> * 译文出自:[掘金翻译计划](https://github.com/xitu/gold-miner)
> * 本文永久链接:[https://github.com/xitu/gold-miner/blob/master/TODO/incrementally-migrate-from-sqlite-to-room.md](https://github.com/xitu/gold-miner/blob/master/TODO/incrementally-migrate-from-sqlite-to-room.md)
> * 译者:
> * 校对者:
> * 译者:[IllllllIIl](https://github.com/IllllllIIl)
> * 校对者:[tanglie1993](https://github.com/tanglie1993), [jaymz1439](https://github.com/jaymz1439)
# Incrementally migrate from SQLite to Room
# SQLite 逐步迁移到 Room
![](https://cdn-images-1.medium.com/max/2000/1*zoWpyj2lq7EpWiRuIhBwuQ.png)
_Migrate your complex database to Room with manageable PRs._
通过可管理的 PR 将复杂的数据库迁移到 Room
Youve heard about Room — perhaps you checked out the [documentation](https://developer.android.com/topic/libraries/architecture/room.html), watched a [video](https://www.youtube.com/watch?v=H7I3zs-L-1w) or [two](https://youtu.be/DeIKyVfCvC0), and decided to start integrating Room into your project. If your database has only a few tables and simple queries, you can easily migrate with a relatively small pull request by following these 7 steps to Room.
你已经听说过 Room 了吧 —— 或许你已经看过[文档](https://developer.android.com/topic/libraries/architecture/room.html),看过[一个](https://www.youtube.com/watch?v=H7I3zs-L-1w)或[两个](https://youtu.be/DeIKyVfCvC0)视频,并且决定开始整合 Room 到你的项目中。如果你的数据库只有几张表和简单查询的话,你可以很容易地跟着下面这 7 个步骤,通过较小改动的类似 pull request 操作迁移到 Room
- [**7 Steps To Room**: _A step by step guide on how to migrate your app to Room_medium.com](https://medium.com/google-developers/7-steps-to-room-27a5fe5f99b2)
- [**7 Steps To Room**: A step by step guide on how to migrate your app to Room medium.com](https://medium.com/google-developers/7-steps-to-room-27a5fe5f99b2)
However, if your database is larger or has complex queries, implementing all the entities, DAOs, tests for the DAOs, and replacing usages of `SQLiteOpenHelper` can take a long time; youll end up with a big pull request that will take time to implement and review. Lets see how you can gradually migrate from SQLite to Room, with manageable PRs.
不过,如果你的数据库较大或者有复杂的查询操作的话,实现所有 entity 类,DAO 类,DAO的测试类并且替换 `SQLiteOpenHelper` 的使用就会耗费很多时间。你最终会需要一个大改动的 pull request,去实现这些和检查。让我们看看你怎么通过可管理的 PR(pull request),逐步从 SQLite 迁移到 Room
#### TL;DR:
#### 文长不读的话,可以看下面的概括点:
> **_First PR_**_: Create your_ **_entity_** _classes, the_ **_RoomDatabase_**_, and update from your custom SQLiteOpenHelper to_ [**_SupportSQLiteOpenHelper_**](https://developer.android.com/reference/android/arch/persistence/db/SupportSQLiteOpenHelper.html)_._
> **第一个 PR**:创建你的 **entity** 类,**RoomDatabase**,并且更新你自定义的 SQLiteOpenHelper [**SupportSQLiteOpenHelper**](https://developer.android.com/reference/android/arch/persistence/db/SupportSQLiteOpenHelper.html)
> **_Following PRs_**_: Gradually create_ **_DAOs_** _to replace Cursor and ContentValue code._
> **其余的 PR**:创建 DAO 类去代替有 Cursor ContentValue 的代码。
### Project setup
### 项目设置
Lets consider the following:
我们考虑有以下这些情况:
* Our database has 10 tables, each with a corresponding model object. E.g. for the `users` table, we have a corresponding `User` object.
* A `CustomDbHelper` class extends `SQLiteOpenHelper`
* The class that works with the `CustomDbHelper` to access the database is the `LocalDataSource`.
* We have tests for `LocalDataSource`.
* 我们的数据库有 10 张表,每张有一个相应的 model 对象。例如,如果有 users 表的话,我们有相应的 User 对象。
* 一个继承自 `SQLiteOpenHelper``CustomDbHelper`
* `LocalDataSource` 类,这个是通过 `CustomDbHelper` 访问数据库的类。
* 我们有一些对 `LocalDataSource` 类的测试。
### First PR
### 第一个 PR
Your initial PR will contain the minimum amount of changes that are needed to setup the Room database.
你第一个 PR 会包含设置 Room 所需的最小幅度改动操作。
#### Create the entity classes
#### 创建 entity
If you already have data model objects for every table, just add the `[@Entity](https://developer.android.com/reference/android/arch/persistence/room/Entity.html)`, `[@PrimaryKe](https://developer.android.com/reference/android/arch/persistence/room/PrimaryKey.html)y` and `[@ColumnInfo](https://developer.android.com/reference/android/arch/persistence/room/ColumnInfo.html)` annotations.
如果你已经有每张表数据的 model 对象类,就只用添加 [`@Entity`](https://developer.android.com/reference/android/arch/persistence/room/Entity.html) [`@PrimaryKey`](https://developer.android.com/reference/android/arch/persistence/room/PrimaryKey.html) 和 [`@ColumnInfo`](https://developer.android.com/reference/android/arch/persistence/room/ColumnInfo.html) 的注解。
```
+ @Entity(tableName = "users")
@@ -62,11 +62,12 @@ If you already have data model objects for every table, just add the `[@Entity](
}
```
#### Create the Room database
#### 创建 Room 数据库
Create an abstract class extending `[RoomDatabase](https://developer.android.com/reference/android/arch/persistence/room/RoomDatabase.html)`. In the `[@Database](https://developer.android.com/reference/android/arch/persistence/room/Database.html)` annotation, list all the entity classes youve created. For now, we dont need to create DAO classes.
创建一个继承 [`RoomDatabase`](https://developer.android.com/reference/android/arch/persistence/room/RoomDatabase.html) 的抽象类。在 [`@Database`](https://developer.android.com/reference/android/arch/persistence/room/Database.html) 注解中,列出所有你已创建的 entity 类。现在,我们就不用再创建 DAO 类了。
更新你数据库版本号并生成一个 Migration 对象。如果你没改数据库的 schema,你仍需要生成一个空的 Migration 对象让 Room 保留已有的数据。
Increment your database version number and implement a migration. If you didnt change the database schema, youll still need to implement an empty migration to tell Room to keep the existing data.
```
@Database(entities = {<all entity classes>},
@@ -77,15 +78,15 @@ public abstract class AppDatabase extends RoomDatabase {
= new Migration(<sqlite_version>, <incremented_sqlite_version>) {
@Override public void migrate(
SupportSQLiteDatabase database) {
// Since we didnt alter the table, theres nothing else
// to do here.
// 因为我们并没有对表进行更改,
// 所以这里没有什么要做的
}
};
```
### Update the class that works with SQLiteOpenHelper
### 更新使用 SQLiteOpenHelper 的类
Initially, our `LocalDataSource` worked with a `CustomOpenHelper` class. Well update this to use the `**SupportSQLiteOpenHelper**` from the `[RoomDatabase.getOpenHelper()](https://developer.android.com/reference/android/arch/persistence/room/RoomDatabase.html#getOpenHelper%28%29)`.
一开始,我们的 `LocalDataSource` 类使用 `CustomOpenHelper` 进行工作,现在我要把它更新为使用 `**SupportSQLiteOpenHelper**`,这个类可以从 [`RoomDatabase.getOpenHelper()`](https://developer.android.com/reference/android/arch/persistence/room/RoomDatabase.html#getOpenHelper%28%29) 获得。
```
public class LocalUserDataSource {
@@ -95,16 +96,15 @@ public class LocalUserDataSource {
}
```
Because `SupportSQLiteOpenHelper` doesnt directly extend `SQLiteOpenHelper` but instead is a wrapper over it, we need to update calls to get the writable and readable database and use `SupportSQLiteDatabase` instead of `SQLiteDatabase`.
因为 `SupportSQLiteOpenHelper` 并不是直接继承 `SQLiteOpenHelper`,而是对它的一层包装,我们需要更改获得可写可读数据库的调用方式,并使用 `SupportSQLiteDatabase` 而不再是 `SQLiteDatabase`
```
SupportSQLiteDatabase db = mDbHelper.getWritableDatabase();
```
`[SupportSQLiteDatabase](https://developer.android.com/reference/android/arch/persistence/db/SupportSQLiteDatabase.html)` is a database abstraction that provides similar methods to the `SQLiteDatabase`. Because it provides a cleaner API for inserting and querying the database, some changes in the code will be required.
For insert, Room removes the optional `nullColumnHack` parameter. Instead of `[SQLiteDatabase.insertWithOnConflict](https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#insertWithOnConflict%28java.lang.String,%20java.lang.String,%20android.content.ContentValues,%20int%29)`, use `[SupportSQLiteDatabase.insert](https://developer.android.com/reference/android/arch/persistence/db/SupportSQLiteDatabase.html#insert%28java.lang.String,%20int,%20android.content.ContentValues%29)`.
[`SupportSQLiteDatabase`](https://developer.android.com/reference/android/arch/persistence/db/SupportSQLiteDatabase.html) 是一个数据库抽象层,提供类似 `SQLiteDatabase` 中的方法。因为它提供了一个更简洁的 API 去执行插入和查询数据库的操作,代码相比以前也需要做一些改动。
对于插入操作,Room 移除了可选的 `nullColumnHack` 参数。使用 [`SupportSQLiteDatabase.insert`](https://developer.android.com/reference/android/arch/persistence/db/SupportSQLiteDatabase.html#insert%28java.lang.String,%20int,%20android.content.ContentValues%29) 代替 [`SQLiteDatabase.insertWithOnConflict`](https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#insertWithOnConflict%28java.lang.String,%20java.lang.String,%20android.content.ContentValues,%20int%29)。
```
@Override
public void insertOrUpdateUser(User user) {
@@ -122,7 +122,7 @@ public void insertOrUpdateUser(User user) {
}
```
For queries, `SupportSQLiteDatabase` provides 4 methods:
要查询的话,`SupportSQLiteDatabase` 提供了4种方法:
```
Cursor query(String query);
@@ -131,9 +131,9 @@ Cursor query(SupportSQLiteQuery query);
Cursor query(SupportSQLiteQuery query, CancellationSignal cancellationSignal);
```
If youre using raw queries, then no changes will be required. If your queries are more complex, youll have to create a `[SupportSQLiteQuery](https://developer.android.com/reference/android/arch/persistence/db/SupportSQLiteQuery.html)` via `[SupportSQLiteQueryBuilder](https://developer.android.com/reference/android/database/sqlite/SQLiteQueryBuilder.html)`.
如果你只是简单地使用原始的查询操作,那在这里就没有什么要改的。如果你的查询是较复杂的,你就得通过 [`SupportSQLiteQueryBuilder`](https://developer.android.com/reference/android/database/sqlite/SQLiteQueryBuilder.html) 创建一个 [`SupportSQLiteQuery`](https://developer.android.com/reference/android/arch/persistence/db/SupportSQLiteQuery.html)。
For example, we have a `users` table and we want to get only the first user in the table, ordered by name. Heres how the method would look like with `SQLiteDatabase` vs `SupportSQLiteDatabase`.
举个例子,我们有一个 `users` 表,只想获得表中按名字排序的第一个用户。下面就是实现方法在`SQLiteDatabase` `SupportSQLiteDatabase` 中的区别。
```
public User getFirstUserAlphabetically() {
@@ -144,7 +144,7 @@ public User getFirstUserAlphabetically() {
COLUMN_NAME_USERNAME
};
// Get the first user from the table ordered alphabetically
// 按字母顺序从表中获取第一个用户
- Cursor cursor = db.query(TABLE_NAME, projection, null,
- null, null, null, COLUMN_NAME_USERNAME + “ ASC “, “1”);
@@ -169,13 +169,13 @@ public User getFirstUserAlphabetically() {
}
```
> If you dont have **tests** for the usages of your SQLiteOpenHelper implementation, I strongly recommend writing tests first then working on the migration to Room, to decrease the risks of regression issues.
> 如果你没有对你的 SQLiteOpenHelper 实现类进行测试的话,那我强烈推荐你先测试下再进行这个迁移的工作,避免产生相关 bug。
### Following PRs
### 其余的 PR
Now that your data layer is using Room, you can start to gradually create DAOs (with tests) and replace the `Cursor` and `ContentValue` code with DAO calls.
既然你的数据层已经在使用 Room,你可以开始逐渐创建 DAO 类(附带测试)并通过 DAO 的调用替代 `Cursor` `ContentValue` 的代码。
The query that gets the first user from the `users` table ordered by name would be defined in the `UserDao` class.
像在 `users` 表中按名字顺序查询第一个用户这个操作应该定义在 `UserDao` 接口中。
```
@Dao
@@ -185,7 +185,7 @@ public interface UserDao {
}
```
The method will then be used in the `LocalDataSource`.
这个方法会在 `LocalDataSource` 中被调用。
```
public class LocalDataSource {
@@ -198,9 +198,9 @@ public class LocalDataSource {
* * *
Moving a large database from SQLite to Room in a single PR will contain a lot of new and updated files; it can take quite some time to implement and consequently make the PR harder to review. Use the OpenHelper exposed by `RoomDatabase` to make minimal changes to your code for the initial PR and then gradually add DAOs to replace the `Cursor` and `ContentValue` code in follow up PRs.
在单一一个 PR 中,把 SQLite 迁移一个大型的数据库到 Room 会生成很多新文件和更新过后的文件。这需要一定时间去实现,因此导致 PR 更难检查。在最开始的 PR,先使用 `RoomDatabase` 提供的 OpenHelper 从而让代码最小程度地改动,然后在接下来的 PR 中才逐渐创建 DAO 类去替换 `Cursor` `ContentValue` 的代码。
For more info on Room, check out these articles:
想了解 Room 的更多相关信息,请阅读下面这些文章:
- [**7 Pro-tips for Room**: _Learn how you can get the most out of Room_medium.com](https://medium.com/google-developers/7-pro-tips-for-room-fbadea4bfbd1)
- [**Understanding migrations with Room**: _Performing database migrations with the SQLite API always made me feel like I was defusing a bomb — as if I was one…_medium.com](https://medium.com/google-developers/understanding-migrations-with-room-f01e04b07929)