Android 本地數據存儲四 ormlite的使用和基於反射的數據庫工具封裝

一.主要講解內容

1.基於運行時註解的ormlite的使用

2.基於反射的數據庫工具封裝

3.基於編譯時註解的greendao的使用

二.github 的下載地址

https://github.com/MatrixSpring/OrmLiteDemo

三.視頻講解

 

四.原理分析

1.ormlite的使用

1.導入依賴

 implementation 'com.j256.ormlite:ormlite-android:5.0'
 implementation 'com.j256.ormlite:ormlite-core:5.0'

2.使用註解建立bean類

  1. 創建表名         @DatabaseTable(tableName = "xxxx") //xxxx指用戶自己定義的表名
  2. 綁定id字段      @DatabaseField(generatedId = true)   //設置表的id爲主鍵且自動生成
  3. 綁定普通字段  @DatabaseField(columnName = "name")
  4. bean對象的例子
@DatabaseTable(tableName = "article")
public class ArticleBean {
    // article表中各個字段的名稱
    public static final String COLUMNNAME_ID = "id";
    public static final String COLUMNNAME_TITLE = "title";
    public static final String COLUMNNAME_CONTENT = "content";
    public static final String COLUMNNAME_USER = "user_id";

    @DatabaseField(generatedId = true, columnName = COLUMNNAME_ID, useGetSet = true)
    private int id;
    @DatabaseField(columnName = COLUMNNAME_TITLE, useGetSet = true, canBeNull = false, unique = true)
    private String title;
    @DatabaseField(columnName = COLUMNNAME_CONTENT, useGetSet = true)
    private String content;
    @DatabaseField(columnName = COLUMNNAME_USER, foreign = true, foreignAutoRefresh = true, foreignAutoCreate = true,
            foreignColumnName = UserBean.COLUMNNAME_ID)
    private UserBean user_id;

    public ArticleBean() {
    }

    public ArticleBean(String title, String content, UserBean user) {
        this.title = title;
        this.content = content;
        this.user_id = user;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public UserBean getUser() {
        return user_id;
    }

    public void setUser(UserBean user) {
        this.user_id = user;
    }

    @Override
    public String toString() {
        return "ArticleBean{" +
                "id=" + id +
                ", title='" + title + '\'' +
                ", content='" + content + '\'' +
                ", user=" + user_id +
                '}';
    }
}

3.使用OrmLiteSqliteOpenHelper類,建立OrmLiteHelper幫助類繼承自OrmLiteSqliteOpenHelper類。

1.繼承後系統提示會有幾個重要的方法要實現   

@Override // 創建數據庫時調用的方法
public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) {
}

@Override // 數據庫版本更新時調用的方法
public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) {
}

// 釋放資源
@Override
public void close() {
    super.close();
}

2.對OrmLiteHelper實現單例化,採用Java中的雙重檢查鎖(double checked locking)單例 傳進來的上下文context需要使用Application的content不然會產生內存泄漏,當然裏面也可以使用(軟引用,弱引用)的activity。也可以使用內部靜態類產生對象。

3.使用map存儲APP中所有的DAO對象 private Map<String, Dao> daos = new HashMap<>();這樣在使用時通過對象名獲取到相應的操作表的對象。在close()方法中釋放map中引用的對象

4.DatabaseHelper的實現

