[译]从 SQLite 逐步迁移到 Room

从 SQLite 逐步迁移到 Room

经过可管理的 PR 将复杂的数据库迁移到 Roomhtml

你已经据说过 Room 了吧 —— 或许你已经看过文档,看过一个两个视频,而且决定开始整合 Room 到你的项目中。若是你的数据库只有几张表和简单查询的话,你能够很容易地跟着下面这 7 个步骤,经过较小改动的相似 pull request 操做迁移到 Room。前端

不过,若是你的数据库较大或者有复杂的查询操做的话,实现全部 entity 类,DAO 类,DAO的测试类而且替换 SQLiteOpenHelper 的使用就会耗费不少时间。你最终会须要一个大改动的 pull request,去实现这些和检查。让咱们看看你怎么经过可管理的 PR(pull request),逐步从 SQLite 迁移到 Room。java

文长不读的话,能够看下面的归纳点:

第一个 PR:建立你的 entity 类,RoomDatabase,而且更新你自定义的 SQLiteOpenHelper 为 SupportSQLiteOpenHelperandroid

其他的 PR:建立 DAO 类去代替有 Cursor 和 ContentValue 的代码。ios

项目设置

咱们考虑有如下这些状况:git

  • 咱们的数据库有 10 张表,每张有一个相应的 model 对象。例如,若是有 users 表的话,咱们有相应的 User 对象。
  • 一个继承自 SQLiteOpenHelperCustomDbHelper
  • LocalDataSource 类,这个是经过 CustomDbHelper 访问数据库的类。
  • 咱们有一些对 LocalDataSource 类的测试。

第一个 PR

你第一个 PR 会包含设置 Room 所需的最小幅度改动操做。github

建立 entity 类

若是你已经有每张表数据的 model 对象类,就只用添加 @Entity@PrimaryKey@ColumnInfo 的注解。sql

+ @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前端后端区块链产品设计人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划官方微博知乎专栏

相关文章
相关标签/搜索