Jetpack 架构组件 Room 数据库 ORM MD

Markdown版本笔记 个人GitHub首页 个人博客 个人微信 个人邮箱
MyAndroidBlogs baiqiantao baiqiantao bqt20094 baiqiantao@sina.com

目录

简介

官方文档
查看最新版本
参考文章html

Room是什么?java

Room是一个持久性数据库。react

The Room persistence library provides an abstraction layer over SQLite to allow for more robust database access while harnessing the full power of SQLite.android

Room持久性数据库提供了SQLite的抽象层,以便在充分利用SQLite的同时容许流畅的数据库访问。git

Room 的优势
在Android领域,目前已有很多优秀的开源的数据库给你们使用,如SQLite、XUtils、greenDao、Realm,那为何咱们还要去学习使用这个库呢?由于Room有下面几个优势:github

  • SQL查询在编译时就会验证 - 在编译时检查每一个@Query和@Entity等,而且它不只检查语法问题,还会检查是否有该表,这就意味着没有任何运行时错误的风险可能会致使应用程序崩溃
  • LiveDatarxjava集成,特别是与LiveData等架构组件配合使用后,能产生让人惊叹的效果
  • 较少的模板代码

Room 的组成
Room中有三个主要组件组成:sql

  • Database: 用这个组件建立一个数据库。注解定义了一系列entities,而且类中提供一系列Dao的抽象方法,也是下层主要链接的访问点。注解的类应该是一个继承 RoomDatabase 的抽象类。在运行时,你能经过调用Room.databaseBuilder()或者 Room.inMemoryDatabaseBuilder()得到一个实例
  • Entity: 用这个组件建立表,Database类中的entities数组经过引用这些entity类建立数据库表。每一个entity中的字段都会被持久化到数据库中,除非用@Ignore注解
  • DAO: 这个组件表明了一个用来操做表增删改查的dao。Dao 是Room中的主要组件,负责定义访问数据库的方法。被注解@Database的类必须包含一个没有参数的且返回注解为@Dao的类的抽象方法。在编译时,Room建立一个这个类的实现。

Entity 相关注解

当一个类被注解为@Entity而且引用到带有@Database注解的entities属性,Room为这个数据库引用的entity建立一个数据表。 数据库

Entity类可以有一个空的构造函数(若是dao类可以访问每一个持久化的字段)或者一个参数带有匹配entity中的字段的类型和名称的构造函数json

Entity的字段必须为public或提供setter或者getter方法。数组

Ignore:忽略
默认状况下,Room为每一个定义在entity中的字段建立一个列。若是一个entity的一些字段不想持久化,可使用@Ignore注解它们

@Ignore Bitmap picture;

PrimaryKey:主键

每一个entity必须定义至少一个字段做为主键,即便这里只有一个字段,仍然须要使用@PrimaryKey注解这个字段。而且,若是想Room动态给entity分配自增主键,能够设置@PrimaryKey的autoGenerate属性为true。若是entity有个组合的主键,你可使用@Entity注解的primaryKeys属性:

@PrimaryKey(autoGenerate = true) public int id // 自增主键

组合主键:

@Entity(primaryKeys = {"firstName", "lastName"})
class User {}

tableName:数据库的表名
默认状况下,Room使用类名做为数据库的表名。若是但愿表有一个不一样的名称,设置@Entity注解的tableName属性,以下所示:

@Entity(tableName = "users")
class User {}

注意: SQLite中的表名是大小写敏感的。

列名称:ColumnInfo
Room使用字段名称做为列名称。若是你但愿一个列有不一样的名称,为字段增长@ColumnInfo注解

@ColumnInfo(name = "first_name") public String firstName;

indices:索引
数据库索引能够加速数据库查询,@Entity的indices属性能够用于添加索引。在索引或者组合索引中列出你但愿包含的列的名称:

@Entity(indices = {@Index("name"), @Index({"first_name", "last_name"}),})
public class User {}

