用面向對象的方式來操作數據庫

正常來說使用sql來操作數據庫的話沒啥問題,但是對於一些不太擅長寫sql的同學來說,通過面向對象的方式來操作數據庫,可以極大的避免寫sql時不小心犯錯誤的情況,接下來讓我們直接進入主題

首先是需要先定義接口規範,即數據庫常用的增刪改查操作,這用來讓我們知道有哪些功能,當然還可以自己再繼續擴展

public interface IBaseDao<T> {

    /**
     *  插入
     */
    long insert(T entity);

    /**
     * 更新
     */
    long update(T entity, T where);

    /**
     * 刪除
     */
    int delete(T where);

    /**
     * 查詢
     */
    List<T> query(T where);

    /**
     * 限制查詢
     */
    List<T> query(T where, String orderBy, Integer startIndex, Integer limit);
}

使用註解的方式可以讓我們來更好的定義表名以及列名,因爲有時候對象既要對應後臺下發的字段,又想適應數據庫名的規範

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface DbTable {

    String value();
}



@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DbField {

    String value();
}

接着就是建庫的操作,用單例方式來進行建立數據庫操作

public class BaseDaoFactory {

    private static BaseDaoFactory sInstance;
    private SQLiteDatabase sqLiteDatabase;
    /**
     * 設計一個數據庫的連接池
     */
    protected Map<String, BaseDao> map = Collections.synchronizedMap(new HashMap<String, BaseDao>());
    /**
     * 定義建數據庫的路徑
     */
    private String sqliteDatabasePath;

    public static BaseDaoFactory getInstance() {
        if (sInstance == null) {
            synchronized (BaseDaoFactory.class) {
                if (sInstance == null) {
                    sInstance = new BaseDaoFactory();
                }
            }
        }
        return sInstance;
    }

    public BaseDaoFactory() {
//        Environment.getExternalStorageDirectory() + File.separator;
        sqliteDatabasePath = "data/data/com.tgp.dbapp/test.db";
        //新建數據庫
        sqLiteDatabase = SQLiteDatabase.openOrCreateDatabase(sqliteDatabasePath, null);
    }

}

這裏數據庫的路徑可以也寫到sd卡中,這樣即使App被卸載了,下次安裝的時候還可以直接獲取數據繼續操作,當然這個具體根據需求情況來存儲位置就可以

接着創建BaseDao去實現定義的接口,同時讓我們在構造方法中對其進行一些初始化操作,

public void init(SQLiteDatabase sqLiteDatabase, Class<T> entityClass) {
        this.sqLiteDatabase = sqLiteDatabase;
        this.entityClass = entityClass;

        //*************自動建表*************
        //根據傳入的entityClass來建表,只需要建一次
        if (!isInit) {
            //取得表名

            if (entityClass.getAnnotation(DbTable.class) == null) {
                //用類名做表名
                this.tableName = entityClass.getSimpleName();
            } else {
                //取得註解上的名字
                this.tableName = entityClass.getAnnotation(DbTable.class).value();
            }
            //執行建表操作
            String createSql = getCreateTableSql();
            Log.i(TAG, createSql);
            this.sqLiteDatabase.execSQL(createSql);
            cacheMap = new HashMap<>();
            initCacheMap();
            isInit = true;
        }

    }

用面向對象的方式來操作數據庫,本質上來說還是使用sql,只不過對外展現的樣子是面向對象的方式

所以執行建表操作的時候就需要我們根據對象去生成相應的sql語句,平時我們寫的建表語句爲

create table if not exists tb_user(id integer, name TEXT)

這種樣子,上面的表名我們已經可以通過註解或者獲取類名的方式獲取到了,現在需要的是獲取到有哪些列,即對象中是什麼字段,這時候就用到了反射的操作

