接入方法
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();
实际换数据库的时候,由于无法打开数据库,导致线程阻塞很久,最后解决方式是删除了原有的数据库,重新创建
使用 WCDB 其他功能
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();
上述功能暂时没用过
ROOM数据库使用
ROOM数据库中三个主要组成部分
1、Entity
@Entity
public class User {
@PrimaryKey
public int uid;
@ColumnInfo(name = "first_name")
public String firstName;//如果表中的name跟变量名不同,可以自行设置
@ColumnInfo(name = "last_name")
public String lastName;
}
2、Dao
@Dao
public interface UserDao {
@Query("SELECT * FROM user")
List getAll();
@Query("SELECT * FROM user WHERE uid IN (:userIds)")
List 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);
}
3、DataBase
@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();
数据库的增删查改CRUD
1、Insert
@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 friends);
}
如果@INSERT方法只接收一个参数,它可以返回一个long,这是插入项的新rowId。如果参数是数组或集合,则应该返回long[]或list
2、Update
@Dao
public interface MyDao {
@Update
public void updateUsers(User... users);
}
可以让此方法返回一个int值,指示数据库中更新的行数。
3、Delete
@Dao
public interface MyDao {
@Delete
public void deleteUsers(User... users);
}
可以让此方法返回一个int值,指示从数据库中删除的行数。
4、Query
每个@Query方法都在编译时进行验证,因此如果查询有问题,则会发生编译错误,而不是运行时失败。
Room还验证查询的返回值,以便如果返回对象中的字段名称与查询响应中的相应列名不匹配,Room将通过以下两种方式之一提醒您:
- 如果只有某些字段名匹配,则会发出警告。
- 如果没有匹配的字段名,则会产生错误。
@Dao
public interface MyDao {
@Query("SELECT * FROM user WHERE age > :minAge")
public User[] loadAllUsersOlderThan(int minAge);
}
需要注意的是:当只需要查找类中的几列时:
- 需要使用@SupperssWarnings注解
- 对于不需要查找的列,其类型如果为基本数据类型,则需要转换为包装类,修改其get方法。如:
//原get方法
get int getLib_id(){
return lib_id;
};
//将基本类型改为包装类
get Integer getLib_id(){
if(this.lib_id == null) return 0;
return lib_id;
};
数据库升级
当开发中使用了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'
- 如果只是build了数据库,但是没有操作的话,数据库是不会创建到本地的,如果在文件夹里没找到,大家不要方(:з」∠)
- delete和insert操作,如果没有对应的数据可能会crash掉,建议使用之前先检查一下有没有数据。
- 持续掉坑ing,再有什么情况会更新的~~~