一、入门篇
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支持定义多种类型的字段,如:
boolean
、byte
、short
、int
、long
、double
、String
、Date
和byte[]
类型。其中,byte
、short
、int
、long
都将会被应设成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数据库同样能够给数据表设置主键,支持设置主键的类型包括:
byte
、short
、int
、long
、Byte
、Short
、Integer
、Long
以及String
。设置主键的方式是在对应的字段加注解@PrimaryKey
,但是不支持联合主键。值得注意的是,如果将String
类型的属性设置为了主键,那么将会隐示地为其添加@Index
注解。
public class Dog extends RealmObject {
@PrimaryKey
private String name;
private int age;
...
}
关于设置主键有以下几点需要注意:
- 如果使用
copyToRealmOrUpdate
或insertOrUpdate
方法来创建RealmObject
对象的话,则必须为其设置主键,否则将会跑出异常;- 如果设置了主键,那么查询速度将会稍微快一点,但是插入和更新操作将会稍微变慢,性能的变化取决于数据的规模;
- 由于
Realm.createObject
方法返回的是一个所有属性被赋予默认值的对象,因此如果有具备有主键的RealmObject
通过这种方式创建后未被赋值,那么再次调用这个方法来创建具备主键的RealmObject
将会产生冲突。解决这个问题的方式是先创建一个RealmObject
对象,然后给其字段赋值,再调用copyToRealm
或insert
方法加入数据库;- 被设置为主键的
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
快速、高效,也能使你的代码变得更加简洁和更具有交互性。更重要的是,如果你的Activity
和Fragment
依赖于某个特定的RealmObject
或者RealmResults
实例,那么更新UI
之前无需担心数据刷新或者重新拉取数据。
(b)自定义RealmObject
对象
RealmObject
与JavaBean
类似,我们也可以直接继承RealmObject
。RealmObject
的属性权限可以是public
、protected
或者private
。如果声明为了public
权限,那么无需定义getter
、setter
方法,可以直接给属性赋值。
// 定义 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
类中我们定义了name
和age
属性,除此之外还可以添加其他自定义函数。
(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
来执行事务,那么产生异常后的后续操作都将会自动执行,无需人工参与。
得益于
Realm
的MVCC
架构,即使开启了事务,我们仍然能够同时开启读操作。当你提交了一个写事务后,所有其他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
方法很相似,但是前者速度更快,因为它不用返回被插入对象的副本。如果想要插入多个对象,推荐使用insert
或insertOrUpdate
方法(所有的插入操作都需要放到事务块中)。
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) {
// 写入失败
}
});
以上代码中的
onSuccess
和onError
回调参数是可选的,如果传入了相应的回调函数,那么出发之后将会执行相应回调函数。需要注意的是回调函数在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
过滤函数支持字符串
、二进制数据
、数值类型
和日期类型
。其中数值类型
和日期类型
还额外支持以下几种过滤操作:
between
graterThan
lessThan
greaterThanOrEqualTo
lessThanOrEqualTo
字符串类型
额外支持以下几种过滤操作:
contains
beginsWith
endsWith
like
其中,
字符串类型
有额外的第三个参数来控制是否大小写敏感:Case.INSENSITIVE
和Case.SENSITIVE
,默认是Case.SENSITIVE
。