自己動手寫Android數據庫框架

scorpio300 8年前發布 | 52K 次閱讀 數據庫 Android Android開發 移動開發

來自: http://finalshares.com/read-6711

自己動手寫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) {
}

}</pre>

當數據庫創建時系統會調用其中的 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);
}
...

}</pre>

由此我們的數據庫幫助類就完成了,接下來是這么使用的:

    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();
}</pre> 

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

public static final String CREATE_TABLE = "create table Person ("

        + "id integer primary key autoincrement, "
        + "name text, "
        + "age integer, "
        + "flag boolean)";</pre> 

這里表的字段只有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();
    }</pre> 

工具類代碼如下:

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 : "";
    

    } }</pre>

    如此一來,用戶創建數據庫表就變的很簡單了,傳入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));
    
     ..............
    

    }</pre>

    是不是很簡單!!!領導再也不用擔心我不會創建數據庫了。

    數據庫操作–插入

    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);
    

    }</pre>

    其中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();
    }
}</pre> 

有以上框架之后,我們現在來向數據庫插入一條數據代碼如下:

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之后的數據進行進一步的過濾,不指定則表示不進行過濾。 

第七個參數用于指定查詢結果的排序方式,不指定則表示使用默認的排序方式。

</div>

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

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

第三個參數是查詢條件,去約束查詢結果 age = 18,所以 第三個參數是“age= ?”,第四個參數用于替換第三個參數的占位符(?),因此是String的數組。查詢的結果保存在Cursor中,為了拿到查詢結果,我們不得不去變量里Cursor一一取出其中的數據并保存。代碼如下:

List<Person> 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());

    }</pre> 

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

數據庫查詢框架

1.查詢數據庫中所有數據

  /**

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

.....................

/**
 * 從數據庫得到實體類
 *
 * @param cursor
 * @param clazz
 * @param <T>
 * @return
 */
private <T> List<T> getEntity(Cursor cursor, Class<T> clazz) {
    List<T> 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 <= 0) {
                            value = null;
                        } else {
                            value = new Date(date);
                        }
                    }
                    String methodName = makeSetterMethodName(field);
                    Method method = clazz.getDeclaredMethod(methodName, field.getType());
                    method.invoke(modeClass, value);
                }
                list.add(modeClass);
            } while (cursor.moveToNext());
        }

    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (cursor != null) {
            cursor.close();
        }
    }
    return list;
}</pre> 

查詢所有數據并且自動保存在List中返回,無須用戶去將Cursor解析成對象封裝。簡單易用,自需要一個方法一個參數即可。調用代碼如下:

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

超級簡單啊!

2.查詢指定條件的數據

 /**

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

3.根據指定id查詢一條數據

/**

 * 通過id查找制定數據
 *
 * @param clazz 指定類
 * @param id    條件id
 * @param <T>   類型
 * @return 返回滿足條件的對象
 */
public <T> T findById(Class<T> clazz, int id) {
    Cursor cursor = db.query(clazz.getSimpleName(), null, "id=" + id, null, null, null, null);
    List<T> list = getEntity(cursor, clazz);
    return list.get(0);
}</pre> 

用戶代碼調用如下:

 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);
}</pre> 

用戶調用如下:

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);
}</pre> 

數據庫更新操作

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);
}</pre> 

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

源碼以及示例地址: DataBaseDemo

</div>

 本文由用戶 scorpio300 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!