自己動手寫Android資料庫框架

TigerJin發表於2021-09-09

自己動手寫Android資料庫框架

相信不少開發者跟我一樣,每次都很煩惱自己寫資料庫,而且那些資料庫語句也經常記不住。當然網上也有很多很好的資料庫框架,你可以直接拿來用,但是 很多時候我們的專案,特別是一個小型的Andrond應用原本用到的資料庫結構比較簡單,沒必要去用那些有點臃腫的框架。當然,即使你用那些框架,當你遇到問題時,你是否也得去修改它?你要修改別人的框架必須的讀懂他人的設計程式碼。所以不管從那個角度出發,你都得掌握簡單的資料庫操作。那麼這篇部落格就從簡單的資料庫操作來學習Android資料庫相關知識點,然後一步一步去搭建自己的簡單型資料庫框架,以後就再也不用擔心害怕去寫資料庫了,直接拿自己的資料庫框架用就好了。

框架功能

  1. public long insert(Object obj);插入資料

  2. public List findAll(Class clazz);查詢所有資料

  3. public List findByArgs(Class clazz, String select, String[] selectArgs) ;根據指定條件查詢滿足條件資料

  4. public T findById(Class clazz, int id);根據id查詢一條記錄

  5. public void deleteById(Class)

建立資料庫

Android系統中已經整合了Sqlite資料庫,我們直接使用它就好了,同時Android系統提供了一個資料庫幫助類SQLiteOpenHelper,該類是一個抽象類,所以得寫一個類來繼承它實現裡面的方法。程式碼如下:

MySQLiteHelper類

public class MySQLiteHelper extends SQLiteOpenHelper {    public MySQLiteHelper(Context context, String name, CursorFactory factory,            int version) {        super(context, name, factory, version);
    }    @Override
    public void onCreate(SQLiteDatabase db) {
    }    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }

}

當資料庫建立時系統會呼叫其中的 onCreate方法,那麼我們就可以來實現 onCreate 方法來建立資料庫表。假設我們要建立一張 Person表,表中有 id,name,age,flag欄位。那麼程式碼如下:

public class MySQLiteHelper extends SQLiteOpenHelper {    public static final String CREATE_TABLE = "create table Person ("
            + "id integer primary key autoincrement, "
            + "name text, "
            + "age integer, "
            + "flag boolean)";    public MySQLiteHelper(Context context, String name, CursorFactory factory,            int version) {        super(context, name, factory, version);
    }    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_TABLE);
    }
    ...
}

由此我們的資料庫幫助類就完成了,接下來是這麼使用的:

    private static final String DB_NAME = "demo.db";    private static final int DB_VERSION = 1;    public void oepnDB(){
        MySQLiteHelper helper = new MySQLiteHelper(context, DB_NAME, null, DB_VERSION);
        SQLiteDatabase db = helper.getWritableDatabase();
    }

有以上程式碼就已經完成了一個資料庫建立以及一張表的建立,是不不是感覺不是很難呢?這麼看起來的確不是很難,但是我的也不得不每次去繼承SQLiteOpenHelper類來實現裡面的方法。關鍵是每次都要去寫建立表語句

public static final String CREATE_TABLE = "create table Person ("
            + "id integer primary key autoincrement, "
            + "name text, "
            + "age integer, "
            + "flag boolean)";