有时,表中的某个字段或字段组合须要确保惟一性,能够设置@Entity的@Index注解的unique属性为true。

@Entity(indices = {@Index(value = {"first_name", "last_name"}, unique = true)})
public class User {}

foreignKeys:外键约束
例如:若是有一个entity叫book,你能够经过使用@ForeignKey注解定义它和user的关系:

@Entity(foreignKeys = @ForeignKey(entity = User.class, parentColumns = "id", childColumns = "user_id"))
class Book {}

外键是十分强大的,由于它们容许你指明当引用的entity被更新后作什么。
例如,若是相应的user实例被删除了,你能够经过包含@ForeignKey注解的onDelete=CASCADE属性让SQLite为这个user删除全部的书籍。

SQLite处理@Insert(OnConflict=REPLACE)做为一个REMOVE和REPLACE操做而不是单独的UPDATE操做。这个替换冲突值的方法可以影响你的外键约束。

Embedded:嵌入式字段
有时,但愿entity中包含一个具备多个字段的对象做为字段。在这种状况下,可使用@Embedded注解去表明一个但愿分解成一个表中的次级字段的对象。接着你就能够像其余单独的字段那样查询嵌入字段:

@Embedded public Address address;
class Address {
    @ColumnInfo(name = "post_code") public int postCode; //嵌入式字段还能够包含其余嵌入式字段
}

若是一个实体具备相同类型的多个内嵌字段,则能够经过设置前缀属性(prefix)使每一个列保持唯一。而后将所提供的值添加到嵌入对象中每一个列名的开头

@Embedded(prefix = "foo_") Coordinates coordinates;

对象引用
SQLite是个关系型数据库,可以指明两个对象的关系。大多数ORM库支持entity对象引用其余的。Room明确的禁止这样。更多细节请参考Understand why Room doesn’t allow object references

DAO:增删改查

Room的三大组件之一Dao,以一种干净的方式去访问数据库。

Room 不容许在主线程中访问数据库。除非在建造器中调用allowMainThreadQueries(),这可能会形成长时间的锁住UI。

注意,异步查询API(返回LiveData或者RxJava流的查询)从这个规则中豁免,由于它们异步的在后台线程中进行查询。

Insert

在Dao中建立一个方法而且使用@Insert注解它,Room会为其生成一个实现,此实现会在单独事务中插入全部参数到数据库中:

@Insert
long insertUser(User user); //参数至少有一个,有一个时能够返回long(表明新插入item的rowId)或不返回

@Insert
long[] insertAll(User... users); //参数也能够是集合或者数组,此时能够返回long[]或者List<Long>或不返回

@Insert(onConflict = OnConflictStrategy.REPLACE)
void insertAll(List<User> users);//能够执行事务操做
long id = db.userDao().insertUser(new User("哎"));
List<Long> ids = db.userDao().insertAll(new User("哎哎" + new Random().nextInt(100)));
db.userDao().insertAll(Arrays.asList(new User("哎啊"), new User("啊哎")));

@Insert方法必须至少有一个参数,若是参数只有一个,它能够返回一个新插入item的rowId的long值(或不返回)
若是参数是一个集合或数组,它能够返回long[]或者List<Long>(或不返回)

@Insert,@Update均可以执行事务操做,定义在OnConflictStrategy注解类中:

@Retention(SOURCE)
public @interface OnConflictStrategy {
    int REPLACE = 1; //替换旧数据
    int ROLLBACK = 2; //回滚事务
    int ABORT = 3; //就退出事务
    int FAIL = 4; //使事务失败 
    int IGNORE = 5; //忽略冲突
}

Delete

@Delete是一个从数据库中删除一系列给定参数的entities的惯例方法。它使用主键找到要删除的entities:

@Delete
int delete(User user);//根据主键删除entities,能够没有返回值或者返回int,返回int值时表示删除的数量

