Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android SQLite的ORM接口實現(一)---findAll和find的實現

Android SQLite的ORM接口實現(一)---findAll和find的實現

編輯:關於Android編程

最近在看Android的ORM數據庫框架LitePal,就想到可以利用原生的SQLite來實現和LitePal類似的ORM接口實現。       LitePal有一個接口是這樣的:   List<Status> statuses = DataSupport.findAll(Status.class);    指定什麼類型,就能獲取到該類型的數據集合。      這樣是很方便,於是想著自己不看它們的實現,自己搞一個出來。      首先想到的就是利用反射和泛型。      利用反射有一個比較好的方式就是注解,讀取注解就知道哪些屬性是要被賦值的,但現在我還不想使用注解,那該怎麼辦呢?    我想到了利用反射來調用set方法完成賦值。    首先我們要知道什麼字段需要賦值,反射是可以獲取到字段,但可惜的是,它無法確定屬性的名稱和類型,原生的SQLite操作是要知道列名的。    反射是可以知道屬性的名字的: Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) {    Log.e("DatabaseStore", field.getName()); }     Java的Class API有getFields和getDeclaredFields兩個方法,前者是用來獲取public字段的,後者是用來獲取所有聲明的字段的,顯然必須使用後者,而且注意的是,因為獲取到的字段是所有聲明的字段,所以絕對有可能獲取到不需要的字段。      但光知道屬性的名字還是不夠的,Android的SQLite需要知道自己要獲取到的是什麼類型: cursor.getString(cursor.getColumnIndex("name"));     幸運的是,是可以獲取到的:   for (Field field : fields) {    Type type = field.getGenericType();    Log.e("DatabaseStore", type.toString()); }     但如何知道哪些屬性是要被賦值的呢?       在代碼約束上,我們是可以要求model的所有屬性都是要被賦值的,沒有道理一個model出現的屬性竟然是不需要被賦值的,但實現上,我們還是假設有這樣的可能。     這就需要獲取到setter,只要有setter,就說明它是需要被賦值的:           List<Method> setMethods = new ArrayList<Method>();         for (Method method : allMethods) {             String name = method.getName();             if (name.contains("set") && !name.equals("offset")) {                 setMethods.add(method);                 continue;             }         }       這就要求我們所有的屬性的setter前面都必須帶有set關鍵字,這同樣也是種代碼約束。       既然同樣都是代碼約束,為什麼不能直接就是要求屬性必須都是要被賦值的呢?     很可惜的是,有可能這個model是需要被序列化的,而序列化有可能會有一個序列ID,序列ID是不需要被賦值的,但又是有可能存在於model中的。     比起這個,只要我們利用編輯器自動生成的setter,是一定會有set關鍵字的,所以,這種約束更加簡單。     接著我們的操作就很簡單了:判斷Field的名稱數組中的元素是否有對應的setter,如果有,就從Field的類型數組中取出該屬性的類型,然後判斷該類型屬於哪種類型,就去表中取出對應的值。   Cursor cursor = Connector.getDatabase().query(clazz.getSimpleName(), null, null, null, null, null, null);//查詢並獲得游標         List<T> list = new ArrayList<T>();         Constructor<?> constructor = findBestSuitConstructor(clazz);         while (cursor.moveToNext()) {             T data = null;             try {                 data = (T) constructor                         .newInstance();             } catch (InstantiationException e) {                 e.printStackTrace();             } catch (IllegalAccessException e) {                 e.printStackTrace();             } catch (InvocationTargetException e) {                 e.printStackTrace();             }             for (Method method : setMethods) {                 String name = method.getName();                 String valueName = name.substring(3).substring(0, 1).toLowerCase() + name.substring(4);                 String type = null;                 int index = 0;                 if (fieldNames.contains(valueName)) {                     index = fieldNames.indexOf(valueName);                     type = fields[index].getGenericType().toString();                 }                 Object value = new Object();                 if (type != null) {                     if (type.contains("String")) {                         value = cursor.getString(cursor.getColumnIndex(valueName.toLowerCase()));                     } else if (type.equals("int")) {                         value = cursor.getInt(cursor.getColumnIndex(valueName.toLowerCase()));                     } else if (type.equals("double")) {                         value = cursor.getDouble(cursor.getColumnIndex(valueName.toLowerCase()));                     } else if (type.equals("float")) {                         value = cursor.getFloat(cursor.getColumnIndex(valueName.toLowerCase()));                     } else if (type.equals("boolean")) {                         value = cursor.getInt(cursor.getColumnIndex(valueName.toLowerCase())) == 1 ? true : false;                     } else if (type.equals("long")) {                         value = cursor.getLong(cursor.getColumnIndex(valueName.toLowerCase()));                     } else if (type.equals("short")) {                         value = cursor.getShort(cursor.getColumnIndex(valueName.toLowerCase()));                     }                     try {                         fields[index].setAccessible(true);                         fields[index].set(data, value);                     } catch (IllegalAccessException e) {                         Log.e("data", e.toString());                     }                 }             }             list.add(data);         }         cursor.close();      為了保證通用性,使用了泛型,但這裡有個小小的問題需要解決,就是如何new一個T?      這不是開玩笑的,因為T是無法new的,所以還是需要通過反射來完成。    通過反射來獲取構造器是必須的,但構造器有可能是有很多的,如何獲取到最佳的構造器還是個問題。    什麼是最佳構造器?    實際上,model的構造器基本上應該是無參構造器,但以防萬一,我們還是需要通過一個比較:   protected Constructor<?> findBestSuitConstructor(Class<?> modelClass) {         Constructor<?> finalConstructor = null;         Constructor<?>[] constructors = modelClass.getConstructors();         for (Constructor<?> constructor : constructors) {             if (finalConstructor == null) {                 finalConstructor = constructor;             } else {                 int finalParamLength = finalConstructor.getParameterTypes().length;                 int newParamLength = constructor.getParameterTypes().length;                 if (newParamLength < finalParamLength) {                     finalConstructor = constructor;                 }             }         }         finalConstructor.setAccessible(true);         return finalConstructor;     }       誰的參數最少,誰就是最佳構造器,0當然是最少的。       到了這裡,我們基本上就實現了一個擁有和LitePal的API一樣但內在實現卻是原生方法的數據庫接口方法了:   List<Status> newData = DatabaseStore.getInstance().findAll(Status.class);     LitePal當然會提供條件查詢的接口,也就是所謂的模糊查詢。       模糊查詢的基本結構如下: SELECT 字段 FROM 表 WHERE 某字段 Like 條件      其中,條件有四種匹配模式。        1.%,表示任意0個或更多字符,可匹配任意類型和長度的字符,有些情況下若是中文,就得使用%%表示。 SELECT * FROM [user] WHERE u_name LIKE '%三%'       會把u_name中有“三”的記錄找出來。         可以用and條件來增加更多的條件:   SELECT * FROM [user] WHERE u_name LIKE '%三%' AND u_name LIKE '%貓%'       這樣能夠找出u_name中的“三腳貓”的記錄,但無法找到“張貓三”的記錄。         2._,表示任意單個字符,匹配單個任意字符,用來限制表達式的字符長度語句:   SELECT * FROM [user] WHERE u_name LIKE '_三_'       這樣只能找出“張三貓”這樣中間是“三”的記錄。   SELECT * FROM [user] WHERE u_name LIKE '三__';        這樣是找到“三腳貓”這樣“三”放在開頭的三個單詞的記錄。          3.[],表示括號內所列字符中的一個,指定一個字符,字符串,或者范圍,要求匹配對象為它們中的任一個。 SELECT * FROM [user] WHERE u_name LIKE '[張李王]三'        這樣是找到“張三”,“李三”或者“王三”的記錄。          如 [ ] 內有一系列字符(01234、abcde之類的),則可略寫為“0-4“,“a-e”: SELECT * FROM [user] WHERE u_name LIKE '老[1-9]'       這將找出”老1“,”老2“。。。等記錄。         4.[^],表示不在括號所列之內的單個字符,其取值和[]相同,但它要求所匹配對象為指定字符以外的任一個字符。 SELECT * FROM [user] WHERE u_name LIKE '[^張李王]三'       這樣找到的記錄就是排除”張三“,”李三“或者”王三“的其他記錄。         5.查詢內容包含通配符。       如果我們查特殊字符,如”%“,“_"等,一般程序是需要用"/"括起來,但SQL中是用"[]"。       知道了這些基本的知識後,我們就可以開始看LitePal的接口是怎樣的:  List<Status> myStatus = DataSupport.where("text=?", "我好").find(Status.class);        這樣的接口比較簡單,並且允許鏈式調用,形式上更加簡潔。          要想實現這個,倒也不難,我們暫時就簡單的用一個condition的字符串表示要查詢的條件,然後提供一個where方法實現where查詢的拼接,暫時就只是單個條件: private String conditionStr; public DatabaseStore where(String key, String value) {      conditionStr = " where " + key + " like '%" + value + "%'";      return store; }        為了實現鏈式調用,返回DatabaseStore是必須的。          接下來就非常簡單了,只要拼接完整的SQL語句,然後執行就可以了:   public <T> List<T> find(Class<T> clazz) {    String sql = "SELECT  * FROM " + clazz.getSimpleName().toLowerCase() + conditionStr;    Cursor cursor = Connector.getDatabase().rawQuery(sql, null);    Field[] fields = clazz.getDeclaredFields();    List<String> fieldNames = new ArrayList<String>();    for (Field field : fields) {        fieldNames.add(field.getName());    }    List<Method> setMethods = getSetMethods(clazz);    List<T> list = getList(clazz, cursor, setMethods, fieldNames, fields);    cursor.close();    conditionStr = "";    return list; }     復制代碼       getSetMethods方法就是上面獲取setter的代碼的封裝,而getList方法就是上面生成指定類型對象的List的代碼的封裝。         這樣我們的接口方法的調用就是這樣的: List<Status> data = DatabaseStore.getInstance().where("text", "我好").find(Status.class);       無論是LitePal還是我們自己的實現,where都必須放在find前面。        這裡倒有一個小貼士可以說說,就是獲取數據庫所有表名的操作。      由於底層我們還是使用LitePal來建表,而LitePal的建表非常簡單,就是在assets文件夾下面放一個litepal.xml文件:   <?xml version="1.0" encoding="utf-8"?> <litepal>     <!-- 數據庫名稱 -->     <dbname value="xxx.db"></dbname>     <!-- 數據庫版本 -->     <version value="1"></version>     <!-- 數據庫表 -->     <list>         <mapping class="com.example.pc.model.Status"></mapping>     </list> </litepal>        但表名具體到底是啥呢?        為了確認一下,我們可以查詢數據庫中所有的表的名字: Cursor cursor = Connector.getDatabase().rawQuery("select name from sqlite_master where type='table' order by name", null); while (cursor.moveToNext()) {    //遍歷出表名    String name = cursor.getString(0);    Log.e("DatabaseStore", name); }      每一個SQLite的數據庫中都有一個sqlite_master的表,這個表的結構如下:     CREATE TABLE sqlite_master (    type TEXT,    name TEXT,    tbl_name TEXT,    rootpage INTEGER,    sql TEXT );       對於表來說,type字段是”table“,name字段是表的名字,而索引,type就是”index“,name是索引的名字,tbl_name則是該索引所屬的表的名字。     不管是表還是索引,sql字段是原先用CREATE TABLE或者CREATE INDEX語句創建它們時的命令文本,對於自動創建的索引,sql字段為NULL。     sqlite_master表示只讀的,它的更新只能通過CREATE TABLE,CREATE INDEX,DROP TABLE或者DROP INDEX命令自動更新。     臨時表不會出現在sqlite_master中,臨時表及其索引和觸發器是存放在另外一個叫sqlite_temp_master的表中,如果想要查詢包括臨時表在內的所有的表的列表,就需要這樣寫: SELECT name FROM (SELECT * FROM sqlite_master UNION ALL SELECT * FROM sqlite_temp_master) WHERE type=’table’ ORDER BY name     LitePal還可以對結果進行排序:     List<Status> myStatus = DataSupport.where("text=?", "我好").order("updatetime").find(Status.class);     這個也是很簡單就能實現的,類似where方法一樣的處理:    public DatabaseStore order(String key) {       conditionStr += " order by " + key;       return store;   }     默認是升序。       API被人亂用的概率相當大,這時就需要有一些錯誤提示幫助用戶定位問題了,最簡單的例子就是在沒有任何條件的情況下調用find方法,這時就應該提示沒有任何條件:     if (conditionStr.equals("")) {         throw new Throwable("There are not any conditions before find method invoked");    }     還有一種情況並不算是被亂用,但按照上面的實現是會出錯的:   statuses = DatabaseStore.getInstance().order("updatetime").where("text", "我好").find(Status.class);      絕對會報錯,因為最後的SQL語句是這樣的:select * from status order by updatetime where text like '%我好%'。        這是不對的,必須將where放在order by前面。      解決這個問題的方法就是提供兩個字符串:   private String whereStr = ""; private String orderStr = ""; public DatabaseStore where(String key, String value) {    whereStr += " where " + key + " like '%" + value + "%'";    return store; } public DatabaseStore order(String key) {    orderStr += " order by " + key;    return store; }        接著就是在find方法中進行判斷:   if (whereStr.equals("") && orderStr.equals("")) {      throw new Throwable("There are not any conditions before find method invoked"); } String sql = "select  * from " + clazz.getSimpleName().toLowerCase() + (whereStr.equals("") ? "" : whereStr) + (orderStr.equals("") ? "" : orderStr);      暫時就簡單實現了類似LitePal的ORM接口調用形式。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved