也許你并沒有聽說過 Realm 掏婶,這是一個面向安卓(亦面向iOS)的移動端數(shù)據(jù)庫技術(shù)管怠。和SQLite不同棍现,它允許你在持久層直接和數(shù)據(jù)對象工作淮椰。在它之上是一個函數(shù)式風(fēng)格的查詢api臼寄,眾多的努力讓它比傳統(tǒng)的SQLite 操作更快 霸奕。基于這些原因讓我決定試試Realm 吉拳。
大約一年前质帅,當(dāng)我第一次使用Realm 的時候,給我的第一印象非常不錯留攒。我需要保持一些用戶數(shù)據(jù)在手機(jī)本地煤惩,但是SharedPreferences用起來有點(diǎn)復(fù)雜了。Realm允許我用快速干凈的代碼里完成這件事情炼邀。它完全不需要像SQLite那樣自己手動寫額外的代碼魄揉。
我接下來的一個項(xiàng)目在缺少網(wǎng)絡(luò)連接的時候需要一個比較復(fù)雜的離線模式。從網(wǎng)絡(luò)抓取的數(shù)據(jù)必須保存在手機(jī)本地拭宁。我決定完全使用Realm 并觀察它隨項(xiàng)目增大是如何擴(kuò)大的洛退。
而我很快發(fā)現(xiàn)Realm讓數(shù)據(jù)模型的工作成了一種負(fù)擔(dān)瓣俯。它有幾個限制以至于我必須在整個代碼基礎(chǔ)上做處理。結(jié)果我嘗試在Realm之上抽象出另外一層來減輕這種限制兵怯。
定義對象
為了說明這些限制彩匕,讓我們從簡單的Person對象開始:
public class Person extends RealmObject {
private String name;
private int age;
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; }
}
注意我們必須直接繼承自RealmObject。這阻礙我們利用數(shù)據(jù)模型中的任意類型的繼承媒区。
并且我們還不能定義除setters 和 getters之外的實(shí)例方法驼仪。如果你想重寫equals 或者toString那么你就別想了。這樣導(dǎo)致的另外一個后果就是我們只能局限于使用標(biāo)記接口模式(marker interfaces) (注解也是可以的 )袜漩。
不僅僅被限制于setters 和 getters绪爸,實(shí)際上我們還必須提供它們。因此我們的數(shù)據(jù)對象是不可變的宙攻!另外奠货,setters 和 getters方法只是為Realm替換自己實(shí)現(xiàn)的代理方法。它不能操作數(shù)據(jù)座掘,跑出異常仇味,或者打印日志。
雖然我們可以提供一個非默認(rèn)的構(gòu)造函數(shù)雹顺,但是我們必須保證存在一個空的構(gòu)造函數(shù)丹墨。如果你想用一個builder 或者工廠方法來作為實(shí)例化的唯一途徑,那么這種限制就成了一個問題嬉愧。稍后我們將看看如何用Realm創(chuàng)建對象贩挣。
在我們能持有的field類型方面,也有一些限制没酣。所有的基本數(shù)據(jù)類型以及它們的封裝類型都能支持王财,包括String, Date, 和byte[]`。但是對于其它類型裕便,為了被持久化绒净,必須繼承自RealmObject。Lists可以用RealmList來支持偿衰。
但是也僅此而已挂疆。如果我們想使用枚舉而不是int,是沒有辦法的(找到一個使用@IntDef的理由了)下翎。我們還不能使用集合類型缤言,比如Set和Map。
創(chuàng)建和更新對象
為了創(chuàng)建一個Person類的實(shí)例视事,我們必須做如下事情:
Realm realm = Realm.getInstance(context);
realm.beginTransaction();
Person person = realm.createObject(Person.class);
person.setName("John");
person.setAge(25);
realm.commitTransaction();
你會注意到我們必須包裹一下Person 對象胆萧,同時任何對它的修改都在一個transaction 中。如果我們能在transaction 之外做這件事情并在我們準(zhǔn)備好的時候持久化它俐东,就要靈活得多跌穗。而現(xiàn)在我們在想要創(chuàng)建或者更新我們對象的任何時候都要卡在寫額外的Realm 代碼上面订晌。
之前我提過我們可以定義一個非默認(rèn)的構(gòu)造函數(shù)。比如蚌吸,對于Person我們可能有一個帶有name 和 age的構(gòu)造函數(shù):
public class Person extends RealmObject {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
// required empty public constructor
}
// setters and getters
...
}
我們不再需要直接調(diào)用setters :
Person person = new Person("John", 25);
realm.beginTransaction();
Person realmPerson = realm.copyToRealm(person);
realm.commitTransaction();
這讓我們省去了一些寫額外代碼的時間腾仅,但是仍然受限于transaction。
Mitigating These Issues
為了避免在基礎(chǔ)代碼中處理這些限制套利,我為數(shù)據(jù)對象定義了兩套類: POJOs (普通對象)和Realm 對象。然后我們創(chuàng)建了一個能在兩者之間映射的abstraction 鹤耍。
這是可行的肉迫,但是有兩個主要的問題。第一個是當(dāng)你持有許多不同類型的對象時稿黄,你需要許多代碼來映射這些類喊衫。管理這些是很痛苦的而且這也很容易產(chǎn)生bug。 對象映射的概念以及它存在的問題都不是什么新東西了杆怕。
第二個就是我覺得這有違最初使用Realm的目的族购。能在持久層直接使用對象是它的主要好處。如果我們?yōu)榱耸褂肞OJO而必須在Realm 之上創(chuàng)建抽象陵珍,那么它相比SQLite或者像 DBFlow一樣的ORM的優(yōu)勢在哪里呢寝杖?
值得一提的是Realm 的維護(hù)者已經(jīng) 意識到了這些限制 ,而且在一定程度上互纯,許多問題都可能被解決(見這里 和 這里))瑟幕。Realm也的確具有一些其它的優(yōu)勢,比如性能以及在iOS和安卓之間共享數(shù)據(jù)的能力留潦。