手擼一個簡易Android資料庫框架

GitLqr發表於2019-03-04

一、簡述

眾所周知,移動端(不管是Android還是iOS)使用的資料庫是Sqlite,這種小型的資料庫很適合移動端儲存大量的資料,使用上也跟mysql基本無差,但官方提供的API在操作性方面真不咋的,你必須掌握一定程度的sql語句,否則將很難駕馭。所以,有很多第三方的資料庫框架就開始流行,如:GreenDao、Litepal等。這些ORM資料庫框架,可以幫助開發者節省大量編寫資料庫操作程式碼的時間,只需對物件進行賦值操作,便能作用到資料庫上,方便我們開發更加複雜的業務邏輯。本篇的主題就是做一個簡易的資料庫框架,使用設計模式、泛型、註解、反射這些高階技巧來實現。

二、資料庫常用操作

資料庫操作無非就是增刪改查(CRUD),而且一般運算元據庫表的類稱為Dao類,所以可以為這些Dao類抽取一個共同的介面:

public interface IBaseDao<M> {

    Long insert(M entity);

    Integer delete(M where);

    Integer update(M entitiy, M where);

    List<M> query(M where);

    List<M> query(M where, String orderBy);

    List<M> query(M where, String orderBy, Integer page, Integer pageCount);

}複製程式碼

我們要做的資料庫框架也是一個ORM框架,表現層不涉及任何sql語句,直接操作的是資料物件,但具體的資料型別在這個介面中並不清楚,所以使用泛型來表示。

三、Dao類工廠

一個程式,一般只有一個資料庫,一個資料庫中會包含多張表,例如使用者表,許可權表等,這就意味著,專案中,Dao類會有多個,因為資料庫操作無非是CRUD,所以可以確定它們的結構相同,只是具體操作的表與欄位不同(即資料型別不同),所以,使用“泛型+工廠”來生產這些Dao類最合適不過。下面先貼出Dao類工廠程式碼,再一一分析:

public class BaseDaoFactory {

    private static String mDbPath;
    private SQLiteDatabase mDatabase;

    private static class Instance {
        public static BaseDaoFactory INSTANCE = new BaseDaoFactory();
    }

    public static BaseDaoFactory getInstance() {
        return Instance.INSTANCE;
    }

    // 初始化資料庫位置
    public static void init(String dbPath) {
        mDbPath = dbPath;
    }

    public BaseDaoFactory() {
        if (TextUtils.isEmpty(mDbPath)) {
            throw new RuntimeException("在使用BaseDaoFactory之前,請呼叫BaseDaoFactory.init()初始化好資料庫路徑。");
        }
        // 開啟資料庫,得到資料庫物件
        mDatabase = SQLiteDatabase.openOrCreateDatabase(mDbPath, null);
    }

    public <T extends BaseDao<M>, M> T getDataHelper(Class<T> clazz, Class<M> entity) {
        T baseDao = null;
        try {
            baseDao = clazz.newInstance();
            baseDao.init(mDatabase, entity);
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
        return baseDao;
    }
}複製程式碼

1、資料庫位置

使用SQLiteDatabase,可以在任意位置建立或開啟一個資料庫,這樣的好處是:如果,用SQLiteOpenHelper來建立資料庫,預設會將資料庫檔案建立到/data/data/包名/databases目錄下,當應用被刪除時,資料庫也將同應用一起被刪除,有時我們會有這樣的需求,在使用者重灌安裝App時,可以使用之前的資料庫資訊,例如:UC瀏覽器的書籤本地恢復功能。

// 初始化資料庫位置
public static void init(String dbPath) {
    mDbPath = dbPath;
}

public BaseDaoFactory() {
    if (TextUtils.isEmpty(mDbPath)) {
        throw new RuntimeException("在使用BaseDaoFactory之前,請呼叫BaseDaoFactory.init()初始化好資料庫路徑。");
    }
    // 開啟資料庫,得到資料庫物件
    mDatabase = SQLiteDatabase.openOrCreateDatabase(mDbPath, null);
}複製程式碼

本框架可以讓開發者自定義資料庫的存放位置,因為在建構函式中會使用到該路徑對資料庫進行建立,所以這裡使用靜態方法的方式,在Dao工廠例項化之前先對其(mDbPath)進行賦值。這種方式在很多第三方框架的原始碼中很是常見,一般在自定義的Application中對通過呼叫框架的init()方法對框架中必需的變數進行賦值。

2、工廠單例

該框架中的Dao類工廠只要一個就夠了,所以需要用到單例模式,常見的單例模式有餓漢式和懶漢式,這裡選用靜態內部類單例模式,原因是什麼呢?

1)餓漢式

