android使用数据库存储,无非有两种
1、使用原生的sqlite
2、使用第三方库
使用原生sqlite可封装成orm或者简单的封装(便于初级入手),关于sqlite orm。笔者作简单封装一下,直接上代码:
一、创建一个DatabaseManager,管理数据库的打开和关闭
public class DatabaseManager {
private AtomicInteger mOpenCounter = new AtomicInteger();
private static DatabaseManager instance;
private static DbOpenHelper mDatabaseHelper;
private SQLiteDatabase mDatabase;
public static synchronized void initializeInstance(DbOpenHelper helper) {
if (instance == null) {
instance = new DatabaseManager();
mDatabaseHelper = helper;
//启用数据库的预写日志
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
mDatabaseHelper.setWriteAheadLoggingEnabled(true);
}
}
}
public static synchronized DatabaseManager getInstance() {
if (instance == null) {
throw new IllegalStateException(DatabaseManager.class.getSimpleName() +
" is not initialized, call initializeInstance(..) method first.");
}
return instance;
}
public synchronized SQLiteDatabase openDatabase() {
if (mOpenCounter.incrementAndGet() == 1) {
// Opening new database
try {
mDatabase = mDatabaseHelper.getWritableDatabase();
} catch (Exception e) {
mDatabase = mDatabaseHelper.getReadableDatabase();
}
}
return mDatabase;
}
public synchronized void closeDatabase() {
if (mOpenCounter.decrementAndGet() == 0) {
// Closing database
mDatabase.close();
}
}
}
关于setWriteAheadLoggingEnabled
public void setWriteAheadLoggingEnabled(boolean enabled) {
synchronized (this) {
if (mEnableWriteAheadLogging != enabled) {
if (mDatabase != null && mDatabase.isOpen() && !mDatabase.isReadOnly()) {
if (enabled) {
mDatabase.enableWriteAheadLogging();
} else {
mDatabase.disableWriteAheadLogging();
}
}
mEnableWriteAheadLogging = enabled;
}
}
}
这个方法的作用是:启用或禁用数据库的预写日志。
预写日志不能用于只读的数据库,因此如果数据库是以只读的方式被打开,这个标记值会被忽略。
源代码的 if (mEnableWriteAheadLogging != enabled) 的使用方法可以借鉴一下,这行代码从逻辑上是可有可无的,它的妙处在于减少了不必要的代码执行,提高了效率(如果设置的值和当前值相等,就不作任何操作)。
那么这个预写日志到底是什么?
从源码可以看到如果 enabled 的值为 true,就会调用 mDatabase.enableWriteAheadLogging(); 。
调用此方法后,只要数据库保持打开,则并行执行查询操作。用于并行执行查询的连接的最大数目取决于设备内存和其他属性。如果一个查询是事务的一部分,那么它就在同一个数据库句柄上执行。
它通过打开数据库的多个连接并为每个查询使用不同的数据库连接来实现在同一数据库中并行执行多个线程的查询,同时数据库的日志模式也被更改以启用写入与读取同时进行。
当预写日志没有启用时(默认状态),同一时间在数据库上读取和写入是不可能的。因为写入线程会在修改数据库之前隐式地获取数据库上的互斥锁,以防止写入操作完成之前其他线程读取访问数据库。
相反,当启用预写日志时,写入操作会在分离的日志文件中进行,该文件允许读取同时进行。当数据库正在进行写入操作时,其他线程上的读取操作将在写入开始前会感知数据库的状态,当写入操作完成后,其他线程上的读取操作将感知数据库的新状态。
当数据库同时被多个线程同时访问和修改时,启用预写日志是一个好办法。然而,开启预写日志功能,会比普通日记使用更多的内存,因为同一个数据库有多个连接。因此,如果一个数据库只有一个线程使用,或者优化并发不是很重要,那么应该禁用预写日志功能。
二、创建一个BaseDao,数据表基类
public abstract class BaseDao<T> {
/**
* 插入或更新单条数据
*
* @param item
*/
public synchronized void insertOrUpdate(T item) {
ArrayList<T> tmpList = getListInfo();
try {
SQLiteDatabase db = DatabaseManager.getInstance().openDatabase();
if (db.isOpen()) {
//db是否存在此数据
boolean isExist = false;
for (T tmpInfo : tmpList) {
if (compareItem(item, tmpInfo)) {
isExist = true;
break;
}
}
if (isExist) {
updateItem(db, item);
} else {
db.insert(getTableName(), null, getContentValues(item));
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DatabaseManager.getInstance().closeDatabase();
}
}
/**
* 插入单条数据
*
* @param item
*/
public synchronized void insert(T item) {
try {
SQLiteDatabase db = DatabaseManager.getInstance().openDatabase();
if (db.isOpen()) {
db.insert(getTableName(), null, getContentValues(item));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DatabaseManager.getInstance().closeDatabase();
}
}
/**
* 批量插入(旧数据删除)
*
* @param dataList
*/
public synchronized void insert(ArrayList<T> dataList) {
try {
SQLiteDatabase db = DatabaseManager.getInstance().openDatabase();
if (db.isOpen()) {
db.beginTransaction(); // 手动设置开始事务
deleteAll(db);
for (T item : dataList) {
db.insert(getTableName(), null, getContentValues(item));
}
db.setTransactionSuccessful(); // 设置事务处理成功,不设置会自动回滚不提交
db.endTransaction(); // 处理完成
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DatabaseManager.getInstance().closeDatabase();
}
}
/**
* 批量插入或更新
*/
public synchronized void insertOrUpdate(List<T> dataList) {
ArrayList<T> tmpList = getListInfo();
try {
SQLiteDatabase db = DatabaseManager.getInstance().openDatabase();
if (db.isOpen()) {
db.beginTransaction(); // 手动设置开始事务
for (T item : dataList) {
boolean isExist = false;//db是否存在此数据
for (T tmpInfo : tmpList) {
if (compareItem(item, tmpInfo)) {
isExist = true;
break;
}
}
if (isExist) {
updateItem(db, item);
} else {
db.insert(getTableName(), null, getContentValues(item));
}
}
db.setTransactionSuccessful(); // 设置事务处理成功,不设置会自动回滚不提交
db.endTransaction(); // 处理完成
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DatabaseManager.getInstance().closeDatabase();
}
}
/**
* 获取map集合
*
* @return
*/
public Map<Object, T> getMapInfo(String key) {
Map<Object, T> map = new HashMap<>();
Cursor cursor = null;
try {
SQLiteDatabase db = DatabaseManager.getInstance().openDatabase();
cursor = db.query(getTableName(), null, null, null, null, null, null);
if (db.isOpen()) {
while (cursor.moveToNext()) {
String id = cursor.getString(cursor.getColumnIndex(key));
map.put(id, getItemInfo(cursor));
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
DatabaseManager.getInstance().closeDatabase();
}
return map;
}
/**
* 获取list集合
*
* @return
*/
public ArrayList<T> getListInfo() {
ArrayList<T> msgList = new ArrayList<>();
Cursor cursor = null;
try {
SQLiteDatabase db = DatabaseManager.getInstance().openDatabase();
cursor = db.query(getTableName(), null, null, null, null, null, null);
if (db.isOpen()) {
while (cursor.moveToNext()) {
msgList.add(getItemInfo(cursor));
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
DatabaseManager.getInstance().closeDatabase();
}
return msgList;
}
/**
* 获取list集合(自定义db和cursor)
*
* @return
*/
public ArrayList<T> getListInfo(SQLiteDatabase db, Cursor cursor) {
ArrayList<T> msgList = new ArrayList<>();
try {
if (db.isOpen()) {
while (cursor.moveToNext()) {
msgList.add(getItemInfo(cursor));
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (cursor != null)
cursor.close();
DatabaseManager.getInstance().closeDatabase();
}
return msgList;
}
/**
* 获取表名
*
* @return
*/
public abstract String getTableName();
/**
* item转ContentValues
*
* @param item
* @return
*/
public abstract ContentValues getContentValues(T item);
/**
* 获取item
*
* @param cursor
* @return
*/
public abstract T getItemInfo(Cursor cursor);
/**
* 对比两个item是否相同
*
* @param item1
* @param item2
* @return
*/
public abstract boolean compareItem(T item1, T item2);
/**
* 更新item
*
* @param db
* @param item
*/
public abstract void updateItem(SQLiteDatabase db, T item);
/**
* 删除所有信息
*
* @param db
*/
public void deleteAll(SQLiteDatabase db) {
try {
db.delete(getTableName(), null, null);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 删除所有信息
*/
public void deleteAll() {
try {
SQLiteDatabase db = DatabaseManager.getInstance().openDatabase();
if (db.isOpen()) {
db.delete(getTableName(), null, null);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
DatabaseManager.getInstance().closeDatabase();
}
}
public String getString(Cursor cursor, String name) {
return cursor.getString(cursor.getColumnIndex(name));
}
public int getInt(Cursor cursor, String name) {
return cursor.getInt(cursor.getColumnIndex(name));
}
public long getLong(Cursor cursor, String name) {
return cursor.getLong(cursor.getColumnIndex(name));
}
public float getFloat(Cursor cursor, String name) {
return cursor.getFloat(cursor.getColumnIndex(name));
}
public boolean getBool(Cursor cursor, String name) {
return cursor.getInt(cursor.getColumnIndex(name)) == 1;
}
}
在BaseDao中看到使用到事务,为什么需要是事务?
使用SQLiteDatabase的beginTransaction()方法可以开启一个事务,程序执行到endTransaction() 方法时会检查事务的标志是否为成功
如果程序执行到endTransaction()之前调用了setTransactionSuccessful() 方法设置事务的标志为成功则提交事务,如果没有调用setTransactionSuccessful() 方法则回滚事务。
事务处理应用:
很多时候我们需要批量的向Sqlite中插入大量数据时,单独的使用添加方法导致应用响应缓慢, 因为sqlite插入数据的时候默认一条语句就是一个事务,有多少条数据就有多少次磁盘操作。如初始1000条记录也就是要1000次读写磁盘操作。同时也是为了保证数据的一致性,避免出现数据缺失等情况。
三、创建SQLiteOpenHelper,这里创建一个搜索历史表作为例子
public class DbOpenHelper extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 1;
private static DbOpenHelper instance;
/**
* 搜索历史数据
*/
private static final String SEARCH_HISTORY_TABLE_CREATE = "CREATE TABLE "
+ SearchHistoryDao.TABLE_NAME + " ("
+ SearchHistoryDao.COLUMN_NAME_PRIMARY_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, "
+ SearchHistoryDao.COLUMN_NAME_MSG + " TEXT, "
+ SearchHistoryDao.COLUMN_NAME_TIME + " DATETIME); ";
private DbOpenHelper(Context context) {
super(context, getUserDatabaseName(), null, DATABASE_VERSION);
}
public synchronized static DbOpenHelper getInstance(Context context) {
if (instance == null) {
instance = new DbOpenHelper(context.getApplicationContext());
}
return instance;
}
private static String getUserDatabaseName() {
return "test.db";
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(SEARCH_HISTORY_TABLE_CREATE);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
四、创建搜索历史表SearchHistoryDao
public class SearchHistoryDao extends BaseDao<String> {
public static final String TABLE_NAME = "SearchHistory";
public static final String COLUMN_NAME_PRIMARY_ID = "id";//主键id
public static final String COLUMN_NAME_MSG = "msg";//String
public static final String COLUMN_NAME_TIME = "time";//
private static SearchHistoryDao instance;
public synchronized static SearchHistoryDao getInstance() {
if (instance == null) {
instance = new SearchHistoryDao();
}
return instance;
}
@Override
public String getTableName() {
return TABLE_NAME;
}
@Override
public ContentValues getContentValues(String item) {
ContentValues values = new ContentValues();
values.put(COLUMN_NAME_MSG, item);
values.put(COLUMN_NAME_TIME, new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.CHINA).format(new Date()));
return values;
}
@Override
public String getItemInfo(Cursor cursor) {
return getString(cursor, COLUMN_NAME_MSG);
}
@Override
public boolean compareItem(String item1, String item2) {
return item1.equals(item2);
}
@Override
public void updateItem(SQLiteDatabase db, String item) {
db.update(getTableName(), getContentValues(item), COLUMN_NAME_MSG + " = ?", new String[]{item});
}
}
五、初始化数据库
public class MyApplication extends BaseApplication {
@Override
public void onCreate() {
super.onCreate();
DatabaseManager.initializeInstance(DbOpenHelper.getInstance(this));
}
}
六、使用查询表
插入
SearchHistoryDao.getInstance().insert("123")
查询
searchHistoryDao.getInstance().getListInfo()