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;
}
对于一个已经成型的项目,在添加任何第三方框架前都应该慎之又慎,虽然新技术听起来总是很诱人,但能不能和现有的代码相结合是需要考虑的一方面,另一方面,还有可能遇到各种大大小小的坑。这也许是这次更换数据库框架带来的最大感受。