一临扮、入門篇
1.如何安裝Realm
第一步:在
Project
級別的build.gradle
文件中添加依賴:
buildscript {
...
dependencies {
...
classpath "io.realm:realm-gradle-plugin:5.13.0"
}
...
}
第二步:在
app
級別的build.gradle
文件中添加下面這一行:
apply plugin: 'realm-android'
第三步: 同步(
sync
)項(xiàng)目配置,Android Studio運(yùn)行結(jié)束后即可完成安裝。
2.如何定義Realm
對象
定義
Realm
對象的方法非常簡單,只需定義一個(gè)Model
枕稀,然后繼承RealmObject
即可。
//定義Dog類,繼承RealmObject
public class Dog extends RealmObject {
private String name;
private int age;
//注意:不能定義構(gòu)造函數(shù)近忙,否則編譯不通過。
//如若想定義有參構(gòu)造函數(shù)智润,則必須要有一個(gè)public修飾的無參構(gòu)造函數(shù)
//public Dog(String name, int age) {
// this.name = name;
// this.age = age;
//}
//自動生成的標(biāo)準(zhǔn) 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
數(shù)據(jù)庫
//Realm數(shù)據(jù)庫的默認(rèn)初始化方式:
Realm.init(context); //初始化數(shù)據(jù)庫
Realm realm = Realm.getDefaultInstance();//獲取默認(rèn)配置的實(shí)例
Realm
數(shù)據(jù)庫在使用前需要被初始化及舍,因?yàn)樾枰?code>init(Context context)函數(shù)傳遞context
,因此我們可以在Application
中進(jìn)行初始化操作:
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Realm.init(this);
}
}
如果在Application
類中初始化,那么我們還需對AndroidManifest.xml
做如下修改:
<application
android:name=".MyApplication"
...
</application>
4.如何使用Realm
數(shù)據(jù)庫
//向realm數(shù)據(jù)庫中存儲Dog對象
realm.beginTransaction();
Dog dog = realm.createObject(Dog.class);
dog.setAge(2);
dog.setName("金毛");
realm.commitTransaction();
5.如何查看Realm
數(shù)據(jù)庫
默認(rèn)
Realm
數(shù)據(jù)庫文件名為default.realm
,我們可以使用官方推薦的Realm Studio
軟件查看窟绷、編輯數(shù)據(jù)庫锯玛。
二、基礎(chǔ)篇
1.Realm Model
(a)屬性類型
上面簡單介紹過Realm Model的創(chuàng)建方式兼蜈,這里不再贅述攘残。Realm Model支持定義多種類型的字段,如:
boolean
为狸、byte
歼郭、short
、int
辐棒、long
病曾、double
、String
漾根、Date
和byte[]
類型泰涂。其中,byte
辐怕、short
逼蒙、int
、long
都將會被應(yīng)設(shè)成long
類型寄疏。同時(shí)是牢,Realm還支持ReamlObject
派生的子類和ReamlObject
數(shù)組顶考。
此外,Realm Model還支持以上幾種類型對應(yīng)的包裝類型,并且默認(rèn)值為null
妖泄。
(b) 非空屬性
Realm Model通過注解
@required
來聲明屬性值是否非空驹沿。這里需要注意的是非空
是針對默認(rèn)值為null
的屬性而言,也就是說只有上述幾種基本類型的包裝類型才能被聲明為@required
public class Dog extends RealmObject {
@Required //正確蹈胡。String類型的屬性默認(rèn)值為null渊季,因此可以聲明為required
private String name;
@Required //錯(cuò)誤。int類型的默認(rèn)值為0罚渐,聲明為required毫無意義
private int age;
......
}
(c) 主鍵
Realm數(shù)據(jù)庫同樣能夠給數(shù)據(jù)表設(shè)置主鍵却汉,支持設(shè)置主鍵的類型包括:
byte
、short
荷并、int
合砂、long
、Byte
源织、Short
翩伪、Integer
、Long
以及String
谈息。設(shè)置主鍵的方式是在對應(yīng)的字段加注解@PrimaryKey
,但是不支持聯(lián)合主鍵缘屹。值得注意的是,如果將String
類型的屬性設(shè)置為了主鍵侠仇,那么將會隱示地為其添加@Index
注解轻姿。
public class Dog extends RealmObject {
@PrimaryKey
private String name;
private int age;
...
}
關(guān)于設(shè)置主鍵有以下幾點(diǎn)需要注意:
- 如果使用
copyToRealmOrUpdate
或insertOrUpdate
方法來創(chuàng)建RealmObject
對象的話,則必須為其設(shè)置主鍵逻炊,否則將會跑出異常互亮;- 如果設(shè)置了主鍵,那么查詢速度將會稍微快一點(diǎn)余素,但是插入和更新操作將會稍微變慢豹休,性能的變化取決于數(shù)據(jù)的規(guī)模;
- 由于
Realm.createObject
方法返回的是一個(gè)所有屬性被賦予默認(rèn)值的對象溺森,因此如果有具備有主鍵的RealmObject
通過這種方式創(chuàng)建后未被賦值慕爬,那么再次調(diào)用這個(gè)方法來創(chuàng)建具備主鍵的RealmObject
將會產(chǎn)生沖突窑眯。解決這個(gè)問題的方式是先創(chuàng)建一個(gè)RealmObject
對象屏积,然后給其字段賦值,再調(diào)用copyToRealm
或insert
方法加入數(shù)據(jù)庫磅甩;- 被設(shè)置為主鍵的
String
類型或包裝類型字段可以被設(shè)置為null
炊林,除非同時(shí)設(shè)置@PrimatyKey
和@Required
注解。
2.使用 Realm Object
(a) 對象自動更新
RealmObject
是實(shí)時(shí)卷要、自動將視圖更新到基礎(chǔ)數(shù)據(jù)中的渣聚,并且對象的更新能夠立即作用到(已有的)查詢結(jié)果中独榴。
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()); // 輸出結(jié)果: 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()); // 輸出結(jié)果: 4(已經(jīng)不是2了哦)
//以上代碼段經(jīng)運(yùn)行后結(jié)果:
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
依賴于某個(gè)特定的RealmObject
或者RealmResults
實(shí)例隘道,那么更新UI
之前無需擔(dān)心數(shù)據(jù)刷新或者重新拉取數(shù)據(jù)症歇。
(b)自定義RealmObject
對象
RealmObject
與JavaBean
類似,我們也可以直接繼承RealmObject
谭梗。RealmObject
的屬性權(quán)限可以是public
忘晤、protected
或者private
。如果聲明為了public
權(quán)限激捏,那么無需定義getter
设塔、setter
方法,可以直接給屬性賦值远舅。
// 定義 Cat 類闰蛔,繼承自 RealmObject
public class Cat extends RealmObject {
public String name;
public String age;
// 注意:name、age均是public權(quán)限图柏,因此無需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;
// }
}
// 創(chuàng)建 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
屬性爆办,除此之外還可以添加其他自定義函數(shù)难咕。
(c)RealmModel
接口
除了繼承
RealmObject
外,還可以實(shí)現(xiàn)RealmModel
接口距辆,并配合@RealmClass
注解余佃。這種情況下,所有RealmObject
對象的屬性只能通過靜態(tài)方法來調(diào)用跨算。
// 繼承 RealmObject
cat.deleteFromRealm();
cat.isValid();
//實(shí)現(xiàn) RealmModel 接口
RealmObject.deleteFromRealm(cat);
RealmObject.isValid(cat);
3.插入操作
與讀操作不同的是爆土,Realm的寫操作必須包含在事務(wù)(transaction
)中。在事務(wù)的結(jié)尾诸蚕,我們可以選擇提交事務(wù)或者取消事務(wù)步势。事務(wù)一旦被提交,那么事務(wù)作出的所有改變都將會被寫入磁盤背犯。事務(wù)被取消后坏瘩,那么所有的改變都將失效。換句話說漠魏,同一個(gè)事務(wù)中的操作要么全都做倔矾、要么就全都不錯(cuò),這保證了數(shù)據(jù)的一致性和線程安全。
realm.beginTransaction();//開始事務(wù)
// do some things...
realm.commitTransaction();//提交事務(wù)
realm.beginTransaction();//開始事務(wù)
// do some things...
realm.cancelTransaction();//取消事務(wù)
需要注意的是
Realm
的寫操作會互相死鎖哪自,因此如果你在UI
線程和后臺線程中同時(shí)開啟了寫入事務(wù)丰包,那么將可能會導(dǎo)致ANR
錯(cuò)誤。為了避免這種事件發(fā)生壤巷,官方建議 在異步事務(wù)(async transaction
)處理寫入事務(wù)邑彪。
如果在事務(wù)中發(fā)生了異常,那么事務(wù)中所有的改變都將消失胧华,但是
Realm
本身不受影響锌蓄。如果捕捉了事務(wù)中的異常,并且程序?qū)^續(xù)執(zhí)行下去撑柔,那么需要取消產(chǎn)生異常的事務(wù)瘸爽。有個(gè)省事的做法就是使用excuteTransaction
來執(zhí)行事務(wù),那么產(chǎn)生異常后的后續(xù)操作都將會自動執(zhí)行铅忿,無需人工參與剪决。
得益于
Realm
的MVCC
架構(gòu),即使開啟了事務(wù)檀训,我們?nèi)匀荒軌蛲瑫r(shí)開啟讀操作柑潦。當(dāng)你提交了一個(gè)寫事務(wù)后,所有其他Realm
實(shí)例都會收到通知并自動更新峻凫。
(a)創(chuàng)建對象
在事務(wù)中用createObject
方法創(chuàng)建對象:
realm.beginTransaction(); //開啟事務(wù)
Dog dog = realm.createObject(Dog.class); //創(chuàng)建 Dog 對象
dog.setAge(2);
dog.setName("金毛");
realm.commitTransaction(); //提交事務(wù)
如果是首先創(chuàng)建了一個(gè)對象渗鬼,然后使用
copyToRealm
將對象寫入到Realm
數(shù)據(jù)庫,那么你需要將這個(gè)操作放入事務(wù)中提交荧琼。Realm
支持無限個(gè)自定義有參構(gòu)造函數(shù)來創(chuàng)建對象譬胎,但前提是必須至少要有一個(gè)public
修飾的無參構(gòu)造函數(shù)。否則將會報(bào)錯(cuò):Class "Cat" must declare a public constructor with no arguments if it contains custom constructors.
Cat cat = new Cat("小奶貓"); // 有參構(gòu)造函數(shù)
cat.setAge(5);
realm.beginTransaction();
Cat catRealm = realm.copyToRealm(cat); //將對象拷貝到 Realm 數(shù)據(jù)庫中
realm.commitTransaction();
這里需要強(qiáng)調(diào)的是只有在返回的對象(上述代碼中的
catRealm
對象)上的操作會生效命锄,而原始的對象(上述代碼中的cat
對象)上的操作將不會同步到Realm
數(shù)據(jù)庫中堰乔。如果你僅僅是向Realm
數(shù)據(jù)庫中插入一個(gè)對象,而不會立馬對對象做出修改的話脐恩,那就盡可能用insert
方法镐侯。insert
方法的實(shí)現(xiàn)方式和copyToRealm
方法很相似,但是前者速度更快驶冒,因?yàn)樗挥梅祷乇徊迦雽ο蟮母北竟斗H绻胍迦攵鄠€(gè)對象,推薦使用insert
或insertOrUpdate
方法(所有的插入操作都需要放到事務(wù)塊中)骗污。
List<Cat> cats = Arrays.asList(new Cat("金毛"), new Cat("小奶貓"));
realm.beginTransaction();
realm.insert(cats); //插入多個(gè)Cat對象
realm.commitTransaction();
(b)異步事務(wù)
前文講到如果在
UI
線程和后臺線程中同時(shí)開啟了寫入事務(wù)崇猫,那么可能會導(dǎo)致ANR
。因此身堡,為了避免阻塞UI
線程邓尤,我們可以采用異步事務(wù)來避免這個(gè)問題拍鲤。
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
回調(diào)參數(shù)是可選的贴谎,如果傳入了相應(yīng)的回調(diào)函數(shù)汞扎,那么出發(fā)之后將會執(zhí)行相應(yīng)回調(diào)函數(shù)。需要注意的是回調(diào)函數(shù)在Lopper
線程中執(zhí)行擅这。
(c)對象批量更新
如果對對象某個(gè)字段進(jìn)行批量更新澈魄,最高效的方式是先查詢到所有符合條件的對象,然后調(diào)用
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屬性值設(shè)置為3
}
});
其中
setXXX()
函數(shù)的第一個(gè)參數(shù)是對象字段名痹扇,第二個(gè)參數(shù)是字段值。
4.查詢操作
Realm的查詢引擎使用Fluent接口來構(gòu)造多子句查詢溯香。所有提撒旯埂(包括查詢)在
Realm
中都是懶加載模式,并且永遠(yuǎn)不會復(fù)制數(shù)據(jù)玫坛。例如查詢所有name="小奶貓"
并且age = 2
的對象结笨,我們可以按如下方式來查詢:
RealmResults<Cat> cats2 = realm.where(Cat.class)
.equalTo("name","小奶貓")
.and()
.equalTo("age",2)
.findAll();
(a)結(jié)果過濾
where方法通過指定被查詢的
model
來開始RealmQuery。過濾條件使用謂詞作為名稱的函數(shù)指定湿镀,其中大多數(shù)函數(shù)的名稱不言自明(例如炕吸,equalTo),過濾函數(shù)將字段名稱作為其第一個(gè)參數(shù)勉痴。
使用
in
函數(shù)從查詢結(jié)果集中匹配相應(yīng)的字段赫模,例如要查找name
為小奶貓
、金毛
或者小花貓
的對象蒸矛,我們可以使用以下語法:
RealmResults<Cat> cats3 = realm.where(Cat.class)
.in("name", new String[]{"小奶貓","小花貓","金毛"})
.findAll();
in
過濾函數(shù)支持字符串
瀑罗、二進(jìn)制數(shù)據(jù)
、數(shù)值類型
和日期類型
雏掠。其中數(shù)值類型
和日期類型
還額外支持以下幾種過濾操作:
between
graterThan
lessThan
greaterThanOrEqualTo
lessThanOrEqualTo
字符串類型
額外支持以下幾種過濾操作:
contains
beginsWith
endsWith
like
其中,
字符串類型
有額外的第三個(gè)參數(shù)來控制是否大小寫敏感:Case.INSENSITIVE
和Case.SENSITIVE
廓脆,默認(rèn)是Case.SENSITIVE
。