private String getCreateTableSql() {
        //create table if not exists tb_user(id integer, name TEXT)
        StringBuilder sb = new StringBuilder();
        sb.append("create table if not exists ");
        sb.append(tableName).append("(");
        //反射得到所有的成員變量
        Field[] declaredFields = entityClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //拿到成員類型
            Class<?> type = field.getType();
            if (field.getAnnotation(DbField.class) != null) {
                if (type == String.class) {
                    sb.append(field.getAnnotation(DbField.class).value()).append(" TEXT,");
                } else if (type == Integer.class) {
                    sb.append(field.getAnnotation(DbField.class).value()).append(" Integer,");
                } else if (type == Long.class) {
                    sb.append(field.getAnnotation(DbField.class).value()).append(" BIGINT,");
                } else if (type == Double.class) {
                    sb.append(field.getAnnotation(DbField.class).value()).append(" DOUBLE,");
                } else if (type == byte[].class) {
                    sb.append(field.getAnnotation(DbField.class).value()).append(" BLOB,");
                } else {
                    //不支持的類型
                    continue;
                }
            } else {
                if (type == String.class) {
                    sb.append(field.getName()).append(" TEXT,");
                } else if (type == Integer.class) {
                    sb.append(field.getName()).append(" Integer,");
                } else if (type == Long.class) {
                    sb.append(field.getName()).append(" BIGINT,");
                } else if (type == Double.class) {
                    sb.append(field.getName()).append(" DOUBLE,");
                } else if (type == byte[].class) {
                    sb.append(field.getName()).append(" BLOB,");
                } else {
                    //不支持的類型
                    continue;
                }
            }
        }
        //去掉最後的逗號
        if (',' == (sb.charAt(sb.length() - 1))) {
            sb.deleteCharAt(sb.length() - 1);
        }
        sb.append(")");
        return sb.toString();
    }

上述代碼中,同樣的有註解的用註解來做列名,沒有的話使用字段名來做列名,並根據數據庫的列的字段類型來匹配

this.sqLiteDatabase.execSQL(createSql);獲取到創建表的語句後我們就可以執行建表操作了

我們這裏需要緩存下列名和字段的map,以此來方便後面的增刪改查等操作

private void initCacheMap() {
        //取得所有字段名
        // 查詢空表
        String sql = "select * from " + tableName + " limit 1, 0";
        Cursor cursor = sqLiteDatabase.rawQuery(sql, null);
        //獲取列名
        String[] columnNames = cursor.getColumnNames();
        //取所有的成員變量
        Field[] declaredFields = entityClass.getDeclaredFields();
        for (Field field : declaredFields) {
            field.setAccessible(true);
        }
        //字段跟對象的成員變量一一對應
        for (String columName : columnNames) {
            Field columnField = null;
            for (Field field : declaredFields) {
                String fieldName = "";
                if (field.getAnnotation(DbField.class) != null) {
                    fieldName = field.getAnnotation(DbField.class).value();
                } else {
                    fieldName = field.getName();
                }

                if (columName.equals(fieldName)) {
                    columnField = field;
                    break;
                }
            }
            if (columnField != null) {
                cacheMap.put(columName, columnField);
            }
        }
    }

到這裏我們就可以進行增刪改查的操作了,首先來看下增的操作

正常的sql語句寫法爲    sqLiteDatabase.insert(tableName, null, contentValues);

表名我們已經獲取到了,接着就是去組裝數據的操作,首先讓我們來獲取一個Map,key是列名,value就是字段對應的值,上面我們緩存的對象的 列名-字段,這裏我們拿到傳進來的對象,通過反射的操作就可以拿到對應的值