餓漢式在類載入時就已經初始化好了,不管專案中是否使用,都會佔用記憶體,雖然效率高,但開發中一般不用。

public class BaseDaoFactory {
    private static BaseDaoFactory Instance = new BaseDaoFactory();
    public static BaseDaoFactory getInstance() {
        return Instance;
    }
}複製程式碼

2)懶漢式

懶漢式雖然解決了餓漢式的弊端,實現呼叫時建立單例,但執行緒不安全,當然我們可以使用雙重檢測機制來解決,但這樣也降低了效率(至少第一次初始化時需要同步,降低了效率),是開發中最常見的單例實現方式。

public class BaseDaoFactory {
    private static BaseDaoFactory mInstance;
    public static BaseDaoFactory getInstance() {
        if (mInstance == null) {
            synchronized (BaseDaoFactory.class) {
                if (mInstance == null) {
                    mInstance = new BaseDaoFactory();
                }
            }
        }
        return mInstance;
    }
}複製程式碼

3)靜態內部類單例

靜態內部類單例綜合了前面兩者的優點,即呼叫時建立單例,效率高且沒有執行緒安全問題。當在呼叫getInstance()方法建立工廠單例時,靜態內部類Instance才會被載入,同時初始化內部類屬性INSTANCE,即初始化外部類BaseDaoFactory物件(Dao工廠),因為該靜態內部類只會載入一次,所以該INSTANCE物件也只會被建立一次。

private static class Instance {
    public static BaseDaoFactory INSTANCE = new BaseDaoFactory();
}

public static BaseDaoFactory getInstance() {
    return Instance.INSTANCE;
}

// 單例模式,一般會將建構函式私有化,以保證不會被外界初始化。
private BaseDaoFactory() {
   ...
}複製程式碼

3、生產Dao類

Dao工廠會提供一個public方法來供外界獲取需要的Dao類,而外界只需要傳入具體Dao類和資料實體類對應的class即可。

