SQLite数据库一款轻量级关系数据库,轻量型、跨平台、多语言接口、安全、支持事务处理,而且运算速度快,占用资源少。Android数据持久化存储的内部存储主要存在在内部存储设备上,恰恰Android内部设备存储空间往往是有限的,如果不有效的管理内部存储空间,很有可能导致手机直接崩溃无法使用。而且SQLite轻量级及相关有点正好符合了Android本身的特性。其实Android另外两个存储方式:文件存储和SharePreferences存储中的SharePreferences存储就属于轻量型存储机智。而对于文件存储要去也是尽量存储到外部存储设备中。因此SQLite本身的优点特别适合在移动设备上使用。
本博客主要围绕SQLite展开讲述,其实已经非常便捷和成熟的SQLite封装的工具,有兴趣的可以可以网上搜索一下几个框架去了解:
开源库 ---OrmLite
开源库 ---greenDAO
开源库 ---SugarORM
开源库 ---ActiveAndroid
开源库 ---LitePal
开源库 ---realm
在这里重复简述一下SQLite的特点:
1、SQLite作为一个轻型的关系型数据库;
2、运算速度非常快,占用资源很少;
3、SQLite不仅支持标准的SQL语法,还遵守了数据库的 ACID 事务,上手快;
4、提供了零配置运行模式;
5、无需服务进程,轻量、跨平台、多语言接口及安全性等;
创建数据主要有四种方式:
1、create 在内存中创建数据库,当数据库关闭时即销毁;
2、openOrCreateDatabase 创建并打开数据库;
3、继承SQLiteOpenHelper类,且通过getWritableDatabase和getReadableDatabase方法创建;
4、openDatabase 创建并打开数据库,可以指定打开方式;
create简述
/**
* Create a memory backed SQLite database. Its contents will be destroyed
* when the database is closed.
*
* Sets the locale of the database to the the system's current locale.
* Call {@link #setLocale} if you would like something else.
*
* @param factory an optional factory class that is called to instantiate a
* cursor when query is called
* @return a SQLiteDatabase object, or null if the database can't be created
*/
public static SQLiteDatabase create(CursorFactory factory) {
// This is a magic string with special meaning for SQLite.
return openDatabase(SQLiteDatabaseConfiguration.MEMORY_DB_PATH,
factory, CREATE_IF_NECESSARY);
}
通过查看源码,我们可以发现,使用create方式创建数据库,最终是调用openDatabase方式来实现数据库的创建。
openOrCreateDatabase简述
/**
* Equivalent to openDatabase(file.getPath(), factory, CREATE_IF_NECESSARY).
*/
public static SQLiteDatabase openOrCreateDatabase(File file, CursorFactory factory) {
return openOrCreateDatabase(file.getPath(), factory);
}
/**
* Equivalent to openDatabase(path, factory, CREATE_IF_NECESSARY).
*/
public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory) {
return openDatabase(path, factory, CREATE_IF_NECESSARY, null);
}
/**
* Equivalent to openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler).
*/
public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory,
DatabaseErrorHandler errorHandler) {
return openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler);
}
通过查看源码,openOrCreateDatabase方式创建数据库,不管是带两个参数,还是三个,最终是通过调用openDatabase方式来实现数据库的创建。
getWritableDatabase和getReadableDatabase简述
* Create and/or open a database that will be used for reading and writing.
* The first time this is called, the database will be opened and
* {@link #onCreate}, {@link #onUpgrade} and/or {@link #onOpen} will be
* called.
*
* <p>Once opened successfully, the database is cached, so you can
* call this method every time you need to write to the database.
* (Make sure to call {@link #close} when you no longer need the database.)
* Errors such as bad permissions or a full disk may cause this method
* to fail, but future attempts may succeed if the problem is fixed.</p>
*
* <p class="caution">Database upgrade may take a long time, you
* should not call this method from the application main thread, including
* from {@link android.content.ContentProvider#onCreate ContentProvider.onCreate()}.
*
* @throws SQLiteException if the database cannot be opened for writing
* @return a read/write database object valid until {@link #close} is called
*/
public SQLiteDatabase getWritableDatabase() {
synchronized (this) {
return getDatabaseLocked(true);
}
}
private SQLiteDatabase getDatabaseLocked(boolean writable) {
...
SQLiteDatabase db = mDatabase;
try {
mIsInitializing = true;
if (db != null) {
if (writable && db.isReadOnly()) {
db.reopenReadWrite();
}
} else if (mName == null) {
db = SQLiteDatabase.create(null);
} else {
try {
if (DEBUG_STRICT_READONLY && !writable) {
final String path = mContext.getDatabasePath(mName).getPath();
//位置1
db = SQLiteDatabase.openDatabase(path, mFactory,
SQLiteDatabase.OPEN_READONLY, mErrorHandler);
} else {
//位置2
db = mContext.openOrCreateDatabase(mName, mEnableWriteAheadLogging ?
Context.MODE_ENABLE_WRITE_AHEAD_LOGGING : 0,
mFactory, mErrorHandler);
}
} catch (SQLiteException ex) {
if (writable) {
throw ex;
}
Log.e(TAG, "Couldn't open " + mName
+ " for writing (will try read-only):", ex);
final String path = mContext.getDatabasePath(mName).getPath();
//位置3
db = SQLiteDatabase.openDatabase(path, mFactory,
SQLiteDatabase.OPEN_READONLY, mErrorHandler);
}
}
...
}
同样通过分析源码,我们可以看到getWritableDatabase和getReadableDatabase两个方法最终会调用getDatabaseLocked方法,而在getDatabaseLocked方法内,通过位置1、位置2和位置3我们可以分析得出结果,getWritableDatabase和getReadableDatabase方式创建数据,最终也是通过调用openDatabase方式来实现数据库的创建。
openDatabase简述
通过分析create方式、方式、openOrCreateDatabase、getWritableDatabase和getReadableDatabase方式创建数据的源码,得出在最终是通过openDatabase方式来创建数据库的,所以这里我们做种分析一下openDatabase方法;
/**
* Open the database according to the flags {@link #OPEN_READWRITE}
* {@link #OPEN_READONLY} {@link #CREATE_IF_NECESSARY} and/or {@link #NO_LOCALIZED_COLLATORS}.
*
* Sets the locale of the database to the the system's current locale.
* Call {@link #setLocale} if you would like something else.
*
* Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be
* used to handle corruption when sqlite reports database corruption.
*
* @param path to database file to open and/or create
* @param factory an optional factory class that is called to instantiate a
* cursor when query is called, or null for default
* @param flags to control database access mode
* @param errorHandler the {@link DatabaseErrorHandler} obj to be used to handle corruption
* when sqlite reports database corruption
* @return the newly opened database
* @throws SQLiteException if the database cannot be opened
*/
public static SQLiteDatabase openDatabase(String path, CursorFactory factory, int flags,
DatabaseErrorHandler errorHandler) {
SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler);
db.open();
return db;
}
openDatabase方法分别有四个参数:
第一个参数:path代表着数据库的路径(如果是在默认路径/data/data/
第二个参数:factory代表着在创建Cursor对象时,使用的工厂类,如果为null的话,则使用默认的工厂(这里我们可以实现自己的工厂进行某些数据处理);
第三个参数:flags代表的是创建表时的一些权限设置,多个权限之间用|分隔:
权限 | 权限说明 |
---|---|
OPEN_READONLY | 代表的是以只读方式打开数据库(常量值为:1) |
OPEN_READWRITE | 代表以读写方式打开数据库(常量值为:0) |
CREATE_IF_NECESSARY | 当数据库不存在时创建数据库 |
NO_LOCALIZED_COLLATORS | 打开数据库时,不根据本地化语言对数据库进行排序(常量值为:16) |
第四个参数:errorHandler数据损坏时的回调参数(可null);
以上介绍的四种创建数据库的方式,最终目的就是为了得到SQLiteDatabase对象,因为只有在得到了SQLiteDatabase
对象,才能对相应的数据库增、删、改、查
,一下用代码形式简单表述一下用以上四种方式获取SQLiteDatabase对象。
create方式获取数据库对象
//在内存中创建数据库,当数据库关闭时即销毁;
SQLiteDatabase database = null;
database = SQLiteDatabase.create(null);
openOrCreateDatabase方式获取数据库对象
SQLiteDatabase database = null;
method 1:
//file_path为文件存储路径
File file = new File("file_path");
database = SQLiteDatabase.openOrCreateDatabase(file, null);
method 2:
//data_name为数据名称,且创建的数据库是在默认路径/data/data//databases/下
String dataName = "data_name.db";
database = SQLiteDatabase.openOrCreateDatabase(dataName, null);
//第3种方式和第2中方式没有本质上的区别,区别在于是否对数据损坏进行监测
method 3:
//data_name为数据名称,且创建的数据库是在默认路径/data/data//databases/下
String dataName = "data_name.db";
database = SQLiteDatabase.openOrCreateDatabase(dataName, null, null);
openDatabase方式获取数据库对象
SQLiteDatabase database = null;
method 1:
//data_name为数据名称,且创建的数据库是在默认路径/data/data//databases/下
String dataName = "data_name.db";
database = SQLiteDatabase.openDatabase(dataName, null, CREATE_IF_NECESSARY);
method 2:
//data_name为数据名称,且创建的数据库是在默认路径/data/data//databases/下
String dataName = "data_name.db";
database = SQLiteDatabase.openDatabase(dataName, null, CREATE_IF_NECESSARY, null);
getWritableDatabase和getReadableDatabase方式获取数据库对象
在使用前简单介绍一下SQLiteOpenHelper抽象类,SQLiteOpenHelper是SQLiteDatabase的帮助类, 用于管理数据库的创建和升级。
需要一个继承SQLiteOpenHelper抽象类的自定义类,我们通过继承该类,然后重写数据库创建以及更新的方法, 我们还可以通过该类的对象获得数据库实例,或者关闭数据库;
a、首先需要实现一个自定义类继承于SQLiteOpenHelper
public class DBOpenHelper extends SQLiteOpenHelper {
// 相当于 SQLiteDatabase openDatabase(String, CursorFactory)
public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
// 相当于 SQLiteDatabase openDatabase(String, CursorFactory, DatabaseErrorHandler)
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public DBOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version,
DatabaseErrorHandler errorHandler) {
super(context, name, factory, version, errorHandler);
}
// 相当于 SQLiteDatabase openDatabase(String , OpenParams);
@TargetApi(Build.VERSION_CODES.P)
public DBOpenHelper(Context context, String name, int version, SQLiteDatabase.OpenParams openParams) {
super(context, name, version, openParams);
}
// 创建数据文件时调用,此时适合创建新表
@Override
public void onCreate(SQLiteDatabase db) {
}
// 更新数据库版本时调用,适合更新表结构或创建新表
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
b、其次通过实现DBOpenHelper类,在通过实现该类得到的对象,调用getWritableDatabase或getReadableDatabase方法实现创建数据库,如下:
SQLiteDatabase database = null;
//data_name为数据名称,且创建的数据库是在默认路径/data/data//databases/下
String dataName = "data_name.db";
// 生成 helper 对象,可以打开数据库文件。文件名可以是相对路径或绝对路径
DBOpenHelper dbHelper = new DBOpenHelper(this, dataName, null, 1);
// 用读写的方式打开数据库文件
database = dbHelper.getWritableDatabase();
备注:如果不采用第三方框架的情况下,只是在内部存储设备中创建数据库,最好在使用getWritableDatabase和getReadableDatabase方式获取数据库对象,如果是有需要使用外部数据,那么可以使用openOrCreateDatabase方式。
在第二部分中,我们通过四种方式中的任何一种方式,获取SQLiteDatabase对象,通过该对象我们可以实现接下来的增删改查功能。
新增数据
/**
* 向数据库插入一行数据
*
* @param table 指定表名,插入一行数据
* @param nullColumnHack 可选参数,建议是 null
* 如果设置 null,将不允许向表中插入空数据,即 values = null 时无法正确执行插入操作。
* 如果不设置 null,那么需要设置表中可以为空的属性列的名称。
* 当 values = null 时,可以向表中插入空数据。
* 而实际上是插入一行数据,只有属性列名 nullColumnHack 的值是 null。
* @param values map 集合,包含需要插入的一行数据。至少需要包含一个属性的 key 和 value。
* key 是属性列名称,value 是属性值。
* @return 返回新插入的行序号, 发生错误时,返回 -1
*/
public long insert(String table, String nullColumnHack, ContentValues values) {
try {
return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + values, e);
return -1;
}
}
exp:
/**
* SQL插入语句:
* INSERT INTO tab(param1,param2,param3) VALUES ("param1","param2","param3");
*/
method 1:
String sql = "INSERT INTO tab VALUES(?,?,?)";
database.execSQL(sql,new String[]{"param1","param2","param3"});
method 2:
ContentValues contentValues = new ContentValues();
contentValues.put("param1", "param1");
contentValues.put("param2", "param2");
contentValues.put("param3", "param3");
/**
* @第一参数 table 指定表名,插入一行数据
* @第二参数 nullColumnHack 可选参数,建议是 null
* 如果设置 null,将不允许向表中插入空数据,即 values = null 时无法正确执行插入操作。
* 如果不设置 null,那么需要设置表中可以为空的属性列的名称。
* 当 values = null 时,可以向表中插入空数据。
* 而实际上是插入一行数据,只有属性列名 nullColumnHack 的值是 null。
* @第三参数 values map 集合,包含需要插入的一行数据。至少需要包含一个属性的 key 和 value。
* key 是属性列名称,value 是属性值。
* @return 返回新插入的行序号, 发生错误时,返回 -1
*/
database.insert("tab", null, contentValues);
删除数据
/**
* 删除数据库中一行的方法
*
* @param table 需要删除的表名
* @param whereClause 传入 null 时表示删除表中全部数据。
* 或者指定删除条件,只会删除满足条件的行。
* @param whereArgs 指定删除条件的值,按照顺序替换在删除条件中的 ? 。
* @return 删除满足条件的行时,返回删除的行数。找不到满足条件删除的时候,返回 0 。
*/
public int delete(String table, String whereClause, String[] whereArgs) {
acquireReference();
try {
SQLiteStatement statement = new SQLiteStatement(this, "DELETE FROM " + table +
(!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs);
try {
return statement.executeUpdateDelete();
} finally {
statement.close();
}
} finally {
releaseReference();
}
}
exp:
/**
*SQL删除语句:
* DELETE FROM tab WHERE param1 = 'param1';
*/
method 1:
String sql = "DELETE FROM tab WHERE param1 = ?";
database.execSQL(sql,new String[]{"param1"});
method 2:
/**
* @第一参数 table 需要删除的表名
* @第二参数 whereClause 传入 null 时表示删除表中全部数据。
* 或者指定删除条件,只会删除满足条件的行。
* @第三参数 whereArgs 指定删除条件的值,按照顺序替换在删除条件中的 ? 。
* @return 删除满足条件的行时,返回删除的行数。找不到满足条件删除的时候,返回 0 。
*/
database.delete("tab", "param1=?", new String[]{"param1"});
修改数据
/**
* 更新数据库表中的一行数据
*
* @param table 需要更新的表名
* @param values 包含属性名和新属性值的 map 集合。
* @param whereClause 可选的 WHERE 条件决定需要更新的行。
* 如果是空,则更新所有的行。
* @param whereArgs 替换在 where 条件中包含的 ? 。
* @return 返回更新的行数
*/
public int update(String table, ContentValues values, String whereClause, String[] whereArgs) {
return updateWithOnConflict(table, values, whereClause, whereArgs, CONFLICT_NONE);
}
exp:
/**
* SQL更新语句:
* UPDATE tab SET param1 = "newname" WHERE param1 = 'param1' ;
*/
method 1:
String sql = "UPDATE tab SET param1 = ? WHERE param1 = ?";
database.execSQL(sql,new String[]{"newname", "param1"});
method 2:
ContentValues contentValues = new ContentValues();
contentValues.put("param1", "newname");
/**
* @第一参数 table 需要更新的表名
* @第二参数 values 包含属性名和新属性值的 map 集合。
* @第三参数 whereClause 可选的 WHERE 条件决定需要更新的行。
* 如果是空,则更新所有的行。
* @第四参数 whereArgs 替换在 where 条件中包含的 ? 。
* @return 返回更新的行数
*/
database.update("tab", contentValues, "param1=?", new String[]{"param1"});
查询数据
/**
* 查询数据库插数据
*
* @param sql 所有的 query 方法,最后都会合并出 sql 执行 rawquery 方法
* @param selectionArgs 替换在 selection 中使用的 ? 或 sql 中使用的 ?。
* @return 返回Cursor(查询到的数据)
*/
public Cursor rawQuery(String sql, String[] selectionArgs) {
return rawQueryWithFactory(null, sql, selectionArgs, null, null);
}
exp:
/**
* SQL查询语句:
* SELECT * FROM tab ;
*/
String sql = "SELECT * FROM tab";
Cursor cursor = database.rawQuery(sql, null);
事务(Transaction)是一个对数据库执行工作单元。事务(Transaction)是以逻辑顺序完成的工作单位或序列,可以是由用户手动操作完成。SQLite 事务主要提供了三个 API。在结束事务前,如想将事务提交到数据库,需要设置事务完成标记。否则,在事务开启时候做的数据库操作将不会保留。
exp:
database.beginTransaction();
......
/* 此处执行数据库操作 */
......
database.setTransactionSuccessful();
database.endTransaction();
cursor(游标)是系统为用户开设的一个数据缓冲区,存放SQL语句的执行结果
cursor常用方法介绍:
1、moveToFirst():将指针移动到结果集的第一行;
2、getColumnIndex():获取某一列在表中对应位置的索引;
3、close():关闭指针。
4、move(int offset):以当前位置为参考,移动到指定行
5、moveToLast():移动到最后一行
6、moveToPosition(int position):移动到指定行
7、moveToPrevious():移动到前一行
8、moveToNext():移动到下一行
9、isFirst():是否指向第一条
10、isLast():是否指向最后一条
11、isBeforeFirst():/是否指向第一条之前
12、isAfterLast():是否指向最后一条之后
13、isNull(int columnIndex):指定列是否为空(列基数为0)
14、isClosed():游标是否已关闭
15、getCount():总数据项数
16、getPosition():返回当前游标所指向的行数
17、getString(int columnIndex):返回当前行指定列的值
exp:
Cursor cursor = database.rawQuery("select * from tab", null);
while (cursor.moveToNext()) {
String param1= cursor.getString(cursor.getColumnIndex("param1"));
String param2= cursor.getString(cursor.getColumnIndex("param2"));
String param3= cursor.getString(cursor.getColumnIndex("param3"));
}
cursor.close();
Android数据库存储到此基本也介绍完了,其实这些都非常基础的数据库处理方式,如果你对Android原生的数据库创建方式感觉繁琐,其实你考虑使用第三方开源库。开源库在某种程度上简化了数据创建以及增删改查等功能。
到此Android的三大数据持久化存储(SharePreferences存储、文件存储、数据库存储)之数据库存储就介绍到这了。