從ActiveAndroid到Realm的爬坑之路(二)
Realm是作為一個Gradle外掛整合到專案中的,怎麼配置 文件 裡說的也很清楚了,需要注意的是在官方的GitHub上是這麼新增依賴的:
dependencies {
classpath "io.realm:realm-gradle-plugin:<version>-SNAPSHOT"
}
但copy的時候中間的version 應該替換成最新的版本號。然後,就可以開始使(pa)用(keng) 了。
一、 資料庫實體可以不繼承RealmObject,實現RealmModel介面也可,但不能繼承其它父類。
文件很明確的指出,不一定要繼承RealmObject,實現RealmModel介面加@RealmClass註解一樣可以工作。 不過你要是繼承了別的類,就像這樣:
@RealmClass
public class DepartureRealm extends Model implements RealmModel{
@PrimaryKey
public long key;
...
}
那問題就來了:
Error:(12, 8) 錯誤: Realm model classes must either extend RealmObject or implement RealmModel to be considered a valid model class
不可理解,去掉extends又能正常運行了。這樣就直接否定了我想直接使用ActiveAndroid的實體的想法,最關鍵的是,既然不能繼承其他類,提供這麼個功能的意義何在啊?
二、沒有自增主鍵,不能進行對欄位進行唯一性約束,不支援連線查詢
傳統資料庫很常見的概念在這裡是找不到的,主鍵不自增雖然有點不方便,但使用UUID也不失為一個好辦法。只是只有主鍵相同,它才幫你更新這一點,就讓人滿腦子問號了。而且它不支援左連右連,它儘量的將連線查詢給透明化,你只需要建立好實體之間的關係,就能通過欄位名直接查到關聯表的資料。不得不說,這樣設計很酷炫也很方便,然而,對於一個已存在的專案,就必須去修改它的表結構了,這必然會影響到整個專案。
為了解決這些問題,通過只修改資料庫Service層的相關類達到使用Realm而又能隨時切換回來的目的,就必須提供一個轉換機制,如下圖:
而且因為Realm只支援主鍵唯一的特點,每次Save時,還得判斷欄位是不是有唯一性約束,有就查詢資料庫是否存在此欄位值,存在就更新,不存在才生成新的ID並插入。總的changeActiveToRealm方法如下:
//mID, 轉換到RealmObject後,為其生成UUID,並將其通過這個引數傳遞出去。
public static RealmModel changeActiveToRealm(Model object, Bundle mID) {
Model temp;
if (object == null) {
return null;
} else {
temp = object;
}
Field[] fields = temp.getClass().getFields();
String className = temp.getClass().getName();
...
try {
//載入Active實體對應的Realm實體。
Class obtainClass = Class.forName(className.replace("Active", "Realm").replace("active", "realmobj"));
RealmModel obtainObj = (RealmObject) obtainClass.newInstance();
Field tempObtainField;
String fieldName;
boolean findUnique = false;
Realm realm = Realm.getDefaultInstance();
//記錄下所有需要提供唯一性約束的欄位。(根據Column註解)
List<Field> group = new ArrayList<>();
for (Field field : fields) {
Column c = field.getAnnotation(Column.class);
if (c != null) {
Column.ConflictAction[] a = c.onUniqueConflicts();
if (a.length != 0 && a[0] == Column.ConflictAction.REPLACE) {
group.add(field);
}
}
}
if(group.size() != 0) {
RealmQuery query = realm.where(obtainClass);
for (Field f :
group) {
Object uniqueValue = f.get(temp);
if (uniqueValue instanceof Integer) {
query.equalTo(f.getName(), (Integer) uniqueValue);
} else {
query.equalTo(f.getName(), String.valueOf(uniqueValue));
}
}
RealmModel clazz = query.findFirst();
if (clazz != null) {
Log.i("feng", "找到唯一性約束衝突 class Name :" + clazz.getClass().getSimpleName() +" 更新資料庫資料");
obtainObj = realm.copyFromRealm(clazz);
findUnique = true;
}
realm.close();
}
//將欄位值一一對映
for (Field field : fields) {
fieldName = field.getName();
tempObtainField = obtainObj.getClass().getField(fieldName);
tempObtainField.setAccessible(true);
tempObtainField.set(obtainObj, field.get(temp));
}
//是否生成ID
if (!findUnique) {
Long tempmID = UUID.randomUUID().getLeastSignificantBits();
Field o = obtainClass.getField("key");
if (o.getName().equals("key")) {
int t = tempmID.intValue();
o.set(obtainObj, (long) t);
mID.putLong("id", (long) t);
}
} else {
mID.putLong("id", (long)obtainClass.getField("key").get(obtainObj));
}
return obtainObj;
} catch (ClassNotFoundException e) {
...
}
}
而changeRealmToActive 的基本思路也差不多,都是通過反射獲得實體的值,並將它們對映到新的實體中去。
至此,應用並不知道底層的資料庫已經更換了,它一直用的都是Active的實體物件,而只需要在Service層中增加一個flag做判斷,來回切換兩個資料庫都不是問題了。
還有一種實現思路是不通過反射,而是用JSON字串做中轉,然而效率上肯定是不如直接反射拿到物件並且直接操作了。
三、不支援分頁查詢。
上篇文章已經介紹了懶載入機制,官方自豪的宣稱因為查詢出來並不佔用記憶體,所以你們放心地查出來所有的資料,想要哪段就擷取哪段。(提供介面不是更好嗎?)
//////從查詢的所有資料中取出需要的。(脫離Realm的控制)
public static <E extends RealmModel> List<E> getLimitList(
RealmResults<E> data, int offset, int limit) {
List<E> obtainList = new ArrayList();
Realm realm = Realm.getDefaultInstance();
if (data.size() == 0 ){
return obtainList;
}
for (int i = offset; i < offset + limit; i++) {
if (i >= data.size()) {
break;
}
E temp = realm.copyFromRealm(data.get(i));
obtainList.add(temp);
}
realm.close();
return obtainList;
}
對於一個已經成型的專案,在新增任何第三方框架前都應該慎之又慎,雖然新技術聽起來總是很誘人,但能不能和現有的程式碼相結合是需要考慮的一方面,另一方面,還有可能遇到各種大大小小的坑。這也許是這次更換資料庫框架帶來的最大感受。