Realm学习总结

2019-08-19  本文已影响0人  看不见的手_

一、入门篇

1.如何安装Realm

第一步:在Project级别的 build.gradle 文件中添加依赖:

buildscript {
    ...
    dependencies {
        ...
        classpath "io.realm:realm-gradle-plugin:5.13.0"
    }
    ...
}

第二步:在app级别的build.gradle文件中添加下面这一行:

apply plugin: 'realm-android'

第三步: 同步( sync )项目配置,Android Studio运行结束后即可完成安装。

2.如何定义Realm对象

定义Realm对象的方法非常简单,只需定义一个Model,然后继承RealmObject即可。

//定义Dog类,继承RealmObject
public class Dog extends RealmObject {
    private String name;
    private int age;
    
    //注意:不能定义构造函数,否则编译不通过。
    //如若想定义有参构造函数,则必须要有一个public修饰的无参构造函数
    //public Dog(String name, int age) {
    //    this.name = name;
    //    this.age = age;
    //}
    
    //自动生成的标准 getter\setter方法 
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

3.如何初始化Realm数据库

//Realm数据库的默认初始化方式:
Realm.init(context); //初始化数据库
Realm realm = Realm.getDefaultInstance();//获取默认配置的实例

Realm数据库在使用前需要被初始化,因为需要向init(Context context)函数传递context,因此我们可以在Application中进行初始化操作:

public class MyApplication extends Application {
    @Override
    public void onCreate() {
        super.onCreate();
        Realm.init(this);
    }
}

如果在Application类中初始化,那么我们还需对AndroidManifest.xml做如下修改:

<application
    android:name=".MyApplication"
    ...
</application>

4.如何使用Realm数据库

//向realm数据库中存储Dog对象
realm.beginTransaction();
Dog dog = realm.createObject(Dog.class);
dog.setAge(2);
dog.setName("金毛");
realm.commitTransaction();

5.如何查看Realm数据库

默认Realm数据库文件名为default.realm,我们可以使用官方推荐的Realm Studio软件查看、编辑数据库。

二、基础篇

1.Realm Model

(a)属性类型

上面简单介绍过Realm Model的创建方式,这里不再赘述。Realm Model支持定义多种类型的字段,如:booleanbyteshortintlongdoubleStringDatebyte[]类型。其中,byteshortintlong都将会被应设成long类型。同时,Realm还支持ReamlObject派生的子类和ReamlObject数组。
此外,Realm Model还支持以上几种类型对应的包装类型,并且默认值为null

(b) 非空属性

Realm Model通过注解@required来声明属性值是否非空。这里需要注意的是非空是针对默认值为null的属性而言,也就是说只有上述几种基本类型的包装类型才能被声明为@required

public class Dog extends RealmObject {
    @Required //正确。String类型的属性默认值为null,因此可以声明为required
    private String name;
    @Required //错误。int类型的默认值为0,声明为required毫无意义
    private int age;
    ......
}

(c) 主键

Realm数据库同样能够给数据表设置主键,支持设置主键的类型包括:byteshortintlongByteShortIntegerLong以及String。设置主键的方式是在对应的字段加注解@PrimaryKey,但是不支持联合主键。值得注意的是,如果将String类型的属性设置为了主键,那么将会隐示地为其添加@Index注解。

public class Dog extends RealmObject {

    @PrimaryKey
    private String name;
    
    private int age;
    
    ...
}

关于设置主键有以下几点需要注意:

  1. 如果使用copyToRealmOrUpdateinsertOrUpdate方法来创建RealmObject对象的话,则必须为其设置主键,否则将会跑出异常;
  2. 如果设置了主键,那么查询速度将会稍微快一点,但是插入和更新操作将会稍微变慢,性能的变化取决于数据的规模;
  3. 由于Realm.createObject方法返回的是一个所有属性被赋予默认值的对象,因此如果有具备有主键的RealmObject通过这种方式创建后未被赋值,那么再次调用这个方法来创建具备主键的RealmObject将会产生冲突。解决这个问题的方式是先创建一个RealmObject对象,然后给其字段赋值,再调用copyToRealminsert方法加入数据库;
  4. 被设置为主键的String类型或包装类型字段可以被设置为null,除非同时设置@PrimatyKey@Required注解。

2.使用 Realm Object

(a) 对象自动更新

RealmObject是实时、自动将视图更新到基础数据中的,并且对象的更新能够立即作用到(已有的)查询结果中。

Realm realm = Realm.getDefaultInstance();

realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        Dog dog1 = realm.createObject(Dog.class);
        dog1.setName("牧羊犬");
        dog1.setAge(2); // age = 2
    }
});

