1、在接入 Room 的基础上,gradle 里加上 WCDB 的 room 组件
dependencies {
implementation 'com.tencent.wcdb:room:1.0.8' // 代替 room-runtime,同时也不需要再引用 wcdb-android
annotationProcessor 'android.arch.persistence.room:compiler:1.1.1' // compiler 需要用 room 的
}
2、代码里面,打开 RoomDatabase 时,指定 WCDBOpenHelperFactory 作为 openFactory
QLiteCipherSpec cipherSpec = new SQLiteCipherSpec() // 指定加密方式,使用默认加密可以省略
.setPageSize(4096)
.setKDFIteration(64000);
WCDBOpenHelperFactory factory = new WCDBOpenHelperFactory()
.passphrase("passphrase".getBytes()) // 指定加密DB密钥,非加密DB去掉此行
.cipherSpec(cipherSpec) // 指定加密方式,使用默认加密可以省略
.writeAheadLoggingEnabled(true) // 打开WAL以及读写并发,可以省略让Room决定是否要打开
.asyncCheckpointEnabled(true); // 打开异步Checkpoint优化,不需要可以省略
AppDatabase db = Room.databaseBuilder(this, AppDatabase.class, "dbName") //dbName可以使用单独的名字或者绝对路径
//.allowMainThreadQueries() // 允许主线程执行DB操作,一般不推荐
.openHelperFactory(factory) // 重要:使用WCDB打开Room
.build();
实际换数据库的时候,由于无法打开数据库,导致线程阻塞很久,最后解决方式是删除了原有的数据库,重新创建
Room 使用了 SupportSQLiteDatabase 接口来提供底层操作的抽象,Room 所有相关的 API 返回的都是 SupportSQLiteDatabase 接口,如需要使用 WCDB 其他功能(比如 Repair)一般需要 SQLiteDatabase 接口,可以通过下面的方式取得。
// MyDatabase 为生成的 RoomDatabase
MyDatabase db = Room.databaseBuilder(...)
.openHelperFactory(new WCDBOpenHelperFactory(...))
.build();
// 用这个方法获取 SQLiteDatabase 接口
SQLiteDatabase sqlite = ((WCDBDatabase)db.getOpenHelper().getWritableDatabase()).getInnerDatabase();
// 使用 sqlite
或者在初始化时设置 callback
MyDatabase db = Room.databaseBuilder(...)
.openHelperFactory(new WCDBOpenHelperFactory(...))
// 添加初始化回调接口
.addCallback(new RoomDatabase.Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
// 从 SupportSQLiteDatabase 获取 SQLiteDatabase
SQLiteDatabase sqlite = ((WCDBDatabase)db).getInnerDatabase();
// 做其他事
}
})
.build();
上述功能暂时没用过
@Entity
public class User {
@PrimaryKey
public int uid;
@ColumnInfo(name = "first_name")
public String firstName;//如果表中的name跟变量名不同,可以自行设置
@ColumnInfo(name = "last_name")
public String lastName;
}
@Dao
public interface UserDao {
@Query("SELECT * FROM user")
List<User> getAll();
@Query("SELECT * FROM user WHERE uid IN (:userIds)")
List<User> loadAllByIds(int[] userIds);
@Query("SELECT * FROM user WHERE first_name LIKE :first AND " +
"last_name LIKE :last LIMIT 1")
User findByName(String first, String last);
@Insert
void insertAll(User... users);//
@Delete
void delete(User user);
}
@Database(entities = {User.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract UserDao userDao();
}
创建数据库
AppDatabase db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "database-name").build();
@Dao
public interface MyDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
public void insertUsers(User... users);
@Insert
public void insertBothUsers(User user1, User user2);
@Insert
public void insertUsersAndFriends(User user, List<User> friends);
}
如果@INSERT方法只接收一个参数,它可以返回一个long,这是插入项的新rowId。如果参数是数组或集合,则应该返回long[]或list。
@Dao
public interface MyDao {
@Update
public void updateUsers(User... users);
}
可以让此方法返回一个int值,指示数据库中更新的行数。
@Dao
public interface MyDao {
@Delete
public void deleteUsers(User... users);
}
可以让此方法返回一个int值,指示从数据库中删除的行数。
每个@Query方法都在编译时进行验证,因此如果查询有问题,则会发生编译错误,而不是运行时失败。
Room还验证查询的返回值,以便如果返回对象中的字段名称与查询响应中的相应列名不匹配,Room将通过以下两种方式之一提醒您:
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge")
public User[] loadAllUsersOlderThan(int minAge);
}
需要注意的是:当只需要查找类中的几列时:
//原get方法
get int getLib_id(){
return lib_id;
};
//将基本类型改为包装类
get Integer getLib_id(){
if(this.lib_id == null) return 0;
return lib_id;
};
ps: 如果想要使用非实体类参数来增删改数据,也要使用@Query注解。
此外,还可以在sql语句中进行一些复杂的查询。
@Query("delete from user where lastName=:lastName")
int deleteUserByLastName(String lastName);
@Query("select * from user where age>:age")
List<User> queryUsersByAge(int age);
@Query注解中可以使用复杂的SQL语句。Room内部封装了SQLite,Dao和Database文件会在\app\build\generated\ap_generated_sources\debug\out[package name]\database下生成对应的[XXXDao_impl.java]文件和[XXXDatabase_impl.java]文件。
其原理是,在生成的XXXDao_impl.java文件中,将注解内的SQL语句作为String提取出来,并使用RooSQLiteQuery进行解析,再使用Cursor取出要查询的数据。具体如下:
@Override
public List<User> queryUsersByAge(final int age) {
final String _sql = "select * from user where age>?";
final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 1);
int _argIndex = 1;
_statement.bindLong(_argIndex, age);
__db.assertNotSuspendingTransaction();
final Cursor _cursor = DBUtil.query(__db, _statement, false, null);
try {
final int _cursorIndexOfId = CursorUtil.getColumnIndexOrThrow(_cursor, "id");
final int _cursorIndexOfFirstName = CursorUtil.getColumnIndexOrThrow(_cursor, "firstName");
final int _cursorIndexOfLastName = CursorUtil.getColumnIndexOrThrow(_cursor, "lastName");
final int _cursorIndexOfAge = CursorUtil.getColumnIndexOrThrow(_cursor, "age");
final List<User> _result = new ArrayList<User>(_cursor.getCount());
while(_cursor.moveToNext()) {
final User _item;
_item = new User();
_item.id = _cursor.getInt(_cursorIndexOfId);
_item.firstName = _cursor.getString(_cursorIndexOfFirstName);
_item.lastName = _cursor.getString(_cursorIndexOfLastName);
_item.age = _cursor.getInt(_cursorIndexOfAge);
_result.add(_item);
}
return _result;
} finally {
_cursor.close();
_statement.release();
}
}
Room支持在编译时动态检查SQL语法。
当开发中使用了Google的Room框架的话,当你在之后的版本中新增了表或者改动了某些表结构的话,你就需要对数据库的版本号进行相应的更新,现在整理两种更新方式:
@Database(entities = {User.class}, version = 3)
public abstract class UsersDatabase extends RoomDatabase
database = Room.databaseBuilder(context.getApplicationContext(),
UsersDatabase.class, "Sample.db")
//添加下面这一行
.fallbackToDestructiveMigration()
.build();
这种方式会清空数据库中的数据,所以要使用这种方式之前一定要慎重考虑。fallbackToDestructiveMigration会将所有表全部丢弃。
a) 修改数据库版本号
@Database(entities = {User.class}, version = 2)
public abstract class UsersDatabase extends RoomDatabase
b) 创建Migration,1和2分别代表上一个版本和新的版本
static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
//此处对于数据库中的所有更新都需要写下面的代码
database.execSQL("ALTER TABLE users "
+ " ADD COLUMN last_update INTEGER");
}
};
c)把migration 添加到 Room database builder
database = Room.databaseBuilder(context.getApplicationContext(),
UsersDatabase.class, "Sample.db")
//增加下面这一行
.addMigrations(MIGRATION_1_2)
.build();
注:SQLite的ALTER TABLE命令非常局限,只支持重命名表以及添加新的字段。
使用WCDB结合ROOM数据库,可以大大减少代码量,但需要在注解中使用sql语句对数据库进行增删查改。使用过程中可能会遇到各种问题,这里总结一下我遇到的坑:
Android dependency ‘android.arch.core:runtime’ has different version for the compile (1.0.0) and runtime (1.1.1) classpath.
解决方法:
将implementation ‘com.tencent.wcdb:room:1.0.8’ 的implementation
改为 api ‘com.tencent.wcdb:room:1.0.8’