@Delete
int deleteAll(List<User> users); //参数至少有一个,也能够是集合或者数组,参数必须是使用@Entity注解标注的类,且不能为null
User user = db.userDao().findUserByName("啊哎"); //若是没找到的话返回null
if (user != null) Log.i("bqt", "【delete数量】" + db.userDao().delete(user)); //不能传入空,不然崩溃
Log.i("bqt", "【deleteAll数量】" + db.userDao().deleteAll(db.userDao().getAll()));

尽管一般不是必须的,你可以拥有这个方法返回int值指示数据库中删除的数量。
没有提供根据主键等方式删除(包括更新)数据的方式,参数必须是使用@Entity注解标注的类,不能是字符串!

Update

@Update 是更新一系列entities集合、给定参数的惯例方法。它使用query来匹配每一个entity的主键:

@Update
int updateAll(User... users);//规则和delete同样

尽管一般不是必须的,你可以拥有这个方法返回int值指示数据库中更新的数量。

Query

@Query 是DAO类中使用的主要注解,每一个@Query方法在编译时(而不是运行时)被校验,若是查询包含语法错误,或者若是用户表不存在,Room会报出合适的错误消息:

  • 若是仅有一些字段匹配会警告
  • 若是没有字段匹配会报错
@Query("SELECT * FROM table_user")
List<User> getAll();//返回值为集合或数组的话,返回找到的全部数据,没找到时返回长度为0的集合或数组

查询时传入参数
若是你须要传入参数到查询语句中去过滤操做,你可使用方法参数

@Query("SELECT * FROM user WHERE age > :minAge") User[] loadAllUsersOlderThan(int minAge);
@Query("SELECT * FROM user WHERE uid IN (:userIds)") List<User> loadAllByIds(int[] userIds);

当这个查询在编译期被处理,Room会匹配:minAge绑定的方法参数。Room经过使用参数名称执行匹配,若是没有匹配到,在你的app编译期将会报错。

查询时能够传入多个参数或者屡次引用同一个参数。

@Query("SELECT * FROM user WHERE first_name LIKE :first AND last_name LIKE :last LIMIT 1")
User findByName(String first, String last);

返回列中的子集
多数时候,咱们仅须要获取一个entity中的部分字段。例如,你的UI可能只展现user第一个和最后一个名称,而不是全部关于用户的细节。经过获取展现在UI的有效数据列可使查询完成的更快

只要查询中列结果集可以被映射到返回的对象中,Room容许你返回任何java对象。例如,经过建立以下POJO拿取用户的姓和名:

@Query("SELECT first_name, last_name FROM user") List<NameTuple> loadFullName();
public class NameTuple {
    @ColumnInfo(name="first_name") public String firstName;
    @ColumnInfo(name="last_name") public String lastName;
}

Room理解查询返回first_name和last_name的列值被映射到NameTuple类中。所以,Room可以生成合适的代码。若是查询返回太多columns,或者一个列不存在,Room将会报警。

返回LiveData对象
使用返回值类型为LiveData实现数据库更新时ui数据自动更新:

db.userDao().loadUsersByName2("%哎%") //能够在主线程调用
    .observe(this, users -> tvTitle.setText("【最新查询结果】" + new Gson().toJson(users)));

返回RxJava对象
Room也能返回RxJava2的Publisher和Flowable对象,需添加android.arch.persistence.room:rxjava2依赖:

@Query("SELECT * from user where uid = :uid") Flowable<User> findUserById(int uid);

直接游标访问
若是你的应用逻辑直接访问返回的行,你能够从你的查询当中返回一个Cursor对象:

@Query("SELECT * FROM user WHERE uid > :uid LIMIT 5") Cursor biggerId(int uid);//不推荐

注意:很是不建议使用Cursor API 由于它不能保证行是否存在或者行包含什么值。
使用这个功能仅仅是由于你已经有一个返回cursor的代码,而且你不能轻易的重构。

多表查询
在SQLite数据库中,咱们能够指定对象之间的关系,所以咱们能够将一个或多个对象与一个或多个其余对象绑定。这就是所谓的一对多和多对多的关系。

