Android使用WCDB+Room ORM

接入方法

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<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);
}

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<User> 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);
}

需要注意的是:当只需要查找类中的几列时:

  1. 需要使用@SupperssWarnings注解
  2. 对于不需要查找的列,其类型如果为基本数据类型,则需要转换为包装类,修改其get方法。如:
//原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框架的话,当你在之后的版本中新增了表或者改动了某些表结构的话,你就需要对数据库的版本号进行相应的更新,现在整理两种更新方式:

1. 简单粗暴作死型:

@Database(entities = {User.class}, version = 3)
public abstract class UsersDatabase extends RoomDatabase
database = Room.databaseBuilder(context.getApplicationContext(),
                        UsersDatabase.class, "Sample.db")
                 //添加下面这一行
                .fallbackToDestructiveMigration()
                .build();

这种方式会清空数据库中的数据,所以要使用这种方式之前一定要慎重考虑。fallbackToDestructiveMigration会将所有表全部丢弃。

2. 正确姿势:

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,再有什么情况会更新的~~~
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值