因為ios用的是sqlite資料庫,所以公司要求我也用sqlite,大家都知道sqlite的sql不怎麼好用,尤其是我們app裡至少有46張表,資料量最多能達到2G,所以不重新封裝一層,越到後期怕是越難維護吧,代碼量也不少。
所以我在sqlite上又封裝了一層,參照對象型資料庫的原理也做了這樣module。類似hibernate。
a.首先是欄位咯,entity裡的properties不一定全要映射到資料庫中,所以我定義了
DBColumn : String columnName ,String type ,Class clz.(如果你需要用index和trigger也是如此)
資料庫5種類型可以寫個tool和property的資料類型做映射(當然還有1-1,1-*,*-* 關係)
b.介面設計,如果以後不用sqlite,選用其他資料庫了,為了這樣的考慮,所以介面要設計好
- newOrUpdate(T) - delete(T) -query(sql,String[] args) -query(Class,String[] args) -close()
這裡sqlite有replace()方法,會自動去判斷是需要insert還是update。推薦使用。
介面定義好了,就是資料庫實現了。這裡就會有兩個問題:
1.insert/update如何構建ContentValues,如果要想automatic,那麼必須要用反射了。之前有提到要定義映射的DBColumn,那麼有了每個欄位的定義,通過反射去拿值構建ContentValues應該不是什麼難事了。比較難搞定的是表關係。1-1比較好弄,只是把id存好就可以了。1-*關係 這個關係是存在*的這張表,所以不做任何事情,*-*就比較麻煩,需要存到關聯表中。代碼如下:
case DBColumn.FIELD_TYPE_TMANY:// save the objects'
List<? extends Persistent> list = (List<? extends Persistent>) method.invoke(t, null);
if (list != null && list.size() > 0) {
ContentValues tValues = null;
HashMap<String, ContentValues> tMap = null;
for (Persistent persistent : list) {
tValues = new ContentValues();
tMap = new HashMap<String, ContentValues>();
tValues.put(Repository.PK1, t.realGuid());
tValues.put(Repository.PK2, persistent.realGuid());
tMap.put(DBUtilities.getAssosiationTableName(clz.column.columnName), tValues);
bindArgs.add(tMap);
}
}
2.query出來如何setValue.這裡當然需要Cursor對象咯。同樣的,通過反射給DTO對象賦值,普通對象沒什麼問題,關鍵是1-1,1-*,*-* 都是其他的DTO對象或者是ArrayList<DTO>,所以這裡就引出了很重要的概念:代理(Proxy)。因為Android提供的java代理不能很好用,之後試過CGLib庫裡的Proxy,雖然好用,但是在Android是不能用,悲劇。所以我leader讓我試試AspectJ。會用Spring的人都知道面向切面把。AspectJ就是做這個的。
首先,有這樣一個概念,如果一次就把DTO對象裡的property都setValue,那麼需要查好幾次表把。因為有1-1等那些關係。如果每次都這樣,是不是會引起不必要的浪費,所以我們需要一個lazy load來載入1-1等那些資料。那麼lazyload如何?呢。有了AspectJ就好辦了。首先DTO提供一個有參的構造方法。這裡參數當然是id咯。至於1-*等關係,你就需要extends ArrayList了。同樣是有參的構造方法。把ids或者querySql傳過去,當然還要指明T。然後重寫ArrayList的方法,add(T),size(),remove(T)...例如:
@Override
public T get(int location) {
if (cache.containsKey(location)) {
if (cache.get(location) != null) {
return (T) cache.get(location).get();
} else {
cache.remove(location);
return createObjFromCursor(location);
}
} else {// get obj from cursor and save it into map
return createObjFromCursor(location);
}
}
相信看了代碼大家都很明白了把,取值是從cache或者db裡面取值,然後就是setValue的具體方法了,以下是部分代碼:
case DBColumn.FIELD_TYPE_TONE:
Constructor con = column.clz.getConstructor(String.class);
method = targetClass.getMethod(generateMethodName(false, column.columnName,isBooleanType),new Class[] { column.clz });
method.invoke(t, con.newInstance(cursor.getString(index)));
break;
case DBColumn.FIELD_TYPE_TMANY:
method = targetClass.getMethod(generateMethodName(false, column.columnName,isBooleanType),new Class[] { DTOList.class });
method.invoke(t,new DTOArray(column.clz, DBUtilities.getTManyGuidsStmt(targetClass, column),DBUtilities.getTManyQueryStmt(targetClass,column), new String[] { t.realGuid() }));
break;
case DBColumn.FIELD_TYPE_MMANY:
method = targetClass.getMethod(generateMethodName(false, column.columnName,isBooleanType),new Class[] { DTOList.class });
method.invoke(t,new DTOArray(column.clz, null, DBUtilities.getMManyQueryStmt(column.clz),new String[] { t.realGuid(),getObjectType(targetClass) }));
break;
注意Cursor,要選擇合適的場合close,否則~你懂的~~
寫的這麼複雜,確實有點傷腦筋,但是架構搭好了之後(花了大概一個月時間)就在也不用管了。比起用別人的資料庫架構,自己封裝一個簡易的架構是不是更方便更省心更安全呢。
這裡推薦一些android的資料庫架構:
對象型資料庫:Perst,DB4O.
關係型資料庫架構:Android orm ,ormlite.