一些查询可能访问多个表去查询结果,Room容许你写任何查询,因此你也能链接表。

以下代码段展现如何执行一个根据借书人姓名模糊查询借的书的相关信息。

@Query("SELECT * FROM book "
       + "INNER JOIN loan ON loan.book_id = book.id "
       + "INNER JOIN user ON user.id = loan.user_id "
       + "WHERE user.name LIKE :userName")
List<Book> findBooksBorrowedByNameSync(String userName);

从这些查询当中也能返回POJOs,例如,能够写一个POJO去装载userName和petName,以下:

@Query("SELECT user.name AS userName, pet.name AS petName FROM user, pet WHERE user.id = pet.user_id")
LiveData<List<UserPet>> loadUserAndPetNames();
class UserPet {
   public String userName;
   public String petName;
}

其余

类型转换

Room为原始类型和可选的装箱类型提供嵌入支持。然而,有时你可能使用一个单独存入数据库的自定义数据类型。为了添加这种类型的支持,你能够提供一个TypeConverter把自定义类转化为一个Room可以持久化的已知类型。

例如:若是咱们想持久化Date的实例,咱们能够写以下TypeConverter在数据库中去存储相等的Unix时间戳:

public Date birthday;
public class Converters {
    @TypeConverter
    public static Date fromTimestamp(Long value) {
        return value == null ? null : new Date(value);
    }

    @TypeConverter
    public static Long dateToTimestamp(Date date) {
        return date == null ? null : date.getTime();
    }
}

接着,你增长@TypeConverters注解到RoomDatabase子类:

@Database(entities = {User.java}, version = 1)
@TypeConverters({Converter.class})
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao();
}

使用这些转换器后,你就能够像使用的原始类型同样使用自定义类型了:

@Query("SELECT * FROM user WHERE birthday BETWEEN :from AND :to")
List<User> findUsersBornBetweenDates(Date from, Date to);

注意:除了将 @TypeConverters 放在RoomDatabase里面影响全局外,还能够将 @TypeConverter 限制在不一样的范围内,包含单独的entity、Dao和Dao中的 methods,具体查看官方文档。

模糊匹配

参考

一般,咱们使用SQL语句模糊查询时是这样的:

select * from user where name like '%包_天%'; //%表明任意个字符,_表明一个字符

可是在Dao中不能简单地这么写,任你各类拼接啊、转义啊,都不能把那个%给它弄进去。

这里不得不说一个Room的一个好处,就是你在写sql语句的时候它会检查,格式不对,里面会报红线,根本就编译不经过。

皇天不负有心人,最终在Stack Overflow上面搜了一下,找到了最终答案,应该是这样的

@Query("SELECT * from user where last_name LIKE '%' || :name || '%'")
LiveData<List<User>> loadUsersByName(String name);

原来是用双竖杠去拼接,而不是加号,欲哭无泪啊。而后我就去搜了一下sql语句拼接,找到这样一段话:

在SQL中的SELECT语句中,可以使用一个特殊的操做符来拼接两个列。根据你所使用的DBMS,此操做符可用加号(+)或两个竖杠(||)表示。
在MySQL和MariaDB中,必须使用特殊的函数。
Access和SQL Server使用 + 号。
SQLite、DB二、Oracle、PostgreSQL、Open Office Base使用 ||。

其实除了我上面用双竖杠拼接的方式外,还有一种方法,就是在传参的时候,把%_%给拼接好,这种方式我也是测试了一下,也是能够的,由于最终都是转换成sql的标准查询语句。

db.userDao().loadUsersByName("%包_天%")

并且,我感受这种方式写起来更优雅,应该也是Google变相推荐的方式。

总结

  • 方式一,在传给Dao方法以前自行拼接:如db.userDao().loadUsersByName("%包_天%")
  • 方式二:在Dao方法中的方法参数先后使用||拼接,如:@Query("SELECT * from user where name LIKE '_%' || :name || '%'")