private Map<String, String> getValueMap(T entity) {
        HashMap<String, String> map = new HashMap<>();
        //返回的是所有的成員變量
        for (Field next : cacheMap.values()) {
            next.setAccessible(true);
            try {
                //獲取對象的屬性值
                Object o = next.get(entity);
                if (o == null) {
                    continue;
                }
                //張三, 3
                String value = o.toString();
                String key = "";
                if (next.getAnnotation(DbField.class) != null) {
                    key = next.getAnnotation(DbField.class).value();
                } else {
                    key = next.getName();
                }
                if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value)) {
                    map.put(key, value);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return map;
    }

拿到這裏列-字段內容的map後,我們就可以去組裝contentValues了

 private ContentValues getContentValues(Map<String, String> map) {
        ContentValues contentValues = new ContentValues();
        for (String key : map.keySet()) {
            String value = map.get(key);
            if (value != null) {
                contentValues.put(key, value);
            }
        }
        return contentValues;
    }

相應的增加操作就完成了

@Override
    public long insert(T entity) {
   
        //準備好ContentValues中需要的數據
        Map<String, String> map = getValueMap(entity);
        //把數據轉移到ContentValues中
        ContentValues contentValues = getContentValues(map);
        //數據庫操作
        return sqLiteDatabase.insert(tableName, null, contentValues);
    }

update:

  sqLiteDatabase.update(tableName, contentvalue, "name = ?", new String[]{"tgp"});

更改的sql語句是這樣寫的,表名已經有了,然後就是要變更成什麼以及變更的條件是什麼的組合

@Override
    public long update(T entity, T where) {
        //        sqLiteDatabase.update(tableName, contentvalue, "name = ?", new String[]{"tgp"});
        int result = -1;
        Map<String, String> values = getValueMap(entity);
        ContentValues contentValues = getContentValues(values);
        //條件map
        Map<String, String> values1 = getValueMap(where);
        Condition condition = new Condition(values1);
        result = sqLiteDatabase.update(tableName, contentValues, condition.whereClause, condition.whereArgs);
        return result;
    }

    private class Condition {
        String whereClause;
        String[] whereArgs;

        Condition(Map<String, String> values) {
            ArrayList<String> list = new ArrayList<>();
            StringBuilder sb = new StringBuilder();
            sb.append("1=1");
            //取得所有成員名
            Set<String> keys = values.keySet();
            for (String key : keys) {
                String value = values.get(key);
                if (value != null) {
                    sb.append(" and ").append(key).append("=?");
                    list.add(value);
                }
            }
            this.whereClause = sb.toString();
            whereArgs = list.toArray(new String[list.size()]);
        }
    }

update有兩個參數,第一個參數是要變成什麼,第二個對象是用來組成判斷條件,Condition只是用來做了一個封裝

後面的查詢和刪除本質上差不多,這裏把BaseDao的整個代碼貼出來

package com.tgp.dbapp.db;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.text.TextUtils;
import android.util.Log;

import com.tgp.dbapp.annotation.DbField;
import com.tgp.dbapp.annotation.DbTable;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 建表
 */
public class BaseDao<T> implements IBaseDao<T> {

    private static final String TAG = "BaseDao";

    /**
     * 持有數據庫操作的引用
     */
    private SQLiteDatabase sqLiteDatabase;
    /**
     * 表名
     */
    private String tableName;

    /**
     * 持有操作數據庫所對應的java類型
     */
    private Class<T> entityClass;

    /**
     * 標記用來表示是否做過初始化操作
     */
    private boolean isInit = false;
    /**
     * 定義一個緩存空間(key 字段名 value 成員變量)
     */
    private HashMap<String, Field> cacheMap;

    public void init(SQLiteDatabase sqLiteDatabase, Class<T> entityClass) {
        this.sqLiteDatabase = sqLiteDatabase;
        this.entityClass = entityClass;

        //*************自動建表*************
        //根據傳入的entityClass來建表,只需要建一次
        if (!isInit) {
            //取得表名

            if (entityClass.getAnnotation(DbTable.class) == null) {
                //用類名做表名
                this.tableName = entityClass.getSimpleName();
            } else {
                //取得註解上的名字
                this.tableName = entityClass.getAnnotation(DbTable.class).value();
            }
            //執行建表操作
            String createSql = getCreateTableSql();
            Log.i(TAG, createSql);
            this.sqLiteDatabase.execSQL(createSql);
            cacheMap = new HashMap<>();
            initCacheMap();
            isInit = true;
        }

    }

    private void initCacheMap() {
        //取得所有字段名
        // 查詢空表
        String sql = "select * from " + tableName + " limit 1, 0";
        Cursor cursor = sqLiteDatabase.rawQuery(sql, null);
        //獲取列名
        String[] columnNames = cursor.getColumnNames();
        //取所有的成員變量
        Field[] declaredFields = entityClass.getDeclaredFields();
        for (Field field : declaredFields) {
            field.setAccessible(true);
        }
        //字段跟對象的成員變量一一對應
        for (String columName : columnNames) {
            Field columnField = null;
            for (Field field : declaredFields) {
                String fieldName = "";
                if (field.getAnnotation(DbField.class) != null) {
                    fieldName = field.getAnnotation(DbField.class).value();
                } else {
                    fieldName = field.getName();
                }

                if (columName.equals(fieldName)) {
                    columnField = field;
                    break;
                }
            }
            if (columnField != null) {
                cacheMap.put(columName, columnField);
            }
        }
    }

    /**
     * 拼接SQL
     */
    private String getCreateTableSql() {
        //create table if not exists tb_user(id integer, name TEXT)
        StringBuilder sb = new StringBuilder();
        sb.append("create table if not exists ");
        sb.append(tableName).append("(");
        //反射得到所有的成員變量
        Field[] declaredFields = entityClass.getDeclaredFields();
        for (Field field : declaredFields) {
            //拿到成員類型
            Class<?> type = field.getType();
            if (field.getAnnotation(DbField.class) != null) {
                if (type == String.class) {
                    sb.append(field.getAnnotation(DbField.class).value()).append(" TEXT,");
                } else if (type == Integer.class) {
                    sb.append(field.getAnnotation(DbField.class).value()).append(" Integer,");
                } else if (type == Long.class) {
                    sb.append(field.getAnnotation(DbField.class).value()).append(" BIGINT,");
                } else if (type == Double.class) {
                    sb.append(field.getAnnotation(DbField.class).value()).append(" DOUBLE,");
                } else if (type == byte[].class) {
                    sb.append(field.getAnnotation(DbField.class).value()).append(" BLOB,");
                } else {
                    //不支持的類型
                    continue;
                }
            } else {
                if (type == String.class) {
                    sb.append(field.getName()).append(" TEXT,");
                } else if (type == Integer.class) {
                    sb.append(field.getName()).append(" Integer,");
                } else if (type == Long.class) {
                    sb.append(field.getName()).append(" BIGINT,");
                } else if (type == Double.class) {
                    sb.append(field.getName()).append(" DOUBLE,");
                } else if (type == byte[].class) {
                    sb.append(field.getName()).append(" BLOB,");
                } else {
                    //不支持的類型
                    continue;
                }
            }
        }
        //去掉最後的逗號
        if (',' == (sb.charAt(sb.length() - 1))) {
            sb.deleteCharAt(sb.length() - 1);
        }
        sb.append(")");
        return sb.toString();
    }

    private ContentValues getContentValues(Map<String, String> map) {
        ContentValues contentValues = new ContentValues();
        for (String key : map.keySet()) {
            String value = map.get(key);
            if (value != null) {
                contentValues.put(key, value);
            }
        }
        return contentValues;
    }

    /**
     * key(字段) value(成員變量)
     */

    private Map<String, String> getValueMap(T entity) {
        HashMap<String, String> map = new HashMap<>();
        //返回的是所有的成員變量
        for (Field next : cacheMap.values()) {
            next.setAccessible(true);
            try {
                //獲取對象的屬性值
                Object o = next.get(entity);
                if (o == null) {
                    continue;
                }
                //張三, 3
                String value = o.toString();
                String key = "";
                if (next.getAnnotation(DbField.class) != null) {
                    key = next.getAnnotation(DbField.class).value();
                } else {
                    key = next.getName();
                }
                if (!TextUtils.isEmpty(key) && !TextUtils.isEmpty(value)) {
                    map.put(key, value);
                }
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        return map;
    }

    @Override
    public long insert(T entity) {
        //        ContentValues contentValues = new ContentValues();
        //        contentValues.put
        //        sqLiteDatabase.insert()
        //準備好ContentValues中需要的數據
        Map<String, String> map = getValueMap(entity);
        //把數據轉移到ContentValues中
        ContentValues contentValues = getContentValues(map);
        //數據庫操作
        return sqLiteDatabase.insert(tableName, null, contentValues);
    }

    @Override
    public long update(T entity, T where) {
        //        sqLiteDatabase.update(tableName, contentvalue, "name = ?", new String[]{"tgp"});
        int result = -1;
        Map<String, String> values = getValueMap(entity);
        ContentValues contentValues = getContentValues(values);
        //條件map
        Map<String, String> values1 = getValueMap(where);
        Condition condition = new Condition(values1);
        result = sqLiteDatabase.update(tableName, contentValues, condition.whereClause, condition.whereArgs);
        return result;
    }

    private class Condition {
        String whereClause;
        String[] whereArgs;

        Condition(Map<String, String> values) {
            ArrayList<String> list = new ArrayList<>();
            StringBuilder sb = new StringBuilder();
            sb.append("1=1");
            //取得所有成員名
            Set<String> keys = values.keySet();
            for (String key : keys) {
                String value = values.get(key);
                if (value != null) {
                    sb.append(" and ").append(key).append("=?");
                    list.add(value);
                }
            }
            this.whereClause = sb.toString();
            whereArgs = list.toArray(new String[list.size()]);
        }
    }

    @Override
    public int delete(T where) {
//        sqLiteDatabase.delete(tableName, "name = ?", new String[]{"tgp"});
        Map<String, String> values = getValueMap(where);
        Condition condition = new Condition(values);
        int delete = sqLiteDatabase.delete(tableName, condition.whereClause, condition.whereArgs);
        return delete;
    }

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

    @Override
    public List<T> query(T where, String orderBy, Integer startIndex, Integer limit) {
//        sqLiteDatabase.query(tableName, null, "od = ?", new String[]{}, null, null, "1,5");
        Map<String, String> values = getValueMap(where);
        String limitString = "";
        if (startIndex != null && limit != null) {
            limitString = startIndex + " , " + limit;
        }
        Condition condition = new Condition(values);
        Cursor query = sqLiteDatabase.query(tableName, null, condition.whereClause, condition.whereArgs, null, orderBy, limitString);
        List<T> objects = getResult(query, where);
        return objects;
    }

    private List<T> getResult(Cursor query, T where) {
        ArrayList objects = new ArrayList<>();
        Object item = null;
        while (query.moveToNext()) {
            try {
                item = where.getClass().newInstance();
                for (Map.Entry<String, Field> stringFieldEntry : cacheMap.entrySet()) {
                    //獲取列名
                    String columnName = stringFieldEntry.getKey();
                    //用列名來拿到列名在遊標中的位置
                    int columnIndex = query.getColumnIndex(columnName);
                    Field value = stringFieldEntry.getValue();
                    Class<?> type = value.getType();
                    if (columnIndex != -1) {
                        if (type == String.class) {
                            value.set(item, query.getString(columnIndex));
                        } else if (type == Double.class) {
                            value.set(item, query.getDouble(columnIndex));
                        } else if (type == Integer.class) {
                            value.set(item, query.getInt(columnIndex));
                        } else if (type == Long.class) {
                            value.set(item, query.getLong(columnIndex));
                        } else if (type == byte[].class) {
                            value.set(item, query.getBlob(columnIndex));
                        } else {
                            continue;
                        }
                    }
                }
                objects.add(item);
            } catch (Exception e) {
                e.printStackTrace();
            }

        }
        query.close();
        return objects;
    }
}

這裏說一下,查詢結束後返回的集合,就是根據查詢的結果又使用反射的方式重新賦值了下

 

 

當然,獲取basedao的地方還是在basedao工廠中,這裏是BaseDaoFactory的完整代碼

package com.tgp.dbapp.db;

import android.database.sqlite.SQLiteDatabase;

import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

/**
 * 工廠
 */
public class BaseDaoFactory {

    private static BaseDaoFactory sInstance;
    private SQLiteDatabase sqLiteDatabase;
    /**
     * 設計一個數據庫的連接池
     */
    protected Map<String, BaseDao> map = Collections.synchronizedMap(new HashMap<String, BaseDao>());
    /**
     * 定義建數據庫的路徑
     */
    private String sqliteDatabasePath;

    public static BaseDaoFactory getInstance() {
        if (sInstance == null) {
            synchronized (BaseDaoFactory.class) {
                if (sInstance == null) {
                    sInstance = new BaseDaoFactory();
                }
            }
        }
        return sInstance;
    }

    public BaseDaoFactory() {
//        Environment.getExternalStorageDirectory() + File.separator;
        sqliteDatabasePath = "data/data/com.tgp.dbapp/test.db";
        //新建數據庫
        sqLiteDatabase = SQLiteDatabase.openOrCreateDatabase(sqliteDatabasePath, null);
    }

    public <T> BaseDao<T> getBaseDao(Class<T> entity) {
        BaseDao baseDao = null;
        try {
            baseDao = BaseDao.class.newInstance();
            baseDao.init(sqLiteDatabase, entity);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return baseDao;
    }

    public <T extends BaseDao<M>, M> T getBaseDao(Class<T> daoClass, Class<M> entity){
        BaseDao baseDao = null;
        try {
            baseDao = daoClass.newInstance();
            baseDao.init(sqLiteDatabase, entity);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return (T)baseDao;
    }
}

使用方式也很簡單,我們建一個對應的model

@DbTable("tb_person")
public class Person {

    @DbField("_id")
    private Integer id;
    private String name;
    private String password;

    public Person() {

    }

    public Person(Integer id, String name, String password) {
        this.id = id;
        this.name = name;
        this.password = password;
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPassword() {
        return password;
    }

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

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

是否使用註解,根據自己的需要來定就可以

使用的時候,

BaseDao<Person> person = BaseDaoFactory.getInstance().getBaseDao(Person.class);
        person.insert(new Person(1, "tgpperson", "123person"));

通過工廠獲取到對象,直接進行增刪改查的操作即可

 

假設我們想擴展一下,因爲有的數據庫操作比較複雜,牽扯到多表聯動之類的,我們可以自行擴展

public class BaseDaoImpl<T> extends BaseDao<T> {

    public List<T> query(String sql) {
        return null;
    }
}

我們通過繼續BaseDao,來增加方法即可

這裏我們工廠中也提供了對應的方法,直接獲取到對象

BaseDaoNewImpl baseDao = BaseDaoFactory.getInstance().getBaseDao(BaseDaoNewImpl.class, Person.class);
        Person person = new Person();
        person.setName("tgp update");
        Person where = new Person();
        where.setId(1);
        long update = baseDao.update(person, where);

 

 

用面向對象的方式來操作數據庫本質上來說只是進行了一層封裝,其實還是進行的sql操作,只是對寫代碼來說更友好些

 

至於像數據庫升級,字段擴展,我們可以通過xml的方式來進行操作,通過從網絡獲取到最新的xml並進行解析,和當前的版本做比較,因爲有可能用戶長時間不用這時候你的數據庫版本可能已經從1-2-3-4這樣,所以xml中對此都需要寫出來該怎麼進行操作

 

好了,以上文章就是對數據庫的封裝來達到面向對象方式的使用

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