public <T extends BaseDao<M>, M> T getDataHelper(Class<T> clazz, Class<M> entity) {
    T baseDao = null;
    try {
        baseDao = clazz.newInstance();
        baseDao.init(mDatabase, entity);
    } catch (InstantiationException e) {
        e.printStackTrace();
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return baseDao;
}複製程式碼

可能這部分程式碼比較迷的地方就是, M>了(先拋開方法體中的具體實現),其實這只是方法宣告泛型的方式而已。我們知道,類上宣告泛型很簡單,只需要在類後面加上(字母隨意)即可,如:

// 類上泛型可以宣告多個
public class Person<M,T> {
    ...
}複製程式碼

而方法上宣告泛型跟類上宣告泛型有些區別——泛型宣告的位置不同,方法上的泛型需要在修飾符(public等)與返回值之間宣告,如:

public <T> void eat(){
    ...
}複製程式碼

上面的程式碼不會報錯,但一點意義都沒有,因為泛型沒被使用到,一般方法上宣告的泛型可作為方法引數型別和返回值型別,如:

// 同樣,方法泛型也可以宣告多個
public <T,M> T eat(T t, M m){
    ...
}複製程式碼

可以這樣認為,預設泛型表示的是Object(編碼時),有時我們需要更加精確泛型的型別,這可以通過extends辦到,如:

public <T extends Person> void doSomething(T t){
    ...
}複製程式碼

這時,傳入doSomething()的引數必須是Person的子類(Boy或Girl),且在編碼時,可以在方法體中使用t呼叫Person中宣告的方法,如eat(),如果不使用extends來指定泛型T的具體型別,那麼在編碼時,t會被認為是Object型別,也就沒法呼叫eat()這類自定義的方法了。到這裡,回頭再看上面的程式碼,應該就不會迷了。

四、資料庫操作封裝

前面一開始的時候就已經為Dao類抽取了一個共同的介面,規範了Dao類的基本操作,而且,我們不想在具體的Dao類中直接運算元據庫,所以,在這兩者中間必須有一層來完成資料庫操作,並對各操作方法進行封裝,它就是BaseDao,以下是該類中可供外界呼叫的方法:

public abstract class BaseDao<M> implements IBaseDao<M> {
    protected boolean init(SQLiteDatabase database, Class<M> entity) {
        ...
    }
    @Override
    public Long insert(M entity) {
        ...
    }
    @Override
    public Integer delete(M where) {
        ...
    }

    @Override
    public Integer update(M entitiy, M where) {
        ...
    }

    @Override
    public List<M> query(M where) {
        ...
    }

    @Override
    public List<M> query(M where, String orderBy) {
        ...
    }

    @Override
    public List<M> query(M where, String orderBy, Integer page, Integer pageCount) {
        ...
    }
}複製程式碼

在框架設計中,要切記,不需要被外界(表現層)所知的方法或屬性請儘量私有化,一來對框架安全,二來避免團隊開發中不必要的衝突。因為BaseDao的init()方法只被DaoFactory呼叫,且兩者均在同包下,故使用protected修飾。

1、自定義註解:TbName和TbField

在對BaseDao進行解析前,先來說說兩個十分重要的註解——TbName和TbField。幾乎市面上所有的ORM資料庫框架,都會用到自定義註解來對一個資料實體進行描述,比如User類的類名對應表的表名,有可能是user,也有可能是t_user,那開發者就可以使用框架提供的註解(TbName)來進行自定義表名了,同理,類中的屬性名對應表的欄位名也是如此,不過對於表的初始化還需要知道表欄位的長度,所以表欄位註解(TbField)還多了一個length屬性。

/**
 * 表名註解
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TbName {
    String value();
}

/**
 * 表欄位註解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TbField {
    String value();
    int length();
}複製程式碼

一方面,BaseDao對TbName和TbField的解析是處於程式執行階段,所以這2個註解必須是執行時可見,即Retention的值必須是RetentionPolicy.RUNTIME。
另一方面,TbName是要註解在類上的,所以其Target的值是ElementType.TYPE;而TbField則是註解在類屬性上的,所以其Target的值是ElementType.FIELD。

2、Dao類初始化

在Dao工廠中就已經使用了這個BaseDao的init()方法,來對Dao類進行一些通用的初始化工作,下面就來看看它都初始化了什麼:

/**
 * 初始化表操作物件,一般包括:建立表、獲取表欄位與類欄位的對映關係
 */
protected boolean init(SQLiteDatabase database, Class<M> entity) {
    mDatabase = database;
    mEntityClass = entity;

    // 往後的操作必須是基於資料庫已經開啟的前提下
    if (!database.isOpen()) {
        return false;
    }

    // 獲取表名
    TbName tbName = entity.getAnnotation(TbName.class);
    mTbName = tbName == null ? entity.getSimpleName() : tbName.value();

    // 獲取表對映欄位
    if (!genFieldMap()) {
        return false;
    }

    // 建立資料庫
    if (!createTable(database)) {
        return false;
    }

    return true;
}複製程式碼

在這個init()方法中,可以看到,註解TbName率先被該框架使用到了,當開發者有使用該註解時,表名以TbName註解中的值為表名,否則以類名作為表名。

註解是“靜態的”,在類載入時就已經固定了,也就是說執行時無法修改其值(javassist方式除外),所以,可以利用類的class物件,通過 getAnnotation(註解.class).註解屬性() 這種方法也獲取註解的屬性值。

1)獲取表欄位與類欄位的對映關係