输出模式

在编译时,将数据库的模式信息导出到JSON文件中,这样可有利于咱们更好的调试和排错(DataBase的exportSchema = true)

module中的build.gradle:

android {
    defaultConfig {
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = ["room.schemaLocation": "$projectDir/schemas".toString()]//room的数据库概要、记录
            }
        }
    }
}

配置完毕以后,咱们在构建项目以后会在对应路径生成schemas文件夹。
文件夹内的1.json等文件分别是数据库版本一、二、3等版本对应的概要、记录。

数据库升级

若是我们删除了entity中的一个字段,运行程序后,就会出现下面这个问题。

java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.

大体的意思是:你修改了数据库,可是没有升级数据库的版本

这时候我们根据错误提示增长版本号,但没有提供migration,APP同样会crash。

java.lang.IllegalStateException: A migration from 1 to 2 was required but not found. Please provide the necessary Migration path via RoomDatabase.Builder.addMigration(Migration ...) or allow for destructive migrations via one of the RoomDatabase.Builder.fallbackToDestructiveMigration* methods.

大体的意思是:让咱们添加一个addMigration或者调用fallbackToDestructiveMigration完成迁移

接下来,我们增长版本号并使用fallbackToDestructiveMigration(),虽然可使用了,可是咱们会发现,数据库的内容都被咱们清空了,显然这种方式是不友好的。若是不想清空数据库,就须要提供一个实现了的migration。

Room容许使用Migration类保留用户数据。每一个Migration类在运行时指明一个开始版本和一个结束版本,Room执行每一个Migration类的migrate()方法,使用正确的顺序去迁移数据库到一个最近版本。
若是不提供必需的Migrations类,Room会重建数据库,这意味着你将丢失数据库中的全部数据。

正常升级

好比我们要在Department中添加phoneNum

public class Department {
    @PrimaryKey(autoGenerate = true) int id;
    String dept;
    @ColumnInfo(name = "emp_id") int empId;
    @ColumnInfo(name = "phone_num") String phoneNum;
}

一、把版本号自增

@Database(entities = {Department.class, Company.class}, version = 2, exportSchema = false)
@TypeConverters(DateConverter.class)
public abstract class DepartmentDatabase extends RoomDatabase {}

二、添加一个version:1->2的migration

static final Migration MIGRATION_1_2 = new Migration(1, 2) {
    @Override
    public void migrate(SupportSQLiteDatabase database) {
        database.execSQL("ALTER TABLE department ADD COLUMN phone_num TEXT");
    }
};

三、把migration 添加到 databaseBuilder

Room.databaseBuilder(context, DepartmentDatabase.class, DB_NAME)
    .addMigrations(MIGRATION_1_2)
    .build();

再次运行APP就会发现,数据库表更新了,而且旧数据也保留了。

跳跃升级

在平时的开发时,数据库的升级并不老是循序渐进的从 version: 1->2,2->3,3->4。老是会出现 version:1->3,或 2->4 的状况。这时候咱们又该怎么办呢?

方法很简单。当用户升级 APP 时,咱们替用户升级数据库版本。

具体作法:
version:1->2

database.execSQL("CREATE TABLE `Fruit` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`))"); //建立表

version:2->3

database.execSQL("ALTER TABLE Book ADD COLUMN pub_year INTEGER"); //添加字段

version:3->4

//字段类型修改
db.execSQL("CREATE TABLE db_new (_id TEXT, _name TEXT, _phone INTEGER, PRIMARY KEY(_id))");  //建立表
db.execSQL("INSERT INTO db_new (_id, _name, _phone) SELECT _id, _name, _phone FROM db_old"); //复制表
db.execSQL("DROP TABLE db_old");  //删除表
db.execSQL("ALTER TABLE db_new RENAME TO students");  //修改表名称

而后把 migration 添加到 Room database builder:

Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "database-name")
    .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4)
    .build();

