正常來說使用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中對此都需要寫出來該怎麼進行操作
好了,以上文章就是對數據庫的封裝來達到面向對象方式的使用