我們知道,類的屬性名可能會與表的欄位名不同,而BaseDao中的很多後續操作都會跟這兩者打交道,所以,在BaseDao的初始化過程中將這兩者的關係使用Map進行儲存,方便後續的各種操作。

private boolean genFieldMap() {
    mFieldMap = new HashMap<>();
    Field[] fields = mEntityClass.getFields();
    if (fields == null || fields.length == 0) {
        Log.e(TAG, "獲取不到類中欄位");
        return false;
    }
    for (Field field : fields) {
        field.setAccessible(true);
        TbField tbField = field.getAnnotation(TbField.class);
        mFieldMap.put(tbField == null ? field.getName() : tbField.value(), field);
    }
    return true;
}    複製程式碼

反射中的幾個小知識點:

  • Field[] fields = mEntityClass.getFields();// 得到類中的public欄位,包括父類。
  • Field[] fields = mEntityClass.getDeclaredFields();// 得到類中宣告的欄位(不管是public、protected、private),不包括父類。
  • field.setAccessible(true);// 將私有屬性或final屬性可以被訪問

考慮到資料實體類可能會使用繼承的方式來擴充父類,即會用到父類的屬性值,所以使用getFields()方法,但代價就是實體類中的屬性必須是public的。

2)建立表

Dao類的初始化工作也包括了表的建立。一方面,因為不能在每次建立並初始化Dao類時都去重新建立一次表,所以這裡就用到了sql語句中的 if not exists 關鍵字來避免重複建立表的問題。另一方面,其實建立表的sql語句是一種模板,兩個不同的表在使用sql建立時,無非就是表名、欄位名、欄位型別和欄位長度不同,而恰好,這些不同的元素可以使用反射+TbField註解來獲取,從而實現sql語句的動態拼接,結合上一步得到的表欄位與類欄位的對映關係(mFieldMap),程式碼可以這麼寫:

/**
 * 建立表(可以被子類重寫,方便靈活擴充套件)
 */
protected boolean createTable(SQLiteDatabase database) {
    StringBuilder sb = new StringBuilder();
    for (Map.Entry<String, Field> entry : mFieldMap.entrySet()) {
        String columnName = entry.getKey();
        Field field = entry.getValue();
        TbField tbField = field.getAnnotation(TbField.class);
        int length = tbField == null ? 255 : tbField.length();
        String type = "";
        Class<?> fieldType = field.getType();
        if (fieldType == String.class) {
            type = "varchar";
        } else if (fieldType == int.class || fieldType == Integer.class) {
            type = "int";
        } else if (fieldType == double.class || fieldType == Double.class) {
            type = "double";
        } else if (fieldType == float.class || fieldType == Float.class) {
            type = "float";
        }
        if (TextUtils.isEmpty(type)) {
            Log.e(TAG, type.getClass().getName() + "是不支援的欄位");
        } else {
            sb.append(columnName + " " + type + "(" + length + "),");
        }
    }
    sb.deleteCharAt(sb.lastIndexOf(","));
    String s = sb.toString();
    if (TextUtils.isEmpty(s)) {
        Log.e(TAG, "獲取不到表欄位資訊");
        return false;
    }
    String sql = "create table if not exists " + mTbName + " (" + s + ") ";
    Log.e(TAG, sql);
    database.execSQL(sql);
    return true;
}複製程式碼

到這裡,Dao類的初始化工作就完了,下面進行CRUD的封裝。

3、增

我們知道,若使用原生的SQLiteDatabase將資料插入到表中,需要將資料先封裝成ContentValues物件,再呼叫其insert()方法來執行資料插入操作。那麼,現在我們擁有了一個資料實體,要做的,就是將這個資料實體轉成ContentValues物件,再使用SQLiteDatabase的insert()方法來執行插入,可以說我們就是對SQLiteDatabase進行封裝,總結上面的理論,程式碼分如下三步:

  1. 將物件中的屬性轉成鍵值對values。
  2. 將鍵值對values轉成ContentValues物件。
  3. 使用SQLiteDatabase的insert()方法進行資料插入。