public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
    // 數據庫名稱
    public static final String DATABASE_NAME = "mydb.db";
    // 本類的單例實例
    private static DatabaseHelper instance;
    // 存儲APP中所有的DAO對象的Map集合
    private Map<String, Dao> daos = new HashMap<>();

    // 獲取本類單例對象的方法
    public static synchronized DatabaseHelper getInstance(Context context) {
        if (instance == null) {
            synchronized (DatabaseHelper.class) {
                if (instance == null) {
                    instance = new DatabaseHelper(context);
                }
            }
        }
        return instance;
    }

    // 私有的構造方法
    private DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, 1);
    }

    // 根據傳入的DAO的路徑獲取到這個DAO的單例對象(要麼從daos這個Map中獲取,要麼新創建一個並存入daos)
    public synchronized Dao getDao(Class clazz) throws SQLException {
        Dao dao = null;
        String className = clazz.getSimpleName();
        if (daos.containsKey(className)) {
            dao = daos.get(className);
        }
        if (dao == null) {
            dao = super.getDao(clazz);
            daos.put(className, dao);
        }
        return dao;
    }

    @Override // 創建數據庫時調用的方法
    public void onCreate(SQLiteDatabase database, ConnectionSource connectionSource) {
        try {
            TableUtils.createTable(connectionSource, UserBean.class);
            TableUtils.createTable(connectionSource, ArticleBean.class);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override // 數據庫版本更新時調用的方法
    public void onUpgrade(SQLiteDatabase database, ConnectionSource connectionSource, int oldVersion, int newVersion) {
        try {
            TableUtils.dropTable(connectionSource, UserBean.class, true);
            TableUtils.dropTable(connectionSource, ArticleBean.class, true);
            onCreate(database, connectionSource);
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    // 釋放資源
    @Override
    public void close() {
        super.close();
        for (String key : daos.keySet()) {
            Dao dao = daos.get(key);
            dao = null;
        }
    }
}

2.基於反射的數據庫工具封裝

1.思維設計

對於設計數據庫封裝工具。我們首先得把一些場景和條件或者說約束列出來

1.提供的一系列的數據接口,都需要提供統一的方法 增刪改查批量的增加減少。於是就可以抽象出統一的接口來規範這些操作。

2.對於這個數據接口的實現類,我們想一般需要從外面傳進來兩個參數,一個是數據庫操作的類,一個是這個數據接口封裝的對象。

3.對外使用時我們需要調一個統一的入口,返回我們需要的帶有上述增刪改查統一接口方法的對象。這時我們想到了單例模式和工廠模式。

4.由於數據庫的創建和連接銷燬這個功能實現比較耗性能和消耗內存因此這個功能也需要用單例模式來實現。

5.工廠入口使用單例來實現了,裏面的數據庫操作的功能類也是單例實現了。那麼這個工廠類需要根據傳進去的類,初始化表的創建,根據傳進去的類通過反射找到裏面的參數。來實現接口增刪改查功能。反射會有點耗性能。

6.sqlite中和Java中對於基本數據集的定義不同需要根據不同類型做轉化。

7.我們可以將一個工廠實現的接口類裏面定義一個緩存,將發射獲取的參數加入到緩存中。下次其他方式操作這個類時就可以使用之前的緩存減少性能消耗。

8.查詢支持類需要專門分裝,由於查詢的條件多方式多專門封裝後功能清晰不凌亂。

9.在Android6.0以後在存儲器上見文件需要權限,因此在數據庫初始化時需要檢測是否有存儲器上的創建和讀寫的權限。

10.更高級的優化即把運行時的反射功能改成編譯時自動生成代碼功能。比如說greendao。

2.功能模塊

1.接口定義

2.數據庫操作工具單例實現

3.操作入口工廠工具類的實現 

4.轉化工具類的實現

5.查詢功能的封裝

3.代碼

1.接口定義代碼

public interface IDaoSupport<T> {
    void init(SQLiteDatabase sqLiteDatabase, Class<T> tClass);
    //插入數據
    public long insert(T t);
    //批量插入數據
    public void insert(List<T> dataList);
    //獲取專門的查詢的支持類
    QuerySupport<T> querySupport();
    //
    int delete(String whereClause, String... whereArgs);

    int update(T obj, String whereClause, String... whereArgs);
}

2.數據庫操作工具單例實現代碼和工廠入口工具類的實現代碼

public class DaoSupportFactory {
    // 持有外部數據庫的引用
    private SQLiteDatabase mSqLiteDatabase;
    private static final String db_name = "yty";
    private static WeakReference<Context> weakReference;

    public static class DaoSupportInstance{
        public static DaoSupportFactory daoSupportFactory=new DaoSupportFactory();
    }

    public static DaoSupportFactory getInstance(Context context){
        weakReference = new WeakReference<>(context);
        return DaoSupportInstance.daoSupportFactory;
    }

    private DaoSupportFactory(){
        // 把數據庫放到內存卡里面  判斷是否有存儲卡 6.0要動態申請權限
        File dbRoot = new File(weakReference.get().getFilesDir().getAbsolutePath() + File.separator + db_name + File.separator + "database");
        if (!dbRoot.exists()) {
            dbRoot.mkdirs();
        }
        File dbFile = new File(dbRoot, "test.db");

        // 打開或者創建一個數據庫
        mSqLiteDatabase = SQLiteDatabase.openOrCreateDatabase(dbFile, null);
    }

    public <T> IDaoSupport<T> getDao(Class<T> clazz) {
        IDaoSupport<T> daoSupport = new DaoSupport();
        daoSupport.init(mSqLiteDatabase, clazz);
        return daoSupport;
    }
}

3.數據庫接口實現類

public class DaoSupport<T> implements IDaoSupport<T> {
    private String TAG = "DaoSupport";
    private SQLiteDatabase mSqLiteDatabase;
    //範型類
    private Class<T> mClass;
    private static final Object[] mPutMethodArgs = new Object[2];

    private static final Map<String, Method> mPutMethods = new ArrayMap<>();

    private QuerySupport<T> mQuerySupport;

    @Override
    public void init(SQLiteDatabase sqLiteDatabase, Class<T> tClass) {
        this.mSqLiteDatabase = sqLiteDatabase;
        this.mClass = tClass;

        //創建表
//        create table if not exists Student(
//                id integer primary key autoincrement,
//                name text,
//                age integer,
//                flag boolean
//        )

        StringBuffer stringBuffer = new StringBuffer();
        stringBuffer.append("create table if not exists ")
                .append(DaoUtils.getTableName(mClass))
                .append("(id integer primary key autoincrement, ");

        Field[] fields = mClass.getDeclaredFields();
        for(Field field: fields){
            field.setAccessible(true);
            String name = field.getName();
            String type = field.getType().getSimpleName();// int String boolean
            //  type需要進行轉換 int --> integer, String text;
            stringBuffer.append(name).append(DaoUtils.getColumnType(type)).append(", ");
        }

        stringBuffer.replace(stringBuffer.length()-2, stringBuffer.length(),")");
        String createTableSql = stringBuffer.toString();

        // 創建表
        mSqLiteDatabase.execSQL(createTableSql);
    }

    @Override
    public long insert(T t) {
//        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 values = contentValuesByObj(t);

        // null  速度比第三方的快一倍左右
        return mSqLiteDatabase.insert(DaoUtils.getTableName(mClass), null, values);
    }

    @Override
    public void insert(List<T> dataList) {
        //批量插入數據採用 採用事務 提高 性能
        mSqLiteDatabase.beginTransaction();
        for(T data: dataList){
            //採用單條插入
            insert(data);
        }
        mSqLiteDatabase.setTransactionSuccessful();
        mSqLiteDatabase.endTransaction();
    }

    @Override
    public QuerySupport<T> querySupport() {
        if (null == mQuerySupport){
            mQuerySupport = new QuerySupport<>(mSqLiteDatabase, mClass);
        }
        return mQuerySupport;
    }

    @Override
    public int delete(String whereClause, String... whereArgs) {
        return mSqLiteDatabase.delete(DaoUtils.getTableName(mClass), whereClause, whereArgs);
    }

    @Override
    public int update(T obj, String whereClause, String... whereArgs) {
        ContentValues values = contentValuesByObj(obj);
        return mSqLiteDatabase.update(DaoUtils.getTableName(mClass),values,whereClause,whereArgs);
    }

    private ContentValues contentValuesByObj(T obj){
        // 第三方的 使用比對一下 瞭解一下源碼
        ContentValues values = new ContentValues();
        //封裝values
        Field[] fields = mClass.getDeclaredFields();

        for(Field field: fields){
            try{
                //設置權限,私有和公有的都可以訪問
                field.setAccessible(true);
                String key = field.getName();
                //獲取value
                Object value = field.get(obj);

                //put第二個參數是類型 需要轉化
                mPutMethodArgs[0] = key;
                mPutMethodArgs[1] = value;

                //方法使用反射,反射在一定程度上會影響性能
                //參考AppCompatViewInflater源碼
                String filedTypeName = field.getType().getName();
                //還是使用反射  獲取方法  put  緩存方法
                Method putMethod = mPutMethods.get(filedTypeName);
                if(putMethod == null){
                    putMethod = ContentValues.class.getDeclaredMethod("put", String.class, value.getClass());
                    mPutMethods.put(filedTypeName, putMethod);
                }
                //通過反射執行
                putMethod.invoke(values,mPutMethodArgs);
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                mPutMethodArgs[0] = null;
                mPutMethodArgs[1] = null;
            }
        }
        return values;
    }
}

4.轉化工具類的實現代碼

public class DaoUtils {
    private DaoUtils() {
        throw new UnsupportedOperationException("cannot be instantiated");
    }

    /**
     * 獲得類名
     */
    public static String getTableName(Class<?> clazz) {
        return clazz.getSimpleName();
    }

    /**
     * 數據庫操作的時候根據類型進行轉換
     */
    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;
    }

    /**
     * 查詢數據:cursor.getInt();將int第一個字符進行轉換成大寫
     */
    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 : "";
    }
}

5.查詢功能的封裝代碼

public class QuerySupport<T> {
    // 查詢的列
    private String[] mQueryColumns;
    // 查詢的條件
    private String mQuerySelection;
    // 查詢的參數
    private String[] mQuerySelectionArgs;
    // 查詢分組
    private String mQueryGroupBy;
    // 查詢對結果集進行過濾
    private String mQueryHaving;
    // 查詢排序
    private String mQueryOrderBy;
    // 查詢可用於分頁
    private String mQueryLimit;

    private Class<T> mClass;
    private SQLiteDatabase mSQLiteDatabase;

    public QuerySupport(SQLiteDatabase sqLiteDatabase, Class<T> clazz) {
        this.mClass = clazz;
        this.mSQLiteDatabase = sqLiteDatabase;
    }

    public QuerySupport columns(String... columns) {
        this.mQueryColumns = columns;
        return this;
    }

    public QuerySupport selectionArgs(String... selectionArgs) {
        this.mQuerySelectionArgs = selectionArgs;
        return this;
    }

    public QuerySupport having(String having) {
        this.mQueryHaving = having;
        return this;
    }

    public QuerySupport orderBy(String orderBy) {
        this.mQueryOrderBy = orderBy;
        return this;
    }

    public QuerySupport limit(String limit) {
        this.mQueryLimit = limit;
        return this;
    }

    public QuerySupport groupBy(String groupBy) {
        this.mQueryGroupBy = groupBy;
        return this;
    }

    public QuerySupport selection(String selection) {
        this.mQuerySelection = selection;
        return this;
    }

    public List<T> query() {
        Cursor cursor = mSQLiteDatabase.query(DaoUtils.getTableName(mClass), mQueryColumns, mQuerySelection,
                mQuerySelectionArgs, mQueryGroupBy, mQueryHaving, mQueryOrderBy, mQueryLimit);
        clearQueryParams();
        return cursorToList(cursor);
    }

    public List<T> queryAll() {
        Cursor cursor = mSQLiteDatabase.query(DaoUtils.getTableName(mClass), null, null, null, null, null, null);
        return cursorToList(cursor);
    }

    /**
     * 清空參數
     */
    private void clearQueryParams() {
        mQueryColumns = null;
        mQuerySelection = null;
        mQuerySelectionArgs = null;
        mQueryGroupBy = null;
        mQueryHaving = null;
        mQueryOrderBy = null;
        mQueryLimit = null;
    }

    /**
     * 通過Cursor封裝成查找對象
     *
     * @return 對象集合列表
     */
    private List<T> cursorToList(Cursor cursor) {
        List<T> list = new ArrayList<>();
        if (cursor != null && cursor.moveToFirst()) {
            do {
                try {
                    T instance = mClass.newInstance();
                    Field[] fields = mClass.getDeclaredFields();
                    for (Field field : fields) {
                        // 遍歷屬性
                        field.setAccessible(true);
                        String name = field.getName();
                        //cursor.getInt(0);
                        // 獲取角標
                        int index = cursor.getColumnIndex(name);
                        if (index == -1) {
                            continue;
                        }
                        // 通過反射獲取 遊標的方法
                        Method cursorMethod = cursorMethod(field.getType());
                        if (cursorMethod != null) {
                            Object value = cursorMethod.invoke(cursor, index);
                            if (value == null) {
                                continue;
                            }

                            // 處理一些特殊的部分
                            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);
                                }
                            }
                            field.set(instance, value);
                        }
                    }
                    // 加入集合
                    list.add(instance);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            } while (cursor.moveToNext());
        }
        cursor.close();
        return list;
    }

    private Method cursorMethod(Class<?> type) throws Exception {
        String methodName = getColumnMethodName(type);
        Method method = Cursor.class.getMethod(methodName, int.class);
        return method;
    }

    private String getColumnMethodName(Class<?> fieldType) {
        String typeName;
        if (fieldType.isPrimitive()) {//是不是基本類型
            typeName = DaoUtils.capitalize(fieldType.getName());//Int
        } else {
            typeName = fieldType.getSimpleName();//Integer
        }
        String methodName = "get" + typeName;
        if ("getBoolean".equals(methodName)) {
            methodName = "getInt";
        } else if ("getChar".equals(methodName) || "getCharacter".equals(methodName)) {
            methodName = "getString";
        } else if ("getDate".equals(methodName)) {
            methodName = "getLong";
        } else if ("getInteger".equals(methodName)) {
            methodName = "getInt";
        }
        return methodName;
    }

}

6.使用 

新建的java bean對象必須有無參的構造函數

 

 

3.類似封裝SQLCipher

將上面的sqlite換成sqlcipher也可以實現類似封裝,源碼在面鏈接中的libframe中,幫忙點個贊

GitHub地址:https://github.com/MatrixSpring/FrameWork

 

4.基於編譯時註解的greendao的使用

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章