这样,要是用户刚下载的 APP,目前咱们定义了migrations:version 1 到 2, version 2 到 3, version 3 到 4, 因此 Room 会一个接一个的触发全部 migration。

其实 Room 能够处理大于 1 的版本增量:咱们能够一次性定义一个从 1 到 4 的 migration,以提高迁移的速度。

.addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_1_4)

使用案例

添加依赖

implementation "android.arch.persistence.room:runtime:1.1.1"  //Room
annotationProcessor "android.arch.persistence.room:compiler:1.1.1"
testImplementation "android.arch.persistence.room:testing:1.1.1" //Test helpers for Room
implementation "android.arch.persistence.room:rxjava2:1.1.1" //为room添加rxjava支持库
implementation "android.arch.lifecycle:reactivestreams:1.1.1" //和LiveData一块儿使用

定义Database

@Database(entities = {User.class}, version = 1)
@TypeConverters({MyConverters.class})
public abstract class AppDatabase extends RoomDatabase {
    public abstract UserDao userDao(); //没有参数的抽象方法,返回值所表明的类必须用@Dao注解
}

Build后会自动生成的AppDatabase的实现类:

定义实体

@Entity(tableName = "table_user") //自定义表名称
public class User {
    @PrimaryKey(autoGenerate = true) public int uid;//自增主键
    @ColumnInfo(name = "user_name") public String name; //自定义列名称
    public Date birthday; //类型转换
    @Ignore public String tips = "忽略的字段"; //忽略
    //省略了get、set、构造方法等代码
}

定义Dao

@Dao
public interface UserDao {

    @Insert
    long insertUser(User user); //参数至少有一个,有一个时能够返回long(表明新插入item的rowId)或不返回

    @Insert
    List<Long> insertAll(User... users); //参数也能够是集合或者数组,此时能够返回long[]或者List<Long>或不返回

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    void insertAll(List<User> users);//能够执行事务操做

    @Delete
    int delete(User user);//根据主键删除entities,能够没有返回值或者返回int,返回int值时表示删除的数量

    @Delete
    int deleteAll(List<User> users); //参数至少有一个,也能够是集合或者数组,参数必须是使用@Entity注解标注的类,且不能为null

    @Query("SELECT * FROM table_user")
    List<User> getAll();//返回值为集合或数组的话,返回找到的全部数据,没找到时返回长度为0的集合或数组

    @Query("SELECT * FROM table_user WHERE user_name LIKE :name LIMIT 1")
    User findUserByName(String name); //返回值为User的话,只返回找到的第一条数据,没找到时返回null

    @Query("SELECT * FROM table_user WHERE uid IN (:userIds)")
    User[] loadAllByIds(int[] userIds); //没找到时返回长度为0的集合或数组

    @Query("SELECT * FROM table_user WHERE uid > :uid LIMIT 5")
    Cursor biggerId(int uid);//不推荐

    @Query("SELECT * from table_user where uid = :uid")
    Flowable<User> findUserById(int uid); //和rxjava结合使用

    @Query("SELECT * from table_user where user_name LIKE :name ")
    LiveData<List<User>> loadUsersByName(String name); //和LiveData结合使用

    @Query("SELECT * from table_user where user_name LIKE '_%' || :name || '%'")
    LiveData<List<User>> loadUsers(String name);//模糊搜索
}

Build后自动生成的UserDao的实现类:

代码中使用