Dog dog2 = realm.where(Dog.class).equalTo("age",2).findFirst();
Log.i("Age","dog age is:"+dog2.getAge()); // 输出结果: 2

realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        Dog dog3 = realm.where(Dog.class).equalTo("age",2).findFirst();
        dog3.setAge(4); // age = 4
    }
});

Log.i("Age","Now dog age is:"+dog2.getAge()); // 输出结果: 4(已经不是2了哦)

//以上代码段经运行后结果:
08-15 15:57:21.460 21792-21792/com.chivan.realmdemo I/Age: dog age is:2
08-15 15:57:21.460 21792-21792/com.chivan.realmdemo I/Age: Now dog age is:4

这中特性不仅能够使得Realm快速、高效,也能使你的代码变得更加简洁和更具有交互性。更重要的是,如果你的ActivityFragment依赖于某个特定的RealmObject或者RealmResults实例,那么更新UI之前无需担心数据刷新或者重新拉取数据。

(b)自定义RealmObject对象

RealmObjectJavaBean类似,我们也可以直接继承RealmObjectRealmObject的属性权限可以是publicprotected或者private。如果声明为了public权限,那么无需定义gettersetter方法,可以直接给属性赋值。

// 定义 Cat 类,继承自 RealmObject
public class Cat extends RealmObject {
    public String name;
    public String age;

//    注意:name、age均是public权限,因此无需getter、setter方法
//    public String getName() {
//        return name;
//    }
//
//    public void setName(String name) {
//        this.name = name;
//    }
//
//    public String getAge() {
//        return age;
//    }
//
//    public void setAge(String age) {
//        this.age = age;
//    }
}

// 创建 cat 对象,并给对象属性赋值
realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        Cat cat = realm.createObject(Cat.class);
        cat.name = "波斯猫";
        cat.age = 2;
    }
});

Cat类中我们定义了nameage属性,除此之外还可以添加其他自定义函数。

(c)RealmModel接口

除了继承RealmObject外,还可以实现RealmModel接口,并配合@RealmClass注解。这种情况下,所有RealmObject对象的属性只能通过静态方法来调用。

// 继承 RealmObject
cat.deleteFromRealm();
cat.isValid();

//实现 RealmModel 接口
RealmObject.deleteFromRealm(cat);
RealmObject.isValid(cat);

3.插入操作

与读操作不同的是,Realm的写操作必须包含在事务(transaction)中。在事务的结尾,我们可以选择提交事务或者取消事务。事务一旦被提交,那么事务作出的所有改变都将会被写入磁盘。事务被取消后,那么所有的改变都将失效。换句话说,同一个事务中的操作要么全都做、要么就全都不错,这保证了数据的一致性和线程安全。

realm.beginTransaction();//开始事务
// do some things...
realm.commitTransaction();//提交事务

realm.beginTransaction();//开始事务
// do some things...
realm.cancelTransaction();//取消事务

需要注意的是Realm的写操作会互相死锁,因此如果你在UI线程和后台线程中同时开启了写入事务,那么将可能会导致ANR错误。为了避免这种事件发生,官方建议 在异步事务(async transaction)处理写入事务。

如果在事务中发生了异常,那么事务中所有的改变都将消失,但是Realm本身不受影响。如果捕捉了事务中的异常,并且程序将会继续执行下去,那么需要取消产生异常的事务。有个省事的做法就是使用excuteTransaction来执行事务,那么产生异常后的后续操作都将会自动执行,无需人工参与。

得益于RealmMVCC架构,即使开启了事务,我们仍然能够同时开启读操作。当你提交了一个写事务后,所有其他Realm实例都会收到通知并自动更新。

(a)创建对象

在事务中用createObject方法创建对象:

realm.beginTransaction(); //开启事务
Dog dog = realm.createObject(Dog.class); //创建 Dog 对象
dog.setAge(2);
dog.setName("金毛");
realm.commitTransaction(); //提交事务

如果是首先创建了一个对象,然后使用copyToRealm将对象写入到Realm数据库,那么你需要将这个操作放入事务中提交。Realm支持无限个自定义有参构造函数来创建对象,但前提是必须至少要有一个public修饰的无参构造函数。否则将会报错:Class "Cat" must declare a public constructor with no arguments if it contains custom constructors.


Cat cat = new Cat("小奶猫"); // 有参构造函数
cat.setAge(5);
realm.beginTransaction();
Cat catRealm = realm.copyToRealm(cat); //将对象拷贝到 Realm 数据库中
realm.commitTransaction();

这里需要强调的是只有在返回的对象(上述代码中的catRealm对象)上的操作会生效,而原始的对象(上述代码中的cat对象)上的操作将不会同步到Realm数据库中。如果你仅仅是向Realm数据库中插入一个对象,而不会立马对对象做出修改的话,那就尽可能用insert方法。insert方法的实现方式和copyToRealm方法很相似,但是前者速度更快,因为它不用返回被插入对象的副本。如果想要插入多个对象,推荐使用insertinsertOrUpdate方法(所有的插入操作都需要放到事务块中)。

List<Cat> cats = Arrays.asList(new Cat("金毛"), new Cat("小奶猫"));
realm.beginTransaction();
realm.insert(cats); //插入多个Cat对象
realm.commitTransaction();

(b)异步事务

前文讲到如果在UI线程和后台线程中同时开启了写入事务,那么可能会导致ANR。因此,为了避免阻塞UI线程,我们可以采用异步事务来避免这个问题。

Realm realm = Realm.getDefaultInstance();
realm.executeTransactionAsync(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        Cat cat = realm.createObject(Cat.class);
        cat.setName("小黄猫");
        cat.setAge(3);
    }
}, new Realm.Transaction.OnSuccess() {
    @Override
    public void onSuccess() {
        // 写入成功
    }
}, new Realm.Transaction.OnError() {
    @Override
    public void onError(Throwable error) {
        // 写入失败
    }
});

以上代码中的onSuccessonError回调参数是可选的,如果传入了相应的回调函数,那么出发之后将会执行相应回调函数。需要注意的是回调函数在Lopper线程中执行。

(c)对象批量更新

如果对对象某个字段进行批量更新,最高效的方式是先查询到所有符合条件的对象,然后调用RealmResults.setXXX()方法。

//批量更新对象属性值
realm.executeTransaction(new Realm.Transaction() {
    @Override
    public void execute(Realm realm) {
        RealmResults<Cat> cats = realm.where(Cat.class).equalTo("name","小奶猫").findAll();//查询所有name = "小奶猫"的对象 
        cats.setValue("age",3); //将符合条件到的所有Cat对象age属性值设置为3
    }
});

其中setXXX()函数的第一个参数是对象字段名,第二个参数是字段值。

4.查询操作

Realm的查询引擎使用Fluent接口来构造多子句查询。所有提取(包括查询)在Realm中都是懒加载模式,并且永远不会复制数据。例如查询所有name="小奶猫"并且age = 2的对象,我们可以按如下方式来查询:

RealmResults<Cat> cats2 = realm.where(Cat.class)
        .equalTo("name","小奶猫")
        .and()
        .equalTo("age",2)
        .findAll();

(a)结果过滤

where方法通过指定被查询的model来开始RealmQuery。过滤条件使用谓词作为名称的函数指定,其中大多数函数的名称不言自明(例如,equalTo),过滤函数将字段名称作为其第一个参数。

使用in函数从查询结果集中匹配相应的字段,例如要查找name小奶猫金毛或者小花猫的对象,我们可以使用以下语法:

RealmResults<Cat> cats3 = realm.where(Cat.class)
        .in("name", new String[]{"小奶猫","小花猫","金毛"})
        .findAll();

in过滤函数支持字符串二进制数据数值类型日期类型。其中数值类型日期类型还额外支持以下几种过滤操作:

字符串类型额外支持以下几种过滤操作:

其中,字符串类型有额外的第三个参数来控制是否大小写敏感:Case.INSENSITIVECase.SENSITIVE,默认是Case.SENSITIVE

上一篇 下一篇

猜你喜欢

热点阅读