這裡表的欄位只有4個,如果有一天你遇到表裡的欄位有10列怎麼辦?還繼續按照上面的方法寫建立表語句麼?你就不嫌繁瑣麼?而且容易粗錯。那麼有沒有超級簡單的方法一步完成表語句的建立呢?你細想:存放在資料庫中表的這些欄位無非就是一個Person類中的所有成員變數,這麼一來是否可以只透過Person型別直接建立表語句呢?答案是肯定的。我們透過java 的反射機制來一步一勞永逸的實現建表操作。程式碼如下:

 /**
         * 得到建表語句
         *
         * @param clazz 指定類
         * @return sql語句
         */
        private String getCreateTableSql(Class> clazz) {
            StringBuilder sb = new StringBuilder();            //將類名作為表名
            String tabName = Utils.getTableName(clazz);
            sb.append("create table ").append(tabName).append(" (id  INTEGER PRIMARY KEY AUTOINCREMENT, ");            //得到類中所有屬性物件陣列
            Field[] fields = clazz.getDeclaredFields();            for (Field fd : fields) {
                String fieldName = fd.getName();
                String fieldType = fd.getType().getName();                if (fieldName.equalsIgnoreCase("_id") || fieldName.equalsIgnoreCase("id")) {                    continue;
                } else {
                    sb.append(fieldName).append(Utils.getColumnType(fieldType)).append(", ");
                }
            }
            int len = sb.length();
            sb.replace(len - 2, len, ")");
            Log.d(TAG, "the result is " + sb.toString());            return sb.toString();
        }

工具類程式碼如下:

package com.xjp.databasedemo;

import android.text.TextUtils;

import java.util.Locale;/**
 * Created by xjp on 2016/1/23.
 */public class DBUtils {    //得到每一列欄位的資料型別
    public static String getColumnType(String type) {
        String value = null;        if (type.contains("String")) {            value = " text ";
        } else if (type.contains("int")) {            value = " integer ";
        } else if (type.contains("boolean")) {            value = " boolean ";
        } else if (type.contains("float")) {            value = " float ";
        } else if (type.contains("double")) {            value = " double ";
        } else if (type.contains("char")) {            value = " varchar ";
        } else if (type.contains("long")) {            value = " long ";
        }        return value;
    }    //得到表名
    public static String getTableName(Class> clazz){        return clazz.getSimpleName();
    }    public static String capitalize(String string) {        if (!TextUtils.isEmpty(string)) {            return string.substring(0, 1).toUpperCase(Locale.US) + string.substring(1);
        }        return string == null ? null : "";
    }
}

如此一來,使用者建立資料庫表就變的很簡單了,傳入Person類的型別(Person.class)作為引數,那麼程式碼就幫你建立出了一張名字為Person的表。使用程式碼如下:

class MySqLiteHelper extends SQLiteOpenHelper {

        ..................        @Override
        public void onCreate(SQLiteDatabase db) {
            createTable(db);
        }        /**
         * 根據制定類名建立表
         */
        private void createTable(SQLiteDatabase db) {
            db.execSQL(getCreateTableSql(Person.class));

        ..............
    }

是不是很簡單!!!領導再也不用擔心我不會建立資料庫了。

資料庫操作–插入

android提供的資料庫插入操作

對資料庫插入操作 SQLite提供瞭如下方法

public long insert(String table, String nullColumnHack, ContentValues values)

可以看到,第一個引數是table 表示表名,第二個引數通常用不到,傳入null即可,第三個引數將資料以 ContentValues鍵值對的形式儲存。比如我們在資料庫中插入一條人Person的資訊程式碼如下:

public void insert(Person person){
        ContentValues values = new ContentValues();
        values.put("name",person.getName());
        values.put("age",person.getAge());
        values.put("flag",person.getFlag());
        db.insert("Person",null,values);
    }

其中ContentValues是以鍵值對的形式儲存資料,上面程式碼中的key 分別對應資料庫中的每一列的欄位,vaule分別對應著該列的值。你是否發現Person類中有幾個屬性就得寫多少行values.put(key,value);加入它有10個欄位需要儲存到資料庫中,你是否覺得這樣很麻煩呢?覺得麻煩就對了,接下來我們利用反射來一步完成以上資料庫插入操作。

資料庫插入框架

資料庫插入操作框架可以減輕你寫程式碼量,讓你一步完成資料庫插入操作而無須關注其內部繁瑣的操作。同樣利用java反射來實現以上效果。程式碼如下:

