Android SQLite的ORM介面實現(一)---findAll和find的實現

來源:互聯網
上載者:User

Android SQLite的ORM介面實現(一)---findAll和find的實現
最近在看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 ALLSELECT * 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介面調用形式。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.