結合mFieldMap,實現表資料插入的程式碼可以這麼寫:

@Override
public Long insert(M entity) {
    try {
        Map<String, String> values = getValues(entity);
        ContentValues cv = getContentValues(values);
        return mDatabase.insert(mTbName, null, cv);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return 0L;
}

/**
 * 將物件中的屬性轉成鍵值對(列名--值)
 */
private Map<String, String> getValues(M entity) throws IllegalAccessException {
    Map<String, String> result = new HashMap<>();
    for (Map.Entry<String, Field> entry : mFieldMap.entrySet()) {
        Object value = entry.getValue().get(entity);
        result.put(entry.getKey(), value == null ? "" : value.toString());
    }
    return result;
}

/**
 * 將鍵值對轉成ContentValues
 */
private ContentValues getContentValues(Map<String, String> values) {
    ContentValues cv = new ContentValues();
    for (Map.Entry<String, String> val : values.entrySet()) {
        cv.put(val.getKey(), val.getValue());
    }
    return cv;
}複製程式碼

4、刪

要實現刪除表資料功能,需要使用到SQLiteDatabase的delete()方法,其中whereClause和whereArgs是關鍵。又因為該框架是一個ORM框架,在表現層需要將刪除條件使用資料實體進行封裝,而框架內部則是對傳入的資料實體進行解析,將物件中屬性值不為null的屬性拿出來作為刪除的條件(這也意味著常見的資料型別不能用了,如int,但可以使用Integer來替換),可分為兩步:

  1. 將物件中的屬性轉成鍵值對whereMap。
  2. 使用Condition類的建構函式對whereMap中value不為null的鍵值對取出來拼接。

綜上所述,BaseDao的表資料刪除實現程式碼如下:

@Override
public Integer delete(M where) {
    try {
        Map<String, String> whereMap = getValues(where);
        Condition condition = new Condition(whereMap);
        return mDatabase.delete(mTbName, condition.whereClause, condition.whereArgs);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return 0;
}

class Condition {
    public Condition(Map<String, String> whereMap) {

        StringBuilder sb = new StringBuilder();
        List<String> list = new ArrayList<>();

        for (Map.Entry<String, String> entry : whereMap.entrySet()) {
            if (!TextUtils.isEmpty(entry.getValue())) {
                sb.append("and " + entry.getKey() + "=? ");
                list.add(entry.getValue());
            }
        }
        this.whereClause = sb.delete(0, 4).toString();
        this.whereArgs = list.toArray(new String[list.size()]);
    }

    String whereClause;
    String[] whereArgs;
}複製程式碼

whereClause是刪除條件,是個字串,需要使用?來作為佔位符,多個條件需要使用and關鍵字連線,如:name=? and password=?

whereArgs則是對whereClause中佔位符進行數值替換的字型串陣列,如:new String[]{“LQR”,”123456″}

5、改

通過前面對錶資料增和刪的程式碼實現,更新資料部分就比較好理解了,因為SQLiteDatabase的update()方法需要用到的引數有ContentValues物件,whereClause和whereArgs,其實就是將增和刪的程式碼實現相加起來而已,這就不多廢話,實現的程式碼如下:

@Override
public Integer update(M entitiy, M where) {
    try {
        Map<String, String> values = getValues(entitiy);
        ContentValues cv = getContentValues(values);

        Map<String, String> whereMap = getValues(where);
        Condition condition = new Condition(whereMap);

        return mDatabase.update(mTbName, cv, condition.whereClause, condition.whereArgs);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return 0;
}複製程式碼

6、查

終於到了CRUD的最後一步:資料查詢(Retrieve)。可以說這是資料庫操作中最重要且使用頻率最高的一部分了,同樣的,表資料查詢還是用到了SQLiteDatabase,使用其query()方法來進行表資料查詢,它的引數也比較多,這裡就只封裝三種查詢:

  1. 將符合條件的表資料全部查詢出來。
  2. 將符合條件的表資料查詢出來,並可以排序。
  3. 將符合條件的表資料查詢出來,除了可以排序,還可以分頁查詢。

需要注意的就是分頁查詢,因為SQLiteDatabase的第一頁是從0開始的,而我希望的是表現層從1開始,所以框架程式碼中會對其進行自減處理。這三個方法的程式碼具體實現如下:

@Override
public List<M> query(M where) {
    return query(where, null);
}

@Override
public List<M> query(M where, String orderBy) {
    return query(where, orderBy, null, null);
}

@Override
public List<M> query(M where, String orderBy, Integer page, Integer pageCount) {
    List<M> list = null;
    Cursor cursor = null;
    try {
        String limit = null;
        if (page != null && pageCount != null) {
            int startIndex = --page;
            limit = (startIndex < 0 ? 0 : startIndex) + "," + pageCount;
        }

        if (where != null) {
            Map<String, String> whereMap = getValues(where);
            Condition condition = new Condition(whereMap);
            cursor = mDatabase.query(mTbName, null, condition.whereClause, condition.whereArgs, null, null, orderBy, limit);
        } else {
            cursor = mDatabase.query(mTbName, null, null, null, null, null, orderBy, limit);
        }

        // 將查詢出來的表資料轉成物件集合
        list = getDataList(cursor);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    } catch (InstantiationException e) {
        e.printStackTrace();
    } finally {
        if (cursor != null) {
            cursor.close();
            cursor = null;
        }
    }
    return list;
}

/**
 * 通過遊標,將表中資料轉成物件集合
 */
private List<M> getDataList(Cursor cursor) throws IllegalAccessException, InstantiationException {
    if (cursor != null) {
        List<M> result = new ArrayList<>();
        // 遍歷遊標,獲取表中一行行的資料
        while (cursor.moveToNext()) {

            // 建立物件
            ParameterizedType pt = (ParameterizedType) this.getClass().getGenericSuperclass();// 獲取當前new的物件的 泛型的父類 型別
            Class<M> clazz = (Class<M>) pt.getActualTypeArguments()[0];// 獲取第一個型別引數的真實型別
            M item = clazz.newInstance();

            // 遍歷表欄位,使用遊標一個個取值,賦值給新建立的物件。
            Iterator<String> iterator = mFieldMap.keySet().iterator();
            while (iterator.hasNext()) {
                // 找到表欄位
                String columnName = iterator.next();
                // 找到表欄位對應的類屬性
                Field field = mFieldMap.get(columnName);

                // 根據類屬性型別,使用遊標獲取表中的值
                Object val = null;
                Class<?> fieldType = field.getType();
                if (fieldType == String.class) {
                    val = cursor.getString(cursor.getColumnIndex(columnName));
                } else if (fieldType == int.class || fieldType == Integer.class) {
                    val = cursor.getInt(cursor.getColumnIndex(columnName));
                } else if (fieldType == double.class || fieldType == Double.class) {
                    val = cursor.getDouble(cursor.getColumnIndex(columnName));
                } else if (fieldType == float.class || fieldType == Float.class) {
                    val = cursor.getFloat(cursor.getColumnIndex(columnName));
                }

                // 反射給物件屬性賦值
                field.set(item, val);
            }
            // 將物件新增到集合中
            result.add(item);
        }
        return result;
    }
    return null;
}複製程式碼

至此,這個簡易的資料庫框架就寫好了,下面來測試一下。

五、測試

Activity的佈局很簡單,我就不貼了,就是幾個簡單的按鈕而已。

1、測試前準備

1)User

一個簡單的資料實體類,沒什麼好說的,看程式碼。

@TbName("tb_user")
public class User {

    @TbField(value = "tb_name", length = 30)
    public String username;

    @TbField(value = "tb_password", length = 20)
    public String password;

    @TbField(value = "tb_age", length = 11)
    public Integer age;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public User(String username, String password, int age) {
        this.username = username;
        this.password = password;
        this.age = age;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "[username:" + this.username + ", password:" + this.getPassword() + ", age:" + this.getAge() + "]";
    }
}複製程式碼

2)UserDao

可以看到這個UserDao中其實沒什麼程式碼,但它可以通過重寫父類createTable()方法來更靈活的建立表,或自定義一些其它的方法來擴充套件其父類的功能。

public class UserDao extends BaseDao<User> {
    // @Override
    // protected boolean createTable(SQLiteDatabase database) {
    // database.execSQL("create table if not exists t_user(tb_name varchar(30),tb_password varchar(10))");
    // return super.createTable(database);
    // }
}複製程式碼

2、功能測試

1)初始化