 /**
     * 插入一條資料
     *
     * @param obj
     * @return 返回-1代表插入資料庫失敗,否則成功
     * @throws IllegalAccessException
     */
    public long insert(Object obj) {
        Class> modeClass = obj.getClass();
        Field[] fields = modeClass.getDeclaredFields();
        ContentValues values = new ContentValues();        for (Field fd : fields) {
            fd.setAccessible(true);
            String fieldName = fd.getName();            //剔除主鍵id值得儲存,由於框架預設設定id為主鍵自動增長
            if (fieldName.equalsIgnoreCase("id") || fieldName.equalsIgnoreCase("_id")) {                continue;
            }
            putValues(values, fd, obj);
        }        return db.insert(DBUtils.getTableName(modeClass), null, values);
    }

............ /**
     * put value to ContentValues for Database
     *
     * @param values ContentValues object
     * @param fd     the Field
     * @param obj    the value
     */
    private void putValues(ContentValues values, Field fd, Object obj) {
        Class> clazz = values.getClass();        try {
            Object[] parameters = new Object[]{fd.getName(), fd.get(obj)};
            Class>[] parameterTypes = getParameterTypes(fd, fd.get(obj), parameters);
            Method method = clazz.getDeclaredMethod("put", parameterTypes);
            method.setAccessible(true);
            method.invoke(values, parameters);
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }

有以上框架之後,我們現在來向資料庫插入一條資料程式碼如下:

Person person = new Person("Tom",18,false);
DBManager.insert(person);

哇!如此簡單,一行程式碼解決繁瑣的插入操作。我們只需要傳入Person物件的例項作為引數即可完成資料庫插入操作。再也不用去構建什麼ContentVaules鍵值對了。

資料庫操作–查詢

android提供的資料庫查詢

android 的sqlite資料庫提供的查詢語句有rawQuery()方法。該方法的定義如下:

public Cursor rawQuery(String sql, String[] selectionArgs)

其中第一個引數是sql字串,第二個引數是用於替換SQL語句中佔位符(?)的字串陣列。返回結果存放在Cursor物件當中,我們只要迴圈一一取出資料即可。當然我們平時不怎麼用這個方法,因為需要記住很多資料庫查詢語句的規則等。Android給開發者封裝了另外一個資料庫查詢方法,即SQLiteDatabase中的query()方法。該方法的定義如下:

public Cursor query(String table, String[] columns, String selection,            String[] selectionArgs, String groupBy, String having,            String orderBy)

其中,

第一個引數是需要查詢資料的表名稱;

第二個引數指查詢表中的那幾列欄位,如果不指定則預設查詢所有列;

第三個引數是sql語句,表示查詢條件;

第四個引數是用於替換第三個引數sql語句中的佔位符(?)陣列,如果第三,四個引數不指定則預設查詢所有行;

第五個引數用於指定需要去group by的列,不指定則表示不對查詢結果進行group by操作。

第六個引數用於對group by之後的資料進行進一步的過濾,不指定則表示不進行過濾。

第七個引數用於指定查詢結果的排序方式,不指定則表示使用預設的排序方式。

query()方法的引數是不是很多,一般人都很難記住這些引數的意思,在用的時候就很不方便,比如你要查詢資料庫中 age=18的人,你的程式碼得這麼寫:

 Cursor cursor = db.query("Person", null, "age = ?", new String[]{"18"}, null, null, null);

第三個引數是查詢條件,去約束查詢結果 age = 18,所以 第三個引數是“age= ?”,第四個引數用於替換第三個引數的佔位符(?),因此是String的陣列。查詢的結果儲存在Cursor中,為了拿到查詢結果,我們不得不去變數裡Cursor一一取出其中的資料並儲存。程式碼如下:

List list = new ArrayList();        if (cursor != null && cursor.moveToFirst()) {            do {
                Person person = new Person();
                int id = cursor.getInt(cursor.getColumnIndex("id"));
                String name = cursor.getString(cursor.getColumnIndex("name"));
                String age = cursor.getString(cursor.getColumnIndex("age"));
                boolean flag = cursor.getInt(cursor.getColumnIndex("flag")) == 1 ? true : false;
                person.setId(id);
                person.setName(name);
                person.setAge(age);
                person.setFlag(flag);                list.add(person);
            } while (cursor.moveToNext());

        }

為了取得Cursor中的查詢結果,我們寫了如此多的繁瑣的程式碼,如果此時有一個新的Student類,那麼你是否又要去修改這個查詢方法呢?如此看來該查詢方法和取得結果是不是沒有通用性,很不方便使用。對於討厭敲重複程式碼的來說這樣很麻煩,用的不爽,那麼有沒有一種方法直接將查詢結果轉換成我需要的類的集合呢?這裡我們又要用到自己寫的查詢框架了,利用該框架一行程式碼即可搞定所有。

資料庫查詢框架

1.查詢資料庫中所有資料

  /**
     * 查詢資料庫中所有的資料
     *
     * @param clazz
     * @param    以 List的形式返回資料庫中所有資料
     * @return 返回list集合
     * @throws IllegalAccessException
     * @throws InstantiationException
     * @throws NoSuchMethodException
     * @throws InvocationTargetException
     */
    public  List findAll(Class clazz) {
        Cursor cursor = db.query(clazz.getSimpleName(), null, null, null, null, null, null);        return getEntity(cursor, clazz);
    }

.....................    /**
     * 從資料庫得到實體類
     *
     * @param cursor
     * @param clazz
     * @param 
     * @return
     */
    private  List getEntity(Cursor cursor, Class clazz) {
        List list = new ArrayList();        try {            if (cursor != null && cursor.moveToFirst()) {                do {
                    Field[] fields = clazz.getDeclaredFields();
                    T modeClass = clazz.newInstance();                    for (Field field : fields) {
                        Class> cursorClass = cursor.getClass();
                        String columnMethodName = getColumnMethodName(field.getType());
                        Method cursorMethod = cursorClass.getMethod(columnMethodName, int.class);

                        Object value = cursorMethod.invoke(cursor, cursor.getColumnIndex(field.getName()));                        if (field.getType() == boolean.class || field.getType() == Boolean.class) {                            if ("0".equals(String.valueOf(value))) {                                value = false;
                            } else if ("1".equals(String.valueOf(value))) {                                value = true;
                            }
                        } else if (field.getType() == char.class || field.getType() == Character.class) {                            value = ((String) value).charAt(0);
                        } else if (field.getType() == Date.class) {                            long date = (Long) value;                            if (date 

查詢所有資料並且自動儲存在List中返回,無須使用者去將Cursor解析成物件封裝。簡單易用,自需要一個方法一個引數即可。呼叫程式碼如下:

List list = dbManager.findAll(Person.class);

超級簡單啊!

2.查詢指定條件的資料

 /**
     * 根據指定條件返回滿足條件的記錄
     *
     * @param clazz      類
     * @param select     條件語句 :("id>?")
     * @param selectArgs 條件(new String[]{"0"}) 查詢id=0的記錄
     * @param         型別
     * @return 返回滿足條件的list集合
     */
    public  List findByArgs(Class clazz, String select, String[] selectArgs) {
        Cursor cursor = db.query(clazz.getSimpleName(), null, select, selectArgs, null, null, null);        return getEntity(cursor, clazz);
    }

3.根據指定id查詢一條資料

/**
     * 透過id查詢制定資料
     *
     * @param clazz 指定類
     * @param id    條件id
     * @param    型別
     * @return 返回滿足條件的物件
     */
    public  T findById(Class clazz, int id) {
        Cursor cursor = db.query(clazz.getSimpleName(), null, "id=" + id, null, null, null, null);        List list = getEntity(cursor, clazz);        return list.get(0);
    }

使用者程式碼呼叫如下:

 Person p = dbManager.findById(Person.class, 1);

查詢id=1的資料,第一個引數為Person型別,第二個引數為id值,查詢結果直接儲存在Person物件p裡。

以上就是自己封裝的資料庫查詢操作,簡單易用,無須記住quary()方法中的那麼多引數,也無須自己去一個個解析Cursor資料並儲存。該方法一步到位,直接返回Person型別的list集合。註釋:其中用到的一些方法我暫時沒有貼出來,文章最後我會把例子和程式碼都貼出來。

資料庫刪除操作

android提供的刪除

android系統提供了sqlite資料庫刪除方法 delete(),其定義如下:

public int delete(String table, String whereClause, String[] whereArgs)

其中,第一個參數列示表名,第二個引數是條件SQL語句,第三個引數是替換第二個引數中的佔位符(?)。假如我要刪除Person表中的age=18的資料,則程式碼呼叫如下:

db.delete("Person","age = ?",new String[]{"18"});

資料庫刪除框架

刪除這一塊比較簡單,我直接貼出程式碼來

 /**
     * 刪除記錄一條記錄
     *
     * @param clazz 需要刪除的類名
     * @param id    需要刪除的 id索引
     */
    public void deleteById(Class> clazz, long id) {
        db.delete(DBUtils.getTableName(clazz), "id=" + id, null);
    }

使用者呼叫如下:

dbManager.deleteById(Person.class, 1);

第一個 引數是Person類的型別,第二個引數是被刪除資料的id。是不是很簡單呢?它的實現如下:

   /**
     * 刪除記錄一條記錄
     *
     * @param clazz 需要刪除的類名
     * @param id    需要刪除的 id索引
     */
    public void deleteById(Class> clazz, long id) {
        db.delete(DBUtils.getTableName(clazz), "id=" + id, null);
    }

資料庫更新操作

android提供的更新操作

在android的sqlite中提供了update()方法來更新資料操作,其定義如下:

public int update(String table, ContentValues values, String whereClause, String[] whereArgs)

update()方法接收四個引數,第一個引數是表名,第二個引數是一個封裝了待修改資料的ContentValues物件,第三和第四個引數用於指定修改哪些行,對應了SQL語句中的where部分。比如我要修改id=1的Person人的年齡age改成20,那麼程式碼實現如下:

 ContentValues values = new ContentValues();
        values.put("age",20);
        db.update("Person",values,"id = ?",new String[]{"1"});

該方法也算比較簡單,那麼我們來看看自己寫的資料庫框架是怎麼實現的呢?

資料庫框架更新操作

 ContentValues values = new ContentValues();
        values.put("age", 34);
        dbManager.updateById(Person.class, values, 1);

第一個引數為Person類的型別,第二個引數為需要更新的vaules,第三個引數是條件,更新id為1的資料。用法很簡單,它的實現如下:

 /**
     * 更新一條記錄
     *
     * @param clazz  類
     * @param values 更新物件
     * @param id     更新id索引
     */
    public void updateById(Class> clazz, ContentValues values, long id) {
        db.update(clazz.getSimpleName(), values, "id=" + id, null);
    }

自此,資料庫的基本操作都羅列出來了,也說明了Android提供的sqlite資料庫在平時開發中的一些繁瑣的地方,所以自己總結提取了一個簡單型的資料庫操作框架,僅僅是比較簡單的操作,如果你有資料量大的操作,請出門左轉利用其他多功能成熟穩定的資料庫開源框架。該框架只適合資料量小,不存在表與表之間的對應關係,可以將查詢結果直接轉換成物件的輕量級框架。

原始碼以及示例地址: 

原文連結:

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/2144/viewspace-2805371/,如需轉載,請註明出處,否則將追究法律責任。

相關文章