在上一篇博客[《打造android ORM框架opendroid(五)——數據更新的實現》](http://blog.csdn.net/qibin0506/article/details/43148523)??我們介紹了opendroid數據更新的流程,也就在上次,我們OpenDroid類中的所有操作都介紹完了, 那查詢操作呢?不是在OpenDroid中?查詢操作是在OpenDroid中,不過是以內部類的形式呈現的。
還是來看看如果使用opendroid查詢數據吧。
` OpenDroid.query.find(Student.class)??`
` OpenDroid.query.columns("stuName").where("_id>?",?"1").limit(1,?4).order("_id?DESC").find(Student.class);????`
這是opendroid提供的查詢方法,也是最常見的級聯查詢,了解其他ORM框架的朋友們肯定對此不陌生吧,但是,看第二段代碼,我們比傳統的級聯操作多了一個方法columns(),該方法是設置要查詢的字段。
好了, 進入我們今天的重點,通過上面代碼,我們發現是通過調用了OpenDroid的一個靜態字段query的各個方法去組合查詢的。我們來看看query。
` public?static?Query?query?=?new?Query();??`
在OpenDroid中這個Query類型的字段,并且在定義的時候就去實例化了。我們再來看看Query這個內部類。
~~~
?/**??
?*?查詢??
?*?@author?qibin??
?*/????
public?static?class?Query?{????
????private?String[]?mCocumns?=?null;??//?要查詢的字段????
????private?String?mWhere?=?null;???//?查詢的條件????
????private?String[]?mWhereArgs?=?null;?//?查詢的條件的參數????
????private?String?mOrder?=?null;?//?order語句????
????private?String?mLimit;?//?limit語句????
????????????
????/**??
?????*?設置查詢的字段??
?????*?@param?columns?要查詢的字段??
?????*?@return?Query對象??
?????*/????
????public?Query?columns(String...?columns)?{????
????????mCocumns?=?columns;????
????????return?this;????
????}????
????????????
????/**??
?????*?設置查詢的where條件??
?????*?@param?where?where條件??
?????*?@param?whereArgs?where參數??
?????*?@return?Query對象??
?????*/????
????public?Query?where(String?where,?String...?whereArgs)?{????
????????mWhere?=?where;????
????????mWhereArgs?=?whereArgs;????
????????return?this;????
????}????
????????
????/**??
?????*?設置查詢的order??
?????*?@param?order?order語句??
?????*?@return?Query對象??
?????*/????
????public?Query?order(String?order)?{????
????????mOrder?=?order;????
????????return?this;????
????}????
????????????
????/**??
?????*?設置查詢的limit??
?????*?@param?limit?limit語句??
?????*?@return?Query對象??
?????*/????
????public?Query?limit(int...?limit)?{????
????????StringBuilder?builder?=?new?StringBuilder();????
????????builder.append(limit[0]);????
????????????????
????????if(limit.length?==?2)?{????
????????????builder.append(",").append(limit[1]);????
????????}????
????????????????
????????mLimit?=?builder.toString();????
????????????????
????????return?this;????
????}????
????????????
????/**??
?????*?查詢??
?????*?@param?klass?映射的bean??
?????*?@return?查詢結果??
?????*/????
????public?extends?OpenDroid>?List?find(Class?klass)?{????
????????return?CRUD.query(klass,?mCocumns,?mWhere,?mWhereArgs,?????
????????????????mOrder,?mLimit,?sSqliteDatabase);????
????}????
????????????
????/**??
?????*?根據id查詢數據??
?????*?@param?klass?klass?映射的bean??
?????*?@param?id?要查詢數據的id??
?????*?@return??查詢結果??
?????*/????
????public?extends?OpenDroid>?T?find(Class?klass,?int?id)?{????
????????List?result?=?CRUD.query(klass,?mCocumns,?"_id=?",?????
????????????????new?String[]?{?String.valueOf(id)?},?null,?"1",????
????????????????sSqliteDatabase);????
????????return?result.size()?>?0???result.get(0)?:?null;????
????}????
????????????
????public?extends?OpenDroid>?List?find(Class?klass,?int...?ids)?{????
????????StringBuilder?builder?=?new?StringBuilder("_id?in?(");????
????????String[]?whereArgs?=?new?String[ids.length];????
????????buildIn(builder,?whereArgs,?ids);????
????????????????
????????return?CRUD.query(klass,?mCocumns,?builder.toString(),?whereArgs,?????
????????????????mOrder,?mLimit,?sSqliteDatabase);????
????}????
????????????
????/**??
?????*?使用sql語句查詢??
?????*?@param?sql?sql語句??
?????*?@param?selectionArgs?預編譯參數??
?????*?@return??
?????*/????
????public?Cursor?queryBySql(String?sql,?String[]?selectionArgs)?{????
????????return?sSqliteDatabase.rawQuery(sql,?selectionArgs);????
????}????
}??
~~~
這個內部類中,上來咔咔咔列出了5個字段,注釋已經說的很清楚了,其實就是組合查詢語句用的。
往下看,我們看到了熟悉的方法,這些都是在上面介紹的時候用到的,并且一些方法的返回值都是this,大家應該很清楚,我們要級聯操作嘛,就必須要返回當前對象。
好吧,第一個方法
~~~
/**??
?*?設置查詢的字段??
?*?@param?columns?要查詢的字段??
?*?@return?Query對象??
?*/????
public?Query?columns(String...?columns)?{????
????mCocumns?=?columns;????
????return?this;????
}????
~~~
很簡單,就是把要查詢的字段保存起來,然后返回當前對象。
~~~
/**??
?*?設置查詢的where條件??
?*?@param?where?where條件??
?*?@param?whereArgs?where參數??
?*?@return?Query對象??
?*/????
public?Query?where(String?where,?String...?whereArgs)?{????
????mWhere?=?where;????
????mWhereArgs?=?whereArgs;????
????return?this;????
}???
~~~
where方法也很簡單,保存了where條件和where條件的參數。
~~~
/**??
?*?設置查詢的order??
?*?@param?order?order語句??
?*?@return?Query對象??
?*/????
public?Query?order(String?order)?{????
????mOrder?=?order;????
????return?this;????
}???
~~~
好吧,一樣的邏輯(哪里有邏輯!!!)
~~~
/**??
?*?設置查詢的limit??
?*?@param?limit?limit語句??
?*?@return?Query對象??
?*/????
public?Query?limit(int...?limit)?{????
????StringBuilder?builder?=?new?StringBuilder();????
????builder.append(limit[0]);????
????????????????
????if(limit.length?==?2)?{????
????????builder.append(",").append(limit[1]);????
????}????
????????????????
????mLimit?=?builder.toString();????
????????????????
????return?this;????
}???
~~~
設置limit, 這個方法還算長一點,但是一點內容也沒有,就是組合一個limit語句,不過這里做了一個數組長度的判斷,主要是為了適配limit 1和limit 1,2這兩種方式。
~~~
/**??
?*?查詢??
?*?@param?klass?映射的bean??
?*?@return?查詢結果??
?*/????
public?extends?OpenDroid>?List?find(Class?klass)?{????
????return?CRUD.query(klass,?mCocumns,?mWhere,?mWhereArgs,?mOrder,?mLimit,?sSqliteDatabase);????
}??
~~~
這個查詢方法直接調用了CRUD.query并返回它的返回值,相信大家看到CRUD肯定很熟悉了,我們定位到CRUD中query這個方法看看吧。
~~~
/**??
?*?查詢數據??
?*?@param?klass?要映射的類??
?*?@param?columns?查詢的字段,?null?取所有??
?*?@param?where?where條件,?null?忽略條件??
?*?@param?whereArgs?where的參數,?null無參數??
?*?@param?order?order語句,?null忽略order?by??
?*?@param?limit?limit語句,?null忽略limit??
?*?@param?db????數據庫句柄??
?*?@return?查詢后的數據??
?*/????
protected?static?extends?OpenDroid>?List?query(Class?klass,?String[]?columns,?????
????????String?where,?String[]?whereArgs,?String?order,?String?limit,?SQLiteDatabase?db)?{????
????List?resultList?=?new?ArrayList();?//?存放結果????
????String?tableName?=?klass.getSimpleName();?//?獲取類名(表名)????
????Cursor?cursor?=?null;????
????????????
????try?{????
????????cursor?=?db.query(tableName,?columns,?where,?whereArgs,?null,?null,?order,?limit);????
????????foreachCursor(klass,?cursor,?resultList);????
????}?catch?(Exception?e)?{????
????????e.printStackTrace();????
????}?finally?{????
????????if(cursor?!=?null)?{????
????????????cursor.close();????
????????}????
????}????
????????????
????return?resultList;????
}??
~~~
參數有點多,那么我們就從參數說起。
第一個參數是一個Class,應該很清晰了,我們要根據它來獲取要查詢的表明,接下來的一系列就是要查詢的字段,條件,order語句,limit語句,最后一個是我們操作的數據庫句柄。
繼續看代碼,首先new類一個ArrayList,并且獲取了要操作的表名,接下來在try中,直接調用了android原生api的query返回一個Cursor對象,這就是我們查詢的結果。接下來調用foreachCursor方法,將結果遍歷到剛開new的那個ArrayList中,查詢操作完畢!
那么我們來看看foreachCursor方法。
~~~
/**??
?*?遍歷數據庫游標??
?*?@param?klass?要映射的類??
?*?@param?cursor?要遍歷的游標??
?*?@param?resultList?存放返回的結果??
?*?@throws?InstantiationException??
?*?@throws?IllegalAccessException??
?*/????
private?static?extends?OpenDroid>?void?foreachCursor(Class?klass,????
????????Cursor?cursor,?List?resultList)?throws?InstantiationException,????
????????IllegalAccessException?{????
????T?t;?//?反射出的實例????
????String?columnName;?//?數據庫字段名????
????String?methodName;?//?方法名????
????Method?m;?//?反射出的方法????
????????????
????for(cursor.moveToFirst();!cursor.isAfterLast();cursor.moveToNext())?{????
????????t?=?klass.newInstance();??//?通過反射進行實例化????
????????for(int?i=0;i
????????????columnName?=?cursor.getColumnName(i);?//?獲取數據庫字段名????
????????????try?{????
????????????????switch?(cursor.getType(i))?{????
????????????????case?Cursor.FIELD_TYPE_INTEGER:????
????????????????????//?如果字段名是_id的話,?對應的方法是setId????
????????????????????methodName?=?columnName.equals("_id")???"setId"?:?????
????????????????????????getMethodName(cursor.getColumnName(i));????
????????????????????m?=?klass.getMethod(methodName,?int.class);?//?反射出方法????
????????????????????m.invoke(t,?cursor.getInt(i));?//?執行方法????
????????????????????break;????
????????????????case?Cursor.FIELD_TYPE_FLOAT:????
????????????????????methodName?=?getMethodName(cursor.getColumnName(i));????
????????????????????m?=?klass.getMethod(methodName,?float.class);????
????????????????????m.invoke(t,?cursor.getFloat(i));????
????????????????????break;????
????????????????default:????
????????????????????methodName?=?getMethodName(cursor.getColumnName(i));????
????????????????????m?=?klass.getMethod(methodName,?String.class);????
????????????????????m.invoke(t,?cursor.getString(i));????
????????????????????break;????
????????????????}????
????????????}catch(Exception?e)?{????
????????????????e.printStackTrace();????
????????????}????
????????}????
????????resultList.add(t);????
????}????
}??
~~~
又是一個巨長的...
我們來一點點分析吧。首先看參數,前兩個不用說,最后一個參數是要存放我們遍歷的結果的,這種方式在以前的博客中也有介紹過。
大體看一下這個方法,雖然很長,但是不難,switch case中的語句只需要了解一個剩下的就都了解了。
首先定義了4個變量,先有點印象。接著一個for循環,for循環也是做android的很熟悉的,就是去遍歷游標,如果你還不清楚這里,建議去看看android原生api。
18行,我們獲取Class的實例,實例化主要是為將表中的數據查詢保存到我們的java bean中。
接下來19行,又是一個for循環,這個循環是循環的什么呢? 恩,這里是循環的該行的所有字段。
20行,獲取當前index的字段名。
接下來一個switch case,這里我們只去觀察第一個case語句中的代碼。
25行是去拼湊一個setter,不過這個有點特殊,有一個二目運算符,我先解釋一下這是為什么,還記得我曾經提到過id是opendroid默認添加的,而不需要我們去定義bean字段嗎? 而且我們在分析創建數據庫的源碼中也看到了自動創建_id字段的代碼,但是現在就有一個問題了。 我們沒有id字段,id怎么放bean中呢? 所以在OpenDroid類中我強制定義了一個id字段,而且在使用的過程中,你也應該發現你的bean實例會有一個getId和setId的方法。看源碼來證明一下吧:
~~~
private?int?id;????
public?int?getId()?{????
????return?id;????
}????
????
public?void?setId(int?id)?{????
????this.id?=?id;????
}??
~~~
知道了這一點,我們再來理解這句話,如果查詢的是_id字段,那么我們就去調用setId這個方法,否則就去調用getMethodName()根據當前數據庫中的字段名拼湊出一個setter方法。
好吧,接著分析代碼,27~28行,反射出我們拼湊的這個方法,并通過invoke去執行該方法,到這里,比如我們要獲取stuName字段,那么Student.stuName就有值了。
在獲取完當前行數據后,46行將生成的這個對象添加到ArrayList中保存起來。
總之,該方法的作用就是通過反射去實例化對應的類,并通過類中的setter方法設置值。
這里面還用到了一個getMethodName()方法,這個方法很簡單,就是根據數據庫字段名拼湊一個setXXX方法。
~~~
/**??
?*?根據字段名構建方法名??
?*?@param?columnName??
?*?@return??
?*/????
public?static?String?getMethodName(String?columnName)?{????
????String?methodName?=?columnName;????
????methodName?=?Character.toUpperCase(methodName.charAt(0))?+?methodName.substring(1);????
????methodName?=?"set"?+?methodName;????
????????????
????return?methodName;????
}???
~~~
很簡單,不多說了,唯一需要注意的是第8行,意思是將字段名的首字母大寫。
至此,一個find方法的流程就分析完了,其實剩下的find方法都是調用了同一個CRUD.query方法去查詢的。
~~~
/**??
?*?根據id查詢數據??
?*?@param?klass?klass?映射的bean??
?*?@param?id?要查詢數據的id??
?*?@return??查詢結果??
?*/????
public?extends?OpenDroid>?T?find(Class?klass,?int?id)?{????
????List?result?=?CRUD.query(klass,?mCocumns,?"_id=?",?????
????????????new?String[]?{?String.valueOf(id)?},?null,?"1",????
????????????sSqliteDatabase);????
????return?result.size()?>?0???result.get(0)?:?null;????
}???
~~~
這個方法調用了CRUD.query方法去查詢的,只是我們確定了條件。
~~~
public?extends?OpenDroid>?List?find(Class?klass,?int...?ids)?{????
????StringBuilder?builder?=?new?StringBuilder("_id?in?(");????
????String[]?whereArgs?=?new?String[ids.length];????
????buildIn(builder,?whereArgs,?ids);????
????????????????
????return?CRUD.query(klass,?mCocumns,?builder.toString(),?whereArgs,?????
????????????mOrder,?mLimit,?sSqliteDatabase);????
}???
~~~
這個方法同樣的原理,只需要是多個id,只要是多個id的,我們都是通過in語句去實現的,所以調用了buildIn方法去拼湊了一個in語句,剩下的和其他的find方法一樣。
好啦,至此為止,opendroid的查詢操作我們也介紹完了,也就是opendroid的所有CRUD操作有順了一遍,那是不是opendroid所有功能都完了呢? 當然不是,還有我們的數據庫升級方案呢! 所以下篇博客將會介紹opendroid的數據庫升級方案!
最后是opendroid的開源地址:[http://git.oschina.net/qibin/OpenDroid](http://git.oschina.net/qibin/OpenDroid)