- 原文地址:Incrementally migrate from SQLite to Room
- 原文作者:Florina Muntenescu
- 譯文出自:掘金翻譯計劃
- 本文永久連結:github.com/xitu/gold-m…
- 譯者:IllllllIIl
- 校對者:tanglie1993, jaymz1439
通過可管理的 PR 將複雜的資料庫遷移到 Room
你已經聽說過 Room 了吧—— 或許你已經看過文件,看過一個或兩個視訊,並且決定開始整合 Room 到你的專案中。如果你的資料庫只有幾張表和簡單查詢的話,你可以很容易地跟著下面這 7 個步驟,通過較小改動的類似 pull request 操作遷移到 Room。
不過,如果你的資料庫較大或者有複雜的查詢操作的話,實現所有 entity 類,DAO 類,DAO的測試類並且替換 SQLiteOpenHelper
的使用就會耗費很多時間。你最終會需要一個大改動的 pull request,去實現這些和檢查。讓我們看看你怎麼通過可管理的 PR(pull request),逐步從 SQLite 遷移到 Room。
文長不讀的話,可以看下面的概括點:
第一個 PR:建立你的 entity 類,RoomDatabase,並且更新你自定義的 SQLiteOpenHelper 為 SupportSQLiteOpenHelper。
其餘的 PR:建立 DAO 類去代替有 Cursor 和 ContentValue 的程式碼。
專案設定
我們考慮有以下這些情況:
- 我們的資料庫有 10 張表,每張有一個相應的 model 物件。例如,如果有 users 表的話,我們有相應的 User 物件。
- 一個繼承自
SQLiteOpenHelper
的CustomDbHelper
。 LocalDataSource
類,這個是通過CustomDbHelper
訪問資料庫的類。- 我們有一些對
LocalDataSource
類的測試。
第一個 PR
你第一個 PR 會包含設定 Room 所需的最小幅度改動操作。
建立 entity 類
如果你已經有每張表資料的 model 物件類,就只用新增 @Entity
, @PrimaryKey
和 @ColumnInfo
的註解。
+ @Entity(tableName = "users")
public class User {
+ @PrimaryKey
+ @ColumnInfo(name = "userid")
private int mId;
+ @ColumnInfo(name = "username")
private String mUserName;
public User(int id, String userName) {
this.mId = id;
this.mUserName = userName;
}
public int getId() { return mId; }
public String getUserName() { return mUserName; }
}
複製程式碼
建立 Room 資料庫
建立一個繼承 RoomDatabase
的抽象類。在 @Database
註解中,列出所有你已建立的 entity 類。現在,我們就不用再建立 DAO 類了。
更新你資料庫版本號並生成一個 Migration 物件。如果你沒改資料庫的 schema,你仍需要生成一個空的 Migration 物件讓 Room 保留已有的資料。
@Database(entities = {<all entity classes>},
version = <incremented_sqlite_version>)
public abstract class AppDatabase extends RoomDatabase {
private static UsersDatabase INSTANCE;
static final Migration MIGRATION_<sqlite_version>_<incremented_sqlite_version>
= new Migration(<sqlite_version>, <incremented_sqlite_version>) {
@Override public void migrate(
SupportSQLiteDatabase database) {
// 因為我們並沒有對錶進行更改,
// 所以這裡沒有什麼要做的
}
};
複製程式碼
更新使用 SQLiteOpenHelper 的類
一開始,我們的 LocalDataSource
類使用 CustomOpenHelper
進行工作,現在我要把它更新為使用 **SupportSQLiteOpenHelper**
,這個類可以從 RoomDatabase.getOpenHelper()
獲得。
public class LocalUserDataSource {
private SupportSQLiteOpenHelper mDbHelper;
LocalUserDataSource(@NonNull SupportSQLiteOpenHelper helper) {
mDbHelper = helper;
}
複製程式碼
因為 SupportSQLiteOpenHelper
並不是直接繼承 SQLiteOpenHelper
,而是對它的一層包裝,我們需要更改獲得可寫可讀資料庫的呼叫方式,並使用 SupportSQLiteDatabase
而不再是 SQLiteDatabase
。
SupportSQLiteDatabase db = mDbHelper.getWritableDatabase();
複製程式碼
SupportSQLiteDatabase
是一個資料庫抽象層,提供類似 SQLiteDatabase
中的方法。因為它提供了一個更簡潔的 API 去執行插入和查詢資料庫的操作,程式碼相比以前也需要做一些改動。
對於插入操作,Room 移除了可選的 nullColumnHack
引數。使用 SupportSQLiteDatabase.insert
代替 SQLiteDatabase.insertWithOnConflict
。
@Override
public void insertOrUpdateUser(User user) {
SupportSQLiteDatabase db = mDbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_NAME_ENTRY_ID, user.getId());
values.put(COLUMN_NAME_USERNAME, user.getUserName());
- db.insertWithOnConflict(TABLE_NAME, null, values,
- SQLiteDatabase.CONFLICT_REPLACE);
+ db.insert(TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE,
+ values);
db.close();
}
複製程式碼
要查詢的話,SupportSQLiteDatabase
提供了4種方法:
Cursor query(String query);
Cursor query(String query, Object[] bindArgs);
Cursor query(SupportSQLiteQuery query);
Cursor query(SupportSQLiteQuery query, CancellationSignal cancellationSignal);
複製程式碼
如果你只是簡單地使用原始的查詢操作,那在這裡就沒有什麼要改的。如果你的查詢是較複雜的,你就得通過 SupportSQLiteQueryBuilder
建立一個 SupportSQLiteQuery
。
舉個例子,我們有一個 users
表,只想獲得表中按名字排序的第一個使用者。下面就是實現方法在SQLiteDatabase
和 SupportSQLiteDatabase
中的區別。
public User getFirstUserAlphabetically() {
User user = null;
SupportSQLiteDatabase db = mDbHelper.getReadableDatabase();
String[] projection = {
COLUMN_NAME_ENTRY_ID,
COLUMN_NAME_USERNAME
};
// 按字母順序從表中獲取第一個使用者
- Cursor cursor = db.query(TABLE_NAME, projection, null,
- null, null, null, COLUMN_NAME_USERNAME + “ ASC “, “1”);
+ SupportSQLiteQuery query =
+ SupportSQLiteQueryBuilder.builder(TABLE_NAME)
+ .columns(projection)
+ .orderBy(COLUMN_NAME_USERNAME)
+ .limit(“1”)
+ .create();
+ Cursor cursor = db.query(query);
if (c !=null && c.getCount() > 0){
// read data from cursor
...
}
if (c !=null){
cursor.close();
}
db.close();
return user;
}
複製程式碼
如果你沒有對你的 SQLiteOpenHelper 實現類進行測試的話,那我強烈推薦你先測試下再進行這個遷移的工作,避免產生相關 bug。
其餘的 PR
既然你的資料層已經在使用 Room,你可以開始逐漸建立 DAO 類(附帶測試)並通過 DAO 的呼叫替代 Cursor
和 ContentValue
的程式碼。
像在 users
表中按名字順序查詢第一個使用者這個操作應該定義在 UserDao
介面中。
@Dao
public interface UserDao {
@Query(“SELECT * FROM Users ORDERED BY name ASC LIMIT 1”)
User getFirstUserAlphabetically();
}
複製程式碼
這個方法會在 LocalDataSource
中被呼叫。
public class LocalDataSource {
private UserDao mUserDao;
public User getFirstUserAlphabetically() {
return mUserDao.getFirstUserAlphabetically();
}
}
複製程式碼
在單一一個 PR 中,把 SQLite 遷移一個大型的資料庫到 Room 會生成很多新檔案和更新過後的檔案。這需要一定時間去實現,因此導致 PR 更難檢查。在最開始的 PR,先使用 RoomDatabase
提供的 OpenHelper 從而讓程式碼最小程度地改動,然後在接下來的 PR 中才逐漸建立 DAO 類去替換 Cursor
和 ContentValue
的程式碼。
想了解 Room 的更多相關資訊,請閱讀下面這些文章:
- 7 Pro-tips for Room: _Learn how you can get the most out of Room_medium.com
- 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
- Testing Room migrations: _In a previous post I explained how database migrations with Room work under the hood. We saw that an incorrect…_medium.com
- Room ? RxJava: _Doing queries in Room with RxJava_medium.com
掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 Android、iOS、前端、後端、區塊鏈、產品、設計、人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃、官方微博、知乎專欄。