public class MainActivity extends FragmentActivity {
    private TextView tvTitle, tvWeixin, tvFriend, tvContact, tvSetting;
    private String[] titles = {"微信", "通信录", "发现", "我"};
    private MyViewModel mModel;
    private AppDatabase db;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        mModel = ViewModelProviders.of(this).get(MyViewModel.class);
        db = Room.databaseBuilder(getApplicationContext(), AppDatabase.class, "dbname").build();
        db.userDao().loadUsersByName("%哎_%") //返回LiveData<List<User>>,实际数据库操做工做在子线程
            .observe(this, users -> {
                Log.i("bqt", "【是否在主线程中】" + (Looper.getMainLooper() == Looper.myLooper())); //true
                String newUsers = GsonUtils.toJson(users);
                Log.i("bqt", "【数据库中查询到的数据发生变化】" + newUsers);
                mModel.getMutableLiveData().setValue(newUsers); //这里是主线程,能够直接使用setValue
            });
    }

    private void initView() {
        tvWeixin = findViewById(R.id.tv_tab_bottom_weixin);
        tvFriend = findViewById(R.id.tv_tab_bottom_friend);
        tvContact = findViewById(R.id.tv_tab_bottom_contact);
        tvSetting = findViewById(R.id.tv_tab_bottom_setting);

        tvWeixin.setTag(0);
        tvFriend.setTag(1);
        tvContact.setTag(2);
        tvSetting.setTag(3);

        tvWeixin.setOnClickListener(v -> onClickTab((Integer) v.getTag()));
        tvFriend.setOnClickListener(v -> onClickTab((Integer) v.getTag()));
        tvContact.setOnClickListener(v -> onClickTab((Integer) v.getTag()));
        tvSetting.setOnClickListener(v -> onClickTab((Integer) v.getTag()));

        tvTitle = findViewById(R.id.tv_title);
        tvTitle.setOnClickListener(v -> {
            new Thread(() -> {
                List<User> allUsers = db.userDao().getAll();//必须在子线程访问数据库
                mModel.getMutableLiveData().postValue("所有数据\n" + GsonUtils.toJson(allUsers)); //在子线程必须用postValue
            }).start();
            tvTitle.setBackgroundColor(0xFF000000 + new Random().nextInt(0xFFFFFF));
        });
        getSupportFragmentManager().beginTransaction().add(R.id.id_container, MyFragment.newInstance("MyFragment")).commit();
    }

    private void onClickTab(int position) {
        tvWeixin.setSelected(position == 0);
        tvFriend.setSelected(position == 1);
        tvContact.setSelected(position == 2);
        tvSetting.setSelected(position == 3);
        tvTitle.setText(titles[position]);

        switch (position) {
            case 0:
                new Thread(() -> {
                    long id = db.userDao().insertUser(new User("哎"));
                    List<Long> ids = db.userDao().insertAll(new User("哎哎" + new Random().nextInt(100)));
                    db.userDao().insertAll(Arrays.asList(new User("哎啊"), new User("啊哎")));
                    Log.i("bqt", "【id】" + id + "【ids】" + ids);
                }).start(); //不能在主线程访问数据库,不然报IllegalStateException
                break;
            case 1:
                new Thread(() -> {
                    User user = db.userDao().findUserByName("啊哎"); //若是没找到的话返回null
                    if (user != null) Log.i("bqt", "【delete数量】" + db.userDao().delete(user)); //不能传入空,不然崩溃
                    User[] users = db.userDao().loadAllByIds(new int[]{1, 2, 3, 4, 5, 6}); //没找到时返回长度为0的集合或数组
                    Log.i("bqt", "【deleteAll数量】" + db.userDao().deleteAll(Arrays.asList(users)));
                }).start();
                break;
            case 2:
                new Thread(() -> {
                    User user = db.userDao().findUserByName("哎%"); //若是没找到的话返回null
                    user.name = "更新name";
                    Log.i("bqt", "【updateAll数量】" + db.userDao().updateAll(user));
                }).start();
                break;
            case 3:
                db.userDao().findUserById(1) //返回Flowable<User>
                    .map(GsonUtils::toJson) //数组类型转换
                    .subscribeOn(Schedulers.io()) //必须切换到子线程中访问数据库
                    //.observeOn(AndroidSchedulers.mainThread()) //能够在子线程中使用postValue,也能够切换到主线程中使用setValue
                    .subscribe(s -> mModel.getMutableLiveData().postValue(s));
            default:
                break;
        }
    }
}

2019-3-29

相关文章
相关标签/搜索