[譯] 從 SQLite 逐步遷移到 Room

Android_開發者發表於2018-03-26

通過可管理的 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 物件。
  • 一個繼承自 SQLiteOpenHelperCustomDbHelper
  • 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 表,只想獲得表中按名字排序的第一個使用者。下面就是實現方法在SQLiteDatabaseSupportSQLiteDatabase 中的區別。

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 的呼叫替代 CursorContentValue 的程式碼。

像在 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 類去替換 CursorContentValue 的程式碼。

想了解 Room 的更多相關資訊,請閱讀下面這些文章:


掘金翻譯計劃 是一個翻譯優質網際網路技術文章的社群,文章來源為 掘金 上的英文分享文章。內容覆蓋 AndroidiOS前端後端區塊鏈產品設計人工智慧等領域,想要檢視更多優質譯文請持續關注 掘金翻譯計劃官方微博知乎專欄

相關文章