這個框架需要指定一個資料庫位置,我們在Activity的onCreate()方法中呼叫框架的init()方法來指定,建議最好放到自定義的Application中。

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_customer_db_frame);
    BaseDaoFactory.init(new File(getFilesDir(), "csdn_lqr.db").getAbsolutePath());
    mUserDao = BaseDaoFactory.getInstance().getDataHelper(UserDao.class, User.class);
    mUser = new User("CSDN_LQR", "123456", 10);
}複製程式碼

2)增

往user表中插入一條username為”CSDN_LQR”,password為”123456″的資料:

public void insert(View view) {
    Long insert = mUserDao.insert(mUser);
    Toast.makeText(getApplicationContext(), "新增了" + (insert != -1 ? 1 : 0) + "條資料", Toast.LENGTH_SHORT).show();
}複製程式碼

3)刪

從user表中刪除一條username為”CSDN_LQR”的資料:

public void delete(View view) {
    User where = new User();
    where.setUsername("CSDN_LQR");
    Integer delete = mUserDao.delete(where);
    Toast.makeText(getApplicationContext(), "刪除了" + delete + "條資料", Toast.LENGTH_SHORT).show();
}複製程式碼

4)改

將user表中username為”CSDN_LQR”的資料進行修改:

public void update(View view) {
    User user = new User("LQR_CSDN", "654321", 9);

    User where = new User();
    where.setUsername("CSDN_LQR");

    Integer update = mUserDao.update(user, where);
    Toast.makeText(getApplicationContext(), "修改了" + update + "條資料", Toast.LENGTH_SHORT).show();
}複製程式碼

