//查询所有数据
@Query(“Select * from person”)
List getAll();
//删除全部数据
@Query(“DELETE FROM person”)
void deleteAll();
//一次插入单条数据 或 多条
// @Insert(onConflict = OnConflictStrategy.REPLACE),这个是干嘛的呢,下面有详细教程
@Insert
void insert(Person… persons);
//一次删除单条数据 或 多条
@Delete
void delete(Person… persons);
//一次更新单条数据 或 多条
@Update
void update(Person… persons);
//根据字段去查找数据
@Query(“SELECT * FROM person WHERE uid= :uid”)
Person getPersonByUid(int uid);
//一次查找多个数据
@Query(“SELECT * FROM person WHERE uid IN (:userIds)”)
List loadAllByIds(List userIds);
//多个条件查找
@Query(“SELECT * FROM person WHERE name = :name AND age = :age”)
Person getPersonByNameage(String name, int age);
}
这里唯一特殊的就是@Insert。其有一段介绍:对数据库设计时,不允许重复数据的出现。否则,必然造成大量的冗余数据。实际上,难免会碰到这个问题:冲突。当我们像数据库插入数据时,该数据已经存在了,必然造成了冲突。该冲突该怎么处理呢?在@Insert注解中有conflict用于解决插入数据冲突的问题,其默认值为OnConflictStrategy.ABORT。对于OnConflictStrategy而言,它封装了Room解决冲突的相关策略。
这里比如在插入的时候我们加上了OnConflictStrategy.REPLACE,那么往已经有uid=1的person表里再插入uid =1的person数据,那么新数据会覆盖就数据。如果我们什么都不加,那么久是默认的OnConflictStrategy.ABORT,重复上面的动作,你会发现,程序崩溃了。也就是上面说的终止事务。其他大家可以自己试试
直接上代码
//注解指定了database的表映射实体数据以及版本等信息(后面会详细讲解版本升级)
@Database(entities = {Person.class, Clothes.class}, version = 1)
public abstract class AppDataBase extends RoomDatabase {
public abstract PersonDao getPersonDao();
public abstract ClothesDao getClothesDao();
}
Room创建我们的AppDataBase,我们把它封装成单例,省的每次都去执行一遍,耗性能
public class DBInstance {
//private static final String DB_NAME = “/sdcard/LianSou/room_test.db”;
private static final String DB_NAME = “room_test”;
public static AppDataBase appDataBase;
public static AppDataBase getInstance(){
if(appDataBasenull){
synchronized (DBInstance.class){
if(appDataBasenull){
appDataBase = Room.databaseBuilder(MyApplication.getInstance(),AppDataBase.class, DB_NAME)
//下面注释表示允许主线程进行数据库操作,但是不推荐这样做。
//我这里是为了Demo展示,稍后会结束和LiveData和RxJava的使用
.allowMainThreadQueries()
.build();
}
}
}
return appDataBase;
}
}
做完这一切,那么我们的准备工作就做完了。让我们来插入一条数据
Person person_ = new Person(“Room”, 18);
DBInstance.getInstance().getPersonDao().insert(person_);
这里怎么查看db数据呢?首先我们把db文件存在手机内存里,记得打开存储权限,就是在上面代码里指定路径
private static final String DB_NAME = “/sdcard/LianSou/room_test.db”;
插入数据后,就会在手机内存卡生成db文件。
拿到db文件,怎么办呢。用插件!!Database Navigator,插件教程
这里的意思比如我已经往person表存里数据。但是我要增加字段,或者是增加索引。如果你直接写上去,你会发现,你再使用数据库的时候,会直接崩溃。怎么办呢,用过greendao的人都知道,我们要升级数据库版本
@Entity
public class Person {
//…省略部分代码,便于理解。
//这里给Person加上一个儿子
}
然后来到我们的Database类里,把版本信息改下,并增添一个Migration 类,告诉Room是哪张表改了什么东西
//修改版本信息为2
@Database(entities = {Person.class, Clothes.class}, version = 2)
public abstract class AppDataBase extends RoomDatabase {
public abstract PersonDao getPersonDao();
public abstract ClothesDao getClothesDao();
//数据库变动添加Migration,简白的而说就是版本1到版本2改了什么东西
public static final Migration MIGRATION_1_2 = new Migration(1, 2) {
@Override
public void migrate(SupportSQLiteDatabase database) {
//告诉person表,增添一个String类型的字段 son
database.execSQL(“ALTER TABLE person ADD COLUMN son TEXT”);
}
};
}
关于版本更新的execSQL里的用法,可以参考Room升级。也可以自行度娘,网上很多
最后来到我们的Room里:
public class DBInstance {
// private static final String DB_NAME = “/sdcard/LianSou/room_test.db”;
private static final String DB_NAME = “room_test”;
public static AppDataBase appDataBase;
public static AppDataBase getInstance(){
if(appDataBasenull){
synchronized (DBInstance.class){
if(appDataBasenull){
return Room.databaseBuilder(MyApplication.getInstance(),AppDataBase.class, DB_NAME)
.allowMainThreadQueries()
//加上版本升级信息
.addMigrations(AppDataBase.MIGRATION_1_2)
.build();
}
}
}
return appDataBase;
}
}
做完以上操作后,我们来运行下项目看看。成功,打开数据看看(本文demo里,我把升级代码注释了,想测试的可自行打开):
首先看我们DBInstance里的Room创建我们的AppDataBase,这句代码
//下面注释表示允许主线程进行数据库操作,但是不推荐这样做。
.allowMainThreadQueries()
我们,把这句代码注释掉,其他不变,运行下代码,看看。结果会报错,报错信息如下
Caused by: java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time.
这个时候,我们来结合RxJava来使用下,这样数据操作可以放在子线程,回调可以切换到主线程更改UI。首先是引入我们的依赖
implementation ‘android.arch.persistence.room:rxjava2:2.1.4’
//下面这个是配合rxjava使用的
implementation ‘io.reactivex.rxjava2:rxandroid:2.0.2’
这里需要注意2点:
1、在数据库执行**@Insert、@Delete、@Update**操作时候可以使用(注意是可以使用)RxJava里的类型有:Completable,Single,Maybe
2、在执行**@Query**操作时,可以返回的类型有:Single,Maybe,Observable,Flowable
这里需要注意:
这里可能大家对Single,Maybe,Completable,Observable,Flowable不大了解,这里做个简单介绍:
1、Completable:只有onComplete和onError方法,即只有“完成”和“错误”两种状态,不会返回具体的结果。
2、Single:其回调为onSuccess和onError,查询成功会在onSuccess中返回结果,需要注意的是,如果未查询到结果,即查询结果为空,会直接走onError回调,抛出EmptyResultSetException异常。
3、Maybe:其回调为onSuccess,onError,onComplete,查询成功,如果有数据,会先回调onSuccess再回调onComplete,如果没有数据,则会直接回调onComplete。
4、Flowable/Observable:这是返回一个可观察的对象,查询的部分有变化时,都会回调它的onNext方法,没有数据变化的话,不回调。直到Rx流断开。
这里为了demo的清晰化,我们再建一个Dog表。这里申明一点,在数据库执行这些操作的时候@Insert、@Delete、@Update,不能直接把返回类型写成RxJava返回,类型,不然会直接报
错误: Methods annotated with @Insert can return either void, long, Long, long[], Long[] or List.
所以现在好多网上关于这部分,也没有讲清楚。如果有清楚的同学请指正。请看Dao类:
@Dao
public interface DogDao {
//返回值是插入成功的行id
@Insert
List insert(Dog… dogs);
@Delete
void delete(Dog… dogs);
//返回删除的行id
@Delete
int delete(Dog dog);
@Update
void update(Dog… dogs);
@Update
int update(Dog dog);
//查询所有对象 且 观察数据。用背压Flowable可以实现,如果需要一次性查询,可以用别的类型
@Query(“Select * from dog”)
Flowable getAll();
//删除全部数据
@Query(“DELETE FROM dog”)
void deleteAll();
//根据字段去查找数据
@Query(“SELECT * FROM dog WHERE id= :id”)
Single getDogById(int id);
}
让我们在代码里,用可观察的背压,去实时查询我们的全部dog。这里只要调用一次,之后数据有更新的时候,会自动走这个观察者回调。
DBInstance.getInstance().getDogDao().getAll().subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer() {
@Override
public void accept(List dogs) throws Exception {
binding.txtAll.setText(“当前狗狗总数” + dogs.size());
}
});
那很多人问了。@Insert、@Delete、@Update这些该怎么办。很多博客都是把返回值写在Dao里。真实运行起来,直接报错。所以这里要在代码中使用RxJava。用于项目的时候最好封装起来。比如用Single插入数据:(这里用哪个类型呢,完全根据你的需求而定,比如插入数据后,我要知道插入的行id的是多少,就不能用Completable,因为他没有返回值,这个还是灵活运用的)
Single.fromCallable(new Callable() {
@Override
public List call() throws Exception {
Dog dog = new Dog();
return DBInstance.getInstance().getDogDao().insert(dog);
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new SingleObserver() {
@Override
public void onSubscribe(Disposable d) {
}
//一次插入多条数据,返回的行id的集合
@Override
public void onSuccess(List o) {
for (Long data : o) {
LogUtils.i(“使用Single插入数据”, "onSuccess ==> " + data);
}
}
@Override
public void onError(Throwable e) {
LogUtils.i(“使用Single插入数据”, “onError”);
}
});
如果你不需要观察者回调,可以直接。