简评:Google 工程师的复杂 SQLite 数据库 Room 迁移指南。
Room Persistence Library 是 Google 官方提供的数据持久化类库,提供了 SQLite 的抽象层。官方文档中强烈推荐使用 Room 代替直接操作 SQLite。如果你决定使用 Room,但数据库较大或者有复杂查询时,迁移过程就会比较耗时和麻烦。
这篇文章就是 Google 的一位工程师将 SQLite 迁移到 Room 的步骤拆解成了 PR。
项目场景
想象我们有一个的项目是这样的:
- 我们的数据库有 10 张表,分别对应一个 model 类。比如,对于 users 表,有一个相应的 User 类。
- 一个继承自 SQLiteOpenHelper 的 CustomDbHelper。
- 会在 LocalDataSource 类中通过 CustomDbHelper 进行访问数据库的操作。
- 一些针对 LocalDataSource 的测试。
First 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 类。再增加数据库版本号并实现一个 Migration:
@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) {
// Since we didn’t alter the table, there’s nothing else
// to do here.
}
};
更新 SQLiteOpenHelper 为 SupportSQLiteOpenHelper
起初,我们是在 LocalDataSource 中使用我们自己实现的 CustomOpenHelper。现在我们要改成使用 SupportSQLiteOpenHelper 了,SupportSQLiteOpenHelper 提供了更加简洁的 API。
public class LocalUserDataSource {
private SupportSQLiteOpenHelper mDbHelper;
LocalUserDataSource(@NonNull SupportSQLiteOpenHelper helper) {
mDbHelper = helper;
}
对于插入:
@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 提供了四个方法:
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
};
// 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”);
+ 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;
}
接下来的 PRs
完成以上步骤我们的数据层实现就已经变为 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();
}
}
以上。如果想了解更多关于 Room 的知识,可以阅读👇「扩展阅读」中的文章(需科学上网)。
原文:Incrementally migrate from SQLite to Room
扩展阅读: