重拾Android之路(二十五) Realm数据库的使用

在Android开发中,我们经常会遇到这样的情况,就是需要将数据保存到手机上,通常我们有这么几种方法

  1. 使用SharedPreference,这里我们存储的数据,可以快速读取,快速写入,使用很方便,但是他也有一个缺点就是如果存储的内容非常多,或者我们想把读出来的数据进行一些格式化操作,使用这种的方法比较麻烦
  2. 通过字节流的方式将数据输出保存到本地存储空间中,根据手机性能读取速度可能较为缓慢,并且数据容易被破解,不安全
  3. 使用static final关键字修饰,但是这样的方式数据只能存在于我们的内存中,如果杀死进程,则数据消失
  4. 使用SQLite数据库

android中使用的SQLite是一种轻量级可扩展的关系型数据库,这个数据库已经被大部分的移动设备支持,可以说是移动端数据库当之无愧的霸主!至于他的一些优点缺点这里我们不一一赘述,有兴趣的可以自己去百度一下。其实关于Android数据库的使用有非常多的选择,像ORM,GreenDao等等,这里我给出一张图,来说明各个数据库之间的优缺点。
其实关于数据库之间的优缺点无非就是四种情况的内容,连续创建新数据的效率,一次性导入数据的效率,连续查询的效果,一次性批量查询的效率。

在图中我们会发现其实不同的数据库有自己不同的优点和缺点,其实没有真正的好坏之分,因为如果一个数据库框架不好,占内存很大,速度超级慢,那么也不会有这么多人去使用。关键是跟我们实际情况去使用相比较而言更加适合当前项目的数据库框架。
其实之前也有写过一篇关于SQLite的博客,但是那篇博客更加关注的是原生的方式创建数据库和数据库操作。那么这篇文章的重点是放在使用上,希望能够将Realm介绍的更加详细一些。写这篇博客的初衷是因为我在网上看到很多关于Realm数据库操作的文章都是很多年前的,而且使用的版本较低。秉着“自己动手丰衣足食”的思想,还是自己写一篇博客吧!

介绍

关于Realm的介绍,这里我就不再一一赘述,毕竟在Realm的官网中已经给大家介绍的很详细了,官网

使用

Realm数据库是一个典型的NoSql数据库,在Android开发中是以插件的形式使用的,具体的使用方式如下:

要求

  • Android Studio version 1.5.1 or higher
  • JDK version 7.0 or higher
  • A recent version of the Android SDK
  • Android API Level 9 or higher (Android 2.3 and above)

步骤

  1. 将类依赖路径加入到项目层级上的build.gradle中
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "io.realm:realm-gradle-plugin:5.12.0"
    }
}

如下图所示
重拾Android之路(二十五) Realm数据库的使用_第1张图片
2. 将Realm以插件的形式加入到app层级下的build.gradle中

apply plugin: 'realm-android'

如下图所示:
重拾Android之路(二十五) Realm数据库的使用_第2张图片
3. 重新编译项目即可。这样基本的集成操作已经完成了,是不是很简单?!

这里官网给出了一个提示,如果使用的版本是v0.88之前的版本,还需要加上一个./gradlew clean,这个我就没有尝试,我使用的是最新版本(2019年7月15日18:12:13)

例子

这里我先给出官方的例子,是在github上的,可以下载下来作为参考。

这个是我自己的小例子,有兴趣的可以看一下RealmDemo

除此之外,官网还给出了一些别的小例子。这里我们先一起来看一下,然后在根据这些小例子,去理解如何真正的使用Realm数据库到项目中。

// 这里我们定义一个Dog的类让他继承 RealmObject 
// 这个Dog类我们可以看做是数据库中的表,只要我们需要将这个类的实例写入数据库,就相当于在数据库中创建了一个表(奥,NoSQL)
public class Dog extends RealmObject {
    private String name;
    private int age;

    // 这里我们需要添加set/get方法
}
// 定义一个Person的类让他继承自RealmObject
// 这里我们会发现我们使用了注解的方式做了一些特殊操作
public class Person extends RealmObject {
	// 主键,唯一
    @PrimaryKey
    private long id;
    private String name;
    // 这里我们声明了一个一对多的关系,也就是说,一个人可以有多条狗
    private RealmList dogs; 

    // 这里我们需要添加set/get方法
}

// 这里我们实例化了一个小狗对象
Dog dog = new Dog();
dog.setName("旺财"); // 名字叫旺财
dog.setAge(1); // 年龄是1岁

// 这里我们需要将Realm进行初始化,一般只需要在Application中的onCreate()方法中初始化一次就可以了
Realm.init(context);

// 这里我们获取一个默认的Realm数据库对象(其实这里我们没有配置任何信息,全部使用的是默认方式,具体配置后面会介绍)
Realm realm = Realm.getDefaultInstance();

// 这是一个查询语句,查询数据库中所有小狗年龄小于2岁的小狗
// 这里我们会发现我们返回的是一个RealmResults,通过看源码我们会发现,他其实就是一个继承了List的列表集合,不同于List集合的地方的是缺少了List的一些方法,比如addAll()方法,我们可以通过转换的方式将它装换成List集合
final RealmResults puppies = realm.where(Dog.class).lessThan("age", 2).findAll();
puppies.size(); // => 此时返回的数据是0,因为还没有向里面填充数据

// 这里开始我们写入数据库的操作
// 写入数据库的操作是以事务的形式去执行的
realm.beginTransaction(); // 开启事务
final Dog managedDog = realm.copyToRealm(dog); // 我们需要将这个数据加入到数据库中
Person person = realm.createObject(Person.class); // 我们需要创建一个Person对象(这里我们可以理解为在数据库中先创建了一个空的Person对象,后面再赋值)
person.getDogs().add(managedDog);
realm.commitTransaction();

// 添加数据变化监听
puppies.addChangeListener(new OrderedRealmCollectionChangeListener>() {
    @Override
    public void onChange(RealmResults results, OrderedCollectionChangeSet changeSet) {
        // 查询的结果将根据具体变化进行更新
        changeSet.getInsertions(); // => [0] is added.
    }
});

// 上面的这些方式都是同步的方式去查询和插入的,我们可以通过异步的方式去执行,如下
realm.executeTransactionAsync(new Realm.Transaction() {
    @Override
    public void execute(Realm bgRealm) {
    	// 查询数据库中小狗的年龄是1岁的第一只小狗
    	// 这里我们发现我们使用到equalTo(key,value),其中key是在Dog类中的变量,而value是指需要匹配的对象
        Dog dog = bgRealm.where(Dog.class).equalTo("age", 1).findFirst();
        // 将小狗的年龄改为3岁
        dog.setAge(3);
    }
}, new Realm.Transaction.OnSuccess() {
    @Override
    public void onSuccess() {
        // 当数据的内容发生改变,或者操作成功的时候,该方法会被调用
        puppies.size(); // => 0 because there are no more puppies younger than 2 years old
        managedDog.getAge();   // => 3 the dogs age is updated
    }
});

因为是官网的例子,所以里面的英文我已经翻译成中文了,英语战五渣,翻译的不好,请多多包涵
需要注意的是,这里的代码是伪代码,直接将代码复制到Activity中运行是会报错的

不要打我,后面有关于具体的使用,这里也是官网的意思,先让大家对这个使用有一个浅显的印象

从这里开始真正的使用

配置

在刚才的代码中我们会发现有这么一句话

Realm.init(this);

这句话要求我们在Application初始化的时候执行一次即可,其实我们仅仅使用这样的方式去配置Realm数据库是远远不够的,如果你将上面的代码运行了,你就会发现,如果没有自定义Realm数据库配置的话,是无法实现数据库写入去读等操作的

先来看一个一般Realm配置代码

RealmConfiguration config = new RealmConfiguration.Builder()
  .name("myrealm.realm") // 设置数据库保存文件的名称
  .encryptionKey(getKey()) // 
  .schemaVersion(42) // 数据库版本
  .modules(new MySchemaModule()) // 
  .migration(new MyMigration()) // 数据库更新时的操作
  .build();

那么我们就可以使用如下的方式去设置我们的Realm数据配置信息

private void initRealmSQLite(){
        Realm.init(this);
        // 设置Realm配置信息
        RealmConfiguration config = new RealmConfiguration.Builder()
                .name(AppConfig.REALM_SQLITE_NAME)
                .rxFactory(new RealmObservableFactory())
                .schemaVersion(AppConfig.REALM_SQLITE_VERSION)
                .modules(new BundleRealmModules())
                .migration(new RealmMigration() {
                    @Override
                    public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {

                    }
                })
                .build();
        Realm.setDefaultConfiguration(config);
    }

这里我设置了数据库的名称,数据库的版本,数据更新时的操作,并且将上面的数据库配置信息写入到默认配置中,也就是调用了setDefaultConfiguration()方法
这里有一个地方是需要注意的,就是.modules(),对于他的描述,请看下图
在这里插入图片描述

除了上面的一些方法,还有一些别的方式可以供我们实现个性化定制

  1. readonly
    通过添加.readOnly()实现该数据库为只读数据库
  2. in-memory
    通过添加.inMemory()实现该数据库只会将数据保存在内存中,意思就是说如果我们将应用杀死,则数据库中的内容也会消失
  3. Dynamic Realm
    动态数据库,关于动态数据库,我了解的并不是非常多,只能从官方文档的只言片语中了解一个大概,这里先贴出官方的代码,后面有机会,再去深入学习
RealmConfiguration realmConfig = new RealmConfiguration.Builder().build();
DynamicRealm realm = DynamicRealm.getInstance(realmConfig);

// In a DynamicRealm all objects are DynamicRealmObjects
realm.beginTransaction();
DynamicRealmObject person = realm.createObject("Person");
realm.commitTransaction();

// All fields are accessed using strings
String name = person.getString("name");
int age = person.getInt("age");

// An underlying schema still exists, so accessing a field that does not exist
// will throw an exception
person.getString("I don't exist");

// Queries still work normally
RealmResults persons = realm.where("Person")
    .equalTo("name", "John")
    .findAll();
  1. rxFactory
    通过这个方法可以创建一个Rx Observables对象,他的实现对象是RealmObservableFactory
  2. initialData(Transaction)
    传入初始化数据,需要传递一个事务
  3. assetFile(String path)
    拷贝一个Realm文件作为配置文件到当前数据库(这个我是真没有用过,不过听字面意思还是很方便的)
  4. deleteRealmIfMigrationNeeded()
    在升级Realm数据库的时候,如果发生错误则会将数据库中的所有数据全部删除
  5. encryptionKey(byte[] key)
    设置加密和解密Real数据库的64位字节码密匙
  6. migration(RealmMigration)
    在数据库升级的时候,需要执行的操作,特别是当我们的数据库结构发生改变
  7. schemaVersion(long schemaVersion)
    设置Realm数据库的架构版本
  8. name(String)
    设置数据库的名称

那么所有的配置方法都介绍完了,根据自己的需要去调用相应的方法即可。

创建对象

数据类型

因为我们使用的Realm数据库是NoSQL,所以我们这里是不需要建表的。那么所有数据的存储都是以对象的形式去存储,所以这里我们需要创建对象(这个对象可以看做我们的表结构)
在创建对象之前,先说明一下,在Realm中我们可以使用以下数据类型

  1. 基本数据类型(其实就是在Java中可以直接声明的数据类型):
boolean, byte, short, int, long, float, double, String, Date , byte[]		
  1. 封装数据类型
Boolean, Byte, Short, Integer, Long, Float , Double

如果在当前对象中存在另外一个对象,像在person类中我们有声明Dog类型,那么就可以直接使用RealmList的方式。介绍的不详细?没关系,下面我们也会去使用的

注解

在声明对象的时候有一些注解可以帮助我们更方便的声明RealmObject对象

  1. @Required
    注解的该字段不能为空,必须要传递的对象。但是需要注意只有一下的几个类型的数据可以添加该注解Boolean, Byte, Short, Integer, Long, Float, Double, String, byte[] , Date
  2. @PrimaryKey
    主键,当然他对数据类型也有要求,要不就是String类型,要不然就是byte, short, int, long, Byte, Short, Integer, Long

在使用到主键的时候,有一些方法不能再使用,如createObject,但是有一些方法确实给我们提供了很大的便利copyToRealmOrUpdate , insertOrUpdate

  1. @Ignore
    忽视,使用这个注解表示该字段不要存在数据库中
    在官网中还给出了重命名注解的方法,这里我就不多说了。一般情况下,移动端的数据不会大量存在本地手机上,所以我需要的是简单的存储即可

说了这么多,到目前为止我们还没有向数据库中添加一条数据,那么下面我们就来看一下,如果新增一条数据
说道新增一调数据,我们有很多种方法,但是,大方向上分为两个方向,一种是同步操作,另外一种是异步操作

同步增

所谓的同步异步,这里就不在介绍了,简单的说,如果数据操作量不大,可以用同步,如果数据量非常大,可能会造成Android手机的ANR,那么还是用异步的操作比较好。

同步增也是分为三种方法(这里不抬杠,不接受反驳)

  1. 使用事务Transcation
Realm realm = Realm.getDefaultRealm();
realm.beginTransaction();
Dog dog = realm.createObject(Dog.class);
dog.setAge(Integer.valueOf(age));
dog.setName(name);
realm.commitTransaction();
  1. 也是使用事务的方式
Dog dog = new Dog();
dog.setName(name);
dog.setAge(Integer.valueOf(age));
realm.beginTransaction();
Dog dd = realm.copyToRealm(dog);
realm.commitTransaction();
realm.cancelTransaction();

这里除了上面的createObjectcopyToRealm两个方法外,还有一个是insert,但是这个方法的操作结果和前面的两种方法没有区别,也是插入数据,但是如果我们只关心向里面插入数据,而不关心数据结构,那么使用insert这个方法可以更快的完成操作。我觉得这个会在一些初始化数据的时候会用到

  1. 使用executeTransaction方法
            realm.executeTransaction(new Realm.Transaction() {
                @Override
                public void execute(Realm realm) {
                    Dog pupy = realm.createObject(Dog.class);
                    pupy.setName(name);
                    pupy.setAge(Integer.valueOf(age));
                }
            });

异步增

如果我们进行操作的数据量非常巨大,很有可能造成Android主线程的阻塞,从而造成ANR,那么Realm允许我们通过异步的方式增加数据到数据库中

            task = realm.executeTransactionAsync(new Realm.Transaction() {
                @Override
                public void execute(Realm realm) {
                    // 执行插入数据库操作
                    Dog dog = realm.createObject(Dog.class);
                    dog.setAge(Integer.valueOf(age));
                    dog.setName(name);
                }
            }, new Realm.Transaction.OnSuccess() {
                @Override
                public void onSuccess() {
                    // 插入数据库成功
                    Toast.makeText(OfficeDemoActivity.this, "数据插入成功", Toast.LENGTH_SHORT).show();
                }
            }, new Realm.Transaction.OnError() {
                @Override
                public void onError(Throwable error) {
                    // 插入数据库失败
                    Toast.makeText(OfficeDemoActivity.this, "数据插入失败", Toast.LENGTH_SHORT).show();
                }
            });
        }

里面的方法就不多说了,能从字面意思上看出来。那么需要注意的是,当我们使用异步的方式去执行的时候,返回的有一个RealmAnsyTask对象。当我们将当前的Activity页面关闭的时候,需要调用他的cancel方法,如下所示

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (task != null && !task.isCancelled()) {
            task.cancel();
        }
        if (realm != null && !realm.isClosed()) {
            realm.close();
        }
    }

设置主键增加

前面的例子中都没有设置主键,在增加的时候使用copyToRealmcreateObject两个方法,但是如果我们设置了主键,那么就可以使用另外两个方法copyToRealmOrUpdate insertOrUpdate

注意:如果我们的类中没有主键而使用了copyToRealmOrUpdate insertOrUpdate两个方法,则会抛出异常

这里我们给Dog类新增一个id字段,将这个字段设置为主键@PrimaryKey
然后我们使用下面的方式去插入数据

        String name = et_dog_name.getText().toString().trim();
        String age = et_dog_age.getText().toString().trim();
        if (TextUtils.isEmpty(name) || TextUtils.isEmpty(age)) {
            Toast.makeText(this, "请输入小狗的年龄和姓名", Toast.LENGTH_SHORT).show();
            return;
        }
        if (realm != null) {
            final Dog dog = new Dog();
            dog.setId(System.currentTimeMillis());
            dog.setName(name);
            dog.setAge(Integer.valueOf(age));
            realm.executeTransactionAsync(new Realm.Transaction() {
                @Override
                public void execute(Realm realm) {
                    realm.copyToRealmOrUpdate(dog);
                    // 或者另外一种
//                    realm.insertOrUpdate(dog);
                }
            }, new Realm.Transaction.OnSuccess() {
                @Override
                public void onSuccess() {
                    Toast.makeText(OfficeDemoActivity.this, "插入成功!", Toast.LENGTH_SHORT).show();
                }
            }, new Realm.Transaction.OnError() {
                @Override
                public void onError(Throwable error) {
                    Toast.makeText(OfficeDemoActivity.this, "插入失败!", Toast.LENGTH_SHORT).show();
                }
            });
        }

这里可能有一些同学会报错,这是因为我们改变了表结构,但是却没有实现相应的更新数据库操作,解决办法是先将当前应用删除,然后重新安装即可。具体的解决办法,后面在说道更新的时候会介绍

删除的操作需要先去查询,然后根据查询结果进行删除,具体操作如下:

		// 判断name和age是否为空,如果都为空,删除全部信息;如果name为空,age不为空,删除所有age相匹配的;如果name不为空,age为空,删错所有name相匹配的;如果name,age都不为空,则删除name,age相匹配的
        final String name = et_dog_name.getText().toString().trim();
        final String age = et_dog_age.getText().toString().trim();
        if (realm != null) {
            realm.executeTransactionAsync(new Realm.Transaction() {
                @Override
                public void execute(Realm realm) {
                    if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(age)) {
                        RealmResults dogs = realm.where(Dog.class).equalTo("name", name).and().equalTo("age", Integer.valueOf(age)).findAll();
                        dogs.deleteAllFromRealm();
                    } else if (TextUtils.isEmpty(name) && TextUtils.isEmpty(age)) {
                        RealmResults dogs = realm.where(Dog.class).findAll();
                        dogs.deleteAllFromRealm();
                    } else if (!TextUtils.isEmpty(name) && TextUtils.isEmpty(age)) {
                        RealmResults dogs = realm.where(Dog.class).equalTo("name", name).findAll();
                        dogs.deleteAllFromRealm();
                    } else if (TextUtils.isEmpty(name) && !TextUtils.isEmpty(age)) {
                        RealmResults dogs = realm.where(Dog.class).equalTo("age", age).findAll();
                        dogs.deleteAllFromRealm();
                    }
                }
            }, new Realm.Transaction.OnSuccess() {
                @Override
                public void onSuccess() {
                    Toast.makeText(OfficeDemoActivity.this, "删除成功!", Toast.LENGTH_SHORT).show();
                }
            }, new Realm.Transaction.OnError() {
                @Override
                public void onError(Throwable error) {
                    Toast.makeText(OfficeDemoActivity.this, "删除失败!", Toast.LENGTH_SHORT).show();
                }
            });
        }

修改数据也是需要先去查询,根据查询到的结果进行修改,具体操作如下

        final String name = et_dog_name.getText().toString().trim();
        final String age = et_dog_age.getText().toString().trim();
        if (TextUtils.isEmpty(name) || TextUtils.isEmpty(age)) {
            Toast.makeText(this, "请输入小狗的年龄和姓名", Toast.LENGTH_SHORT).show();
            return;
        }
        if (realm != null) {
            realm.executeTransactionAsync(new Realm.Transaction() {
                @Override
                public void execute(Realm realm) {
                    // 找到名字是输入框名字的小狗,把年龄修改成输入框的年龄
                    RealmResults results = realm.where(Dog.class).equalTo("name", name).findAll();
                    if (results.size() > 0) {
                        // 找到的有数据
                        Iterator it = results.iterator();
                        while (it.hasNext()) {
                            Dog puppy = (Dog) it.next();
                            puppy.setAge(Integer.valueOf(age));
                        }
                    }
                }
            }, new Realm.Transaction.OnSuccess() {
                @Override
                public void onSuccess() {
                    Toast.makeText(OfficeDemoActivity.this, "更新成功!", Toast.LENGTH_SHORT).show();
                }
            }, new Realm.Transaction.OnError() {
                @Override
                public void onError(Throwable error) {
                    Toast.makeText(OfficeDemoActivity.this, "更新失败!", Toast.LENGTH_SHORT).show();
                }
            });
        }

通常情况下,增,删,改的操作都比较简单,唯一比较难的就是查,查找的操作比较复杂,可能没有办法一次性写完,如果没有写完的,后面再补充

这里我们还是用小狗类,类的样式如下所示:

public class Dog extends RealmObject {
    @PrimaryKey
    private long id;

    private String name;

    private int age;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

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

然后我这里创建了7条数据,为了方便我们的查询
重拾Android之路(二十五) Realm数据库的使用_第3张图片

默认查询(查询全部)

代码:

        if (realm != null) {
            realm.executeTransactionAsync(new Realm.Transaction() {
                @Override
                public void execute(Realm realm) {
                    RealmResults dogs = realm.where(Dog.class).findAll();
                    if (dogs.size() > 0) {
                        // 将最终的数据打印出来
                        String log = "";
                        Iterator it = dogs.iterator();
                        while (it.hasNext()) {
                            Dog dog = (Dog) it.next();
                            log += dog.getId() + ";" + dog.getName() + ";" + dog.getAge() + "\n";
                        }
                        tv_show_dog_info.setText(log);
                    }
                }
            }, new Realm.Transaction.OnSuccess() {
                @Override
                public void onSuccess() {
                    Toast.makeText(OfficeDemoActivity.this, "查询成功!", Toast.LENGTH_SHORT).show();
                }
            }, new Realm.Transaction.OnError() {
                @Override
                public void onError(Throwable error) {
                    Toast.makeText(OfficeDemoActivity.this, "查询成功!", Toast.LENGTH_SHORT).show();
                }
            });
        }

运行结果:
重拾Android之路(二十五) Realm数据库的使用_第4张图片

按条件查询

查询年龄小于3的小狗

  1. lessThan(String field ,double value)
    private void queryLess3Puppy(){
        if (realm != null){
            realm.executeTransactionAsync(new Realm.Transaction() {
                @Override
                public void execute(Realm realm) {
                    RealmResults dogs = realm.where(Dog.class).lessThan("age",3).findAll();
                    if (dogs.size() > 0) {
                        // 将最终的数据打印出来
                        String log = "";
                        Iterator it = dogs.iterator();
                        while (it.hasNext()) {
                            Dog dog = (Dog) it.next();
                            log += dog.getId() + ";" + dog.getName() + ";" + dog.getAge() + "\n";
                        }
                        tv_show_dog_info.setText(log);
                    }
                }
            }, new Realm.Transaction.OnSuccess() {
                @Override
                public void onSuccess() {
                    Toast.makeText(OfficeDemoActivity.this, "查询成功!", Toast.LENGTH_SHORT).show();
                }
            }, new Realm.Transaction.OnError() {
                @Override
                public void onError(Throwable error) {
                    Toast.makeText(OfficeDemoActivity.this, "查询成功!", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }

查询结果:
重拾Android之路(二十五) Realm数据库的使用_第5张图片

后面的还有很多种判断条件,这里我们简单的写

  1. equal(String field,Object obj)
    // 查找名字=aaa 的小狗
    private void queryEqualPuppy(){
        if (realm != null){
            realm.executeTransactionAsync(new Realm.Transaction() {
                @Override
                public void execute(Realm realm) {
                    RealmResults dogs = realm.where(Dog.class).equalTo("name","aaa").findAll();
                    if (dogs.size() > 0) {
                        // 将最终的数据打印出来
                        String log = "";
                        Iterator it = dogs.iterator();
                        while (it.hasNext()) {
                            Dog dog = (Dog) it.next();
                            log += dog.getId() + ";" + dog.getName() + ";" + dog.getAge() + "\n";
                        }
                        tv_show_dog_info.setText(log);
                    }
                }
            }, new Realm.Transaction.OnSuccess() {
                @Override
                public void onSuccess() {
                    Toast.makeText(OfficeDemoActivity.this, "查询成功!", Toast.LENGTH_SHORT).show();
                }
            }, new Realm.Transaction.OnError() {
                @Override
                public void onError(Throwable error) {
                    Toast.makeText(OfficeDemoActivity.this, "查询成功!", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }
  1. and()
    多条件查询
  2. between(String field,double value1,double value2)
    在两个数值之间查询,其中可以试试double,int,long等类型
  3. contains(String field,String value)
    包含字符串查询
  4. count()
    计数查询
  5. greaterThan(String field ,double value)
    大于某个数值
  6. in(String field,String[] values)
    存在values中
  7. like(String field,String value)
  8. max(String field)
  9. maximumDate(String field)
  10. notEqualTo(String fieldName, Double value)
  11. or()
  12. sort(String fieldName, Sort sortOrder)
    查询结果排序,这里排序分为两种ASCENDING (升序),DESCENDING (降序)
  13. sum(String fieldName)
  14. average(String fieldName)
  15. beginsWith(String fieldName, String value)
  16. beginGroup()
    表示group,结束的时候要使用endGroup()

简单的介绍这么多,其实里面的具体含义,只要学过数据库的都能理解,那么除了这一些之后,还有一些比如多表查询,下面来看这么几个例子,这几个例子是官网的例子,我们直接来分析以下即可

// 创建一个Dog类
public class Dog extends RealmObject {
    private int age;
    // getters & setters ...
}
// 创建一个Person类
public class Person extends RealmObject {
    private int age;
    private RealmList dogs;
    // getters & setters ...
}
// 查询年龄在13至20岁之间并且名字是John的第一个对象
RealmResults teenagers = realm.where(Person.class).between("age", 13, 20).findAll();
Person firstJohn = teenagers.where().equalTo("name", "John").findFirst();
// 查询年龄在13至20岁之间并且小狗的名字是puppy的第一个人物对象
RealmResults teensWithPups = realm.where(Person.class).between("age", 13, 20).equalTo("dogs.name", "puppy").findAll();

上面的代码依然是伪代码。

在来看一个多表查询的例子

public class Person extends RealmObject {
  private String id;
  private String name;
  private RealmList dogs;
  // getters and setters
}

public class Dog extends RealmObject {
  private String id;
  private String name;
  private String color;
  // getters and setters
}

这里我们会发现,每一个Person中都包括一个RealmList的对象,其实就是说一个人可以拥有多条小狗,那么他们的关系如图所示
重拾Android之路(二十五) Realm数据库的使用_第6张图片
查询所有小狗的毛色是棕色的Person对象

RealmResults persons = realm.where(Person.class)
                                .equalTo("dogs.color", "Brown")
                                .findAll();

再来看两个:

// r1 => [U1,U2]
RealmResults r1 = realm.where(Person.class)
                             .equalTo("dogs.name", "Fluffy")
                             .equalTo("dogs.color", "Brown")
                             .findAll();

// r2 => [U2]
RealmResults r2 = realm.where(Person.class)
                             .equalTo("dogs.name", "Fluffy")
                             .findAll()
                             .where()
                             .equalTo("dogs.color", "Brown")
                             .findAll()
                             .where()
                             .equalTo("dogs.color", "Yellow")
                             .findAll();

这两个查询语句也是多表查询的,那么我们来看一下官方给出的解释

  1. The first query reads, “find all Persons who have dogs named ‘Fluffy’ and have dogs whose color is ‘Brown.’”
  2. The second query reads, “find all Persons who have dogs named ‘Fluffy.’ Within that result set, find all Persons who have dogs whose color is ‘Brown.’ Then, within that result set, find all Persons who have dogs whose color is ‘Yellow.’”

其中第二个查询是最容易出错的,先自己看一下是什么查询,然后通过运行代码,对比一下是否和自己想的一致。

关于查询的内容真的很多,没办法一一介绍,先到此为止。

不过有一个还是需要记录一下的,那就是异步查询。我们已经知道了异步操作,那么异步查询是什么样的呢?在RealmQuery的方法中我们会发现有这么一个方法findAllAsync()

在官方的描述中,他们说使用Realm进行查询是非常迅速的(这个其实是跟手机的性能有一定关系的),所以大部分情况下就算将查询放在UI线程中,也是没有问题的(千万别相信官方,最好还是异步比较好),但是为了以防万一,官网还是给出了异步查询的操作(嗯,还是很人性化的)。

    private OrderedRealmCollectionChangeListener> listener = new OrderedRealmCollectionChangeListener>() {
        @Override
        public void onChange(RealmResults dogs, OrderedCollectionChangeSet changeSet) {
            // 数据查询到了,更新数据

        }
    };

    private void queryAllPuppyAsync() {
        if (realm != null) {
            realm.executeTransactionAsync(new Realm.Transaction() {
                @Override
                public void execute(Realm realm) {
                    RealmResults dogs = realm.where(Dog.class).findAllAsync();
                    dogs.addChangeListener(listener);
                }
            }, new Realm.Transaction.OnSuccess() {
                @Override
                public void onSuccess() {
                    Toast.makeText(OfficeDemoActivity.this, "查询成功!", Toast.LENGTH_SHORT).show();
                }
            }, new Realm.Transaction.OnError() {
                @Override
                public void onError(Throwable error) {
                    Toast.makeText(OfficeDemoActivity.this, "查询成功!", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }

在使用完成之后,特别是在Activity或者Fragment被销毁的时候需要将监听Listener移除

public void onStop () {
    result.removeChangeListener(callback); // remove a particular listener
    // or
    result.removeAllChangeListeners(); // remove all registered listeners
}

除了添加监听Listener的方式,我们还可以通过一些方法,比如isLoaded()load()
其中isLoaded()表示数据是否已经查询完毕;load()表示立即获取查询的数据,不过这里需要注意,我们的立即获取不是通过返回,而是通过dogs.load();这样的方式,那么dogs中就有了查询的数据。

更新数据库

在我们的开发过程中,我们肯定会遇到需要升级数据库的情况,所谓的升级数据库,其实就是添加,删除一些字段,而且还要保留其他字段的信息不能错乱。使用Realm更新数据库相比较而言比较简单。

在我们的开头处我们有对Realm进行一些基本的配置,其中有一个方法叫做.migration()。当时因为我们还没有真正的了解Realm,所以对于这方面的内容我们直接跳过了。那么下面我们来说一下如何配置这个内容。
具体配置如下:

public class MyMigration implements RealmMigration {
    @Override
    public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
        // 获取一个realm的动态Schema对象
        RealmSchema schema = realm.getSchema();
        // 这里面我们需要根据当前的数据库版本进行不同的操作
        // 如果旧版本是0,则我们需要创建Person的表,并且创建Field name和age
        if (oldVersion == 0) {
            schema.create("Person")
                    .addField("name", String.class)
                    .addField("age", int.class);
            oldVersion++;
        }
        // 如果旧版本是1
        // 那我们我们需要获取到person表格,添加一个字段id,并且是long类型,而且是主键
        // 创建RealmObject类型,字段是favoriteDog,他的类型是Dog(我们是通过schema.get("Dog")拿到表结构对象)
        // 创建一个RealmList的字段,名称叫做dogs,他的类型时Dog
        if (oldVersion == 1) {
            schema.get("Person")
                    .addField("id", long.class, FieldAttribute.PRIMARY_KEY)
                    .addRealmObjectField("favoriteDog", schema.get("Dog"))
                    .addRealmListField("dogs", schema.get("Dog"));
            oldVersion++;
        }
    }
}

除了在代码中显示的方法外,还有一个别的方式供我们使用renameField(String currentFieldName, String newFieldName),removeField(String fieldName)
使用也非常简单

public class App extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        Realm.init(this);
        RealmConfiguration config = new RealmConfiguration.Builder()
                .name("office.realm")
                .schemaVersion(2)
                .migration(new MyMigration())
                .build();
        Realm.setDefaultConfiguration(config);
    }
}

以后每次更新版本,只需要添加上相应的判断即可

通知

这个通知并不是Android的状态栏通知,而是Realm数据库的通知,他的意思是,我们可以给数据库添加一个监听事件,只要数据库又发生变化,我们就可以作出相应的动作。
这个我就不再多说了,直接看官方的文档即可 通知

##########################################华丽的分割线##########################################################
自己写的小例子,大家可以参考一下,如果有什么问题,可以给我留言,我们一起讨论。例子的根目录中有Realm数据库API文档

参考资料

realm.io
realm数据库使用指南

你可能感兴趣的:(重拾Android之路)