5)查

a. 將符合條件的表資料全部查詢出來

將user表中所有username為”CSDN_LQR”的資料全部查詢出來:

public void query1(View view) {
    User where = new User();
    where.setUsername("CSDN_LQR");

    List<User> list = mUserDao.query(where);
    int query = list == null ? 0 : list.size();
    Toast.makeText(getApplicationContext(), "查出了" + query + "條資料", Toast.LENGTH_SHORT).show();
    for (User user : list) {
        System.out.println(user);
    }
}複製程式碼

b. 將符合條件的表資料查詢出來,並排序

將user表中的資料按age的正反序分別查詢出來:

public void query2(View view) {
    List<User> list = mUserDao.query(null, "tb_age asc");
    int query = list == null ? 0 : list.size();
    Toast.makeText(getApplicationContext(), "查出了" + query + "條資料", Toast.LENGTH_SHORT).show();
    for (User user : list) {
        System.out.println(user);
    }
}

public void query3(View view) {
    List<User> list = mUserDao.query(null, "tb_age desc");
    int query = list == null ? 0 : list.size();
    Toast.makeText(getApplicationContext(), "查出了" + query + "條資料", Toast.LENGTH_SHORT).show();
    for (User user : list) {
        System.out.println(user);
    }
}複製程式碼

c. 將符合條件的表資料查詢出來,並分頁。

只查詢user表中的前2條資料:

public void query4(View view) {
    User where = new User();

    List<User> list = mUserDao.query(where, null, 1, 2);
    int query = list == null ? 0 : list.size();
    Toast.makeText(getApplicationContext(), "查出了" + query + "條資料", Toast.LENGTH_SHORT).show();
    for (User user : list) {
        System.out.println(user);
    }
}複製程式碼

大成功,撒花。

最後貼下Demo地址:

github.com/GitLqr/Simp…

相關文章