Android SQLite資料儲存的通用設計
SQLite用於儲存一些資料量較多,結構比較複雜情況,使用的時候只需要實現SQLiteOpenHelper,在onCreate建立資料表,onUpgrade做升級處理
通過Helper例項對DB進行資料處理,例如,database = dbHelper.getWritableDatabase(); 獲取DB物件進行插入,更新,刪除操作,dbHelper.getReadableDatabase()
進行資料查詢,在此不必多說,這樣實現一個數據庫並不複雜,但是對不同物件儲存操作還需要分別各自去自己實現,比較麻煩,能不能用一種通用設計實現呢?
其實存入DB內的資料都是要分隔成基本String, int , long, double等,在android中可以使用資料集ContentValues進行儲存,ContentValues可以儲存基本型別,類似於Map
ContentValues是可直接用於SQLiteDatabase,ContentProvider中進行批量處理,這是android為此設計的,在SQLiteDatabase我們使用ContentValues是非常簡單的。
好了下面我們需要對通用資料操作定義一種規則,我們只需要傳入tableName,whereArgs(篩選條件),ContentValues即可實現insert, quere,delete,update功能
介面定義如下:
public interface IDBDelegate { public boolean insertData(String tableName,String nullColumnHack,ContentValues values); public boolean deleteData(String tableName,String whereClause, String[] whereArgs); public boolean updateData(String tableName,ContentValues values, String whereClause, String[] whereArgs); //查詢單條資料集 public Map<String, String> getRowData(String tableName, String selection, String[] selectionArgs); //查詢多條資料集 public List<Map<String, String>> getListData(String tableName, String selection, String[] selectionArgs); }
實現類
public class DBDelegateImpl implements IDBDelegate{ private final Object obj=new Object(); private DBHelper dbHelper=null; public DBDelegateImpl(Context context) { dbHelper=DBHelper.getInstance(context); } @Override public boolean insertData(String tableName, String nullColumnHack,ContentValues values) { synchronized (obj) { boolean flag = false; SQLiteDatabase database = null; long id = -1; try { database = dbHelper.getWritableDatabase(); id = database.insert(tableName, nullColumnHack, values); flag = (id != -1); } catch (SQLException e) { e.printStackTrace(); } finally { if (database != null) { database.close(); } } return flag; } } public int insertBatchData(String tableName, String nullColumnHack,List<ContentValues> values) { int count=0; if(values!=null && values.size()>0){ SQLiteDatabase db = dbHelper.getWritableDatabase(); try { db.beginTransaction(); ContentValues cv = null; for (int i = 0; i < values.size(); i++) { cv = values.get(i); db.insert(tableName, nullColumnHack, cv); } db.setTransactionSuccessful(); }finally { if(db!=null) { db.endTransaction(); } } } return count; } @Override public boolean deleteData(String tableName, String whereClause, String[] whereArgs) { synchronized (obj) { boolean flag = false; SQLiteDatabase database = null; int count = 0; try { database = dbHelper.getWritableDatabase(); count = database.delete(tableName, whereClause, whereArgs); flag = (count > 0); } catch (SQLException e) { e.printStackTrace(); } finally { if (database != null) { database.close(); } } return flag; } } @Override public boolean updateData(String tableName, ContentValues values, String whereClause, String[] whereArgs) { synchronized (obj) { boolean flag = false; SQLiteDatabase database = null; int count = 0; try { database = dbHelper.getWritableDatabase(); count = database.update(tableName, values, whereClause, whereArgs); flag = (count > 0); } catch (SQLException e) { e.printStackTrace(); } finally { if (database != null) { database.close(); } } return flag; } } @Override public Map<String, String> getRowData(String tableName, String selection, String[] selectionArgs) { SQLiteDatabase database = null; Cursor cursor = null; Map<String, String> map = new HashMap<String, String>(); try { database = dbHelper.getReadableDatabase(); cursor = database.query(true, tableName, null, selection, selectionArgs, null, null, null, null); //查詢單條記錄,記錄是唯一的,所以第一個引數置為 true. int cols_len = cursor.getColumnCount(); while (cursor.moveToNext()) { for (int i = 0; i < cols_len; i++) { String cols_name = cursor.getColumnName(i); String cols_values = cursor.getString(cursor.getColumnIndex(cols_name)); if (cols_values == null) { cols_values = ""; } map.put(cols_name, cols_values); } } } catch (SQLException e) { e.printStackTrace(); } finally { if(cursor!=null){ cursor.close(); } if (database != null) { database.close(); } } return map; } @Override public List<Map<String, String>> getListData(String tableName, String selection, String[] selectionArgs) { SQLiteDatabase database = null; Cursor cursor = null; List<Map<String, String>> list = new ArrayList<Map<String, String>>(); try { database = dbHelper.getReadableDatabase(); cursor = database.query(false, tableName, null, selection, selectionArgs, null, null, null, null); //查詢所有記錄,所以有重複的資料也要全部檢出,所以第一引數置為false. int cols_len = cursor.getColumnCount(); while (cursor.moveToNext()) { Map<String, String> map = new HashMap<String, String>(); for (int i = 0; i < cols_len; i++) { String cols_name = cursor.getColumnName(i); String cols_values = cursor.getString(cursor.getColumnIndex(cols_name)); if (cols_values == null) { cols_values = ""; } map.put(cols_name, cols_values); } list.add(map); } } catch (SQLException e) { e.printStackTrace(); } finally { if(cursor!=null){ cursor.close(); } if (database != null) { database.close(); } } return list; } }
嗯,比較優雅的實現了通用設計實現,還不錯
DBHelper實現:
public class DBHelper extends SQLiteOpenHelper {
private final static String DB_NAME = "Program_db";
public final static int DB_VERSION = 1;
private static DBHelper mInstance=null;
private DBHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
public static DBHelper getInstance(Context context) {
if (mInstance == null) {
synchronized (DBHelper.class) {
if (mInstance == null) {
mInstance = new DBHelper(context);
}
}
}
return mInstance;
}
@Override
public void onCreate(SQLiteDatabase db) {
final String sql2="CREATE TABLE "+TABLE_SMILFILE+"(" + SID
+ " INTEGER PRIMARY KEY AUTOINCREMENT," +
FILE_VER+" TEXT,"+
FILE_SYNC+" TEXT,"+
FILE_DURATION+" INTEGER,"+
FILE_SRC+" TEXT"+
");";
// LogUtils.debug("create taskTable", "smilSql="+sql2);
db.execSQL(sql2);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// db.execSQL("DROP TABLE IF EXISTS " + TABLE_PROGRAM);
}
}
使用案例:
public void DBInsertTest(Context ctx){
IDBDelegate dao=new DBDelegateImpl(ctx);
ContentValues values=new ContentValues();
values.put("TASK_NAME", "PlayerName");
dao.insertData("table1",null,values);
}
public void DBUpdateTest(Context ctx,String sid){
IDBDelegate dao=new DBDelegateImpl(ctx);
ContentValues values=new ContentValues();
values.put("TASK_NAME", "PlayerName1");
values.put("TASK_TYPE", "Type1");
dao.updateData("table1",values,"sid=?",new String[]{sid});
}
public void DBDeleteTest(Context ctx,String sid){
IDBDelegate dao=new DBDelegateImpl(ctx);
dao.deleteData("table1","sid=?",new String[]{sid});
}
public void DBQureTest(){
List<Map<String, String>> listTypeData=null;
//" id = ? ", new String[] { "2" }
IDBDelegate dao=new DBDelegateImpl(ctx);
listTypeData=dao.getListData(AdsDatabase.TABLE_PROGRAM, AdsDatabase.TASK_TYPE+" = ?",
new String[] {String.valueOf(Constance.PLAY_TYPE_INSERT)});
}
2016-08-14 22:20更新說明
以上資料庫還可以優化一點處理,在資料庫讀取與寫入時沒有必要同步處理(即讀取資料庫不加鎖,對資料庫內容修改操作加鎖處理)採用讀寫分離實現,也稱為COW模式。這時可能會產生讀取是舊資料,修改內容後新資料,無法更新,我們可以使用observe來資料內容變化監聽來及時確保資料為最新。其實在多執行緒併發訪問時這種全部通過一個物件鎖實現效率很低,讀寫分離能夠明顯提高程式效率。
SqlLite一些問題:
1,使用多個SQLiteOpenHelper問題,例如:
// Thread 1
Context context = getApplicationContext();
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
// Thread 2
Context context = getApplicationContext();
DatabaseHelper helper = new DatabaseHelper(context);
SQLiteDatabase database = helper.getWritableDatabase();
使用多個DBHelper寫入資料時,會發生寫入失敗問題,程式並不會報異常,Logcat會有一個輸出
android.database.sqlite.SQLiteDatabaseLockedException: database is locked
由此可見DBHelper只能有一個例項物件存在,建議使用單例維護
2,關於在SQLite上資料庫連線池的問題
學過java web都知道,資料庫連線池可以提高程式效能,網站併發訪問需要使用,但是在android只能同時存在一個DB connection,即one helper,one connection at the same time,otherwise one fail