概述
文章目录
- 0 前言
- 1 创建数据库
- 1.1 create
- 1.2 openDatabase
- 1.3 openOrCreateDatabase
- 1.4 SQLiteOpenHelper
- 1.5 使用场景
- 2 插入数据
- 3 删除数据
- 4 更新数据
- 5 查询数据
- 6 事务
- 7 结束语
0 前言
SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它包含在一个相对小的C库中。它是D.RichardHipp建立的公有领域项目。它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如 Tcl、C#、PHP、Java等,还有ODBC接口,同样比起Mysql、PostgreSQL这两款开源的世界著名数据库管理系统来讲,它的处理速度比他们都快。(摘自百度百科)
鉴于 SQLite
具备如此优秀的业务处理能力,Android 平台自然使用它作为内置的数据库存储模块。存储在 SD 卡上,只是一个文件的形式,可以方便的备份、移植。有兴趣的小伙伴,可以至SQLite官网下载 C 源码研究研究。
另外 Android 平台提供了一整套的 SQLite 操作 API。可以方便快速的创建表、更新表、对表数据进行增删改查。
1 创建数据库
创建数据库方法,分四类。
- create/createInMemory 在内存中创建数据库,当数据库关闭时即销毁。
- openDatabase 打开数据库,可以指定打开方式。
- openOrCreateDatabase 创建并打开数据库。
- getWritableDatabase/getReadableDatabase 打开读写,或只读数据库。
上述四个方式,都可以指定 CursorFactory
、Flags
、DatabaseErrorHandler
(API 11)、OpenParams
(API 27) 四个基本参数。最终都 执行到 openDatabase
方法。
SQLite 对数据库文件添加四种访问权限,做 flags 参数:
- OPEN_READWRITE:读写方式打开数据库文件,可以进行增删改查等操作。
- OPEN_READONLY:只读方式打开数据库文件,只能执行查询操作,插入数据会抛出异常
android.database.sqlite.SQLiteReadOnlyDatabaseException
。 - CREATE_IF_NECESSARY:打开数据库时,如没有数据库文件,便会自动创建,并允许增删改查。
- NO_LOCALIZED_COLLATORS: 使 setLocale 方法不生效。
// 执行 query 方法时,使用该对象,用于生成 cursor
SQLiteDatabase.CursorFactory factory = new SQLiteDatabase.CursorFactory() {
@Override
public Cursor newCursor(SQLiteDatabase db, SQLiteCursorDriver masterQuery, String editTable, SQLiteQuery query) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
return new SQLiteCursor(masterQuery, editTable, query);
}else
return new SQLiteCursor(db,masterQuery, editTable, query);
}
};
// 数据库发生异常时,执行该对象。
DatabaseErrorHandler errorHandler = new DatabaseErrorHandler() {
@Override
public void onCorruption(SQLiteDatabase dbObj) {
// 做关闭数据库的操作
dbObj.close();
}
};
// 打开数据库的参数,API 27 才支持。
SQLiteDatabase.OpenParams openParams = new SQLiteDatabase.OpenParams.Builder()
.setCursorFactory(factory) // 指定 CursorFactory
.setErrorHandler(errorHandler)// 指定 DatabaseErrorHandler
.addOpenFlags(SQLiteDatabase.CREATE_IF_NECESSARY) // 指定打开权限。
.build();
1.1 create
直接在内存中创建数据库,create/createInMemory 两个方法,后者在 API 27 才支持,建议使用 create。
/**
* 在内存中创建 SQLite 数据库,当数据库关闭时,即销毁。
* 适合临时保存数据。
*
* @param factory 可选参数,建议传入 NULL 或者构建 factory 实例。
* @return 返回数据库对象
* @throws 数据库创建失败时,抛出 SQLiteException
*/
@NonNull
public static SQLiteDatabase create(@Nullable CursorFactory factory) {
// This is a magic string with special meaning for SQLite.
return openDatabase(SQLiteDatabaseConfiguration.MEMORY_DB_PATH,
factory, CREATE_IF_NECESSARY);
}
/**
* 在内存中创建 SQLite 数据库,当数据库关闭时,即销毁。
* 适合临时保存数据。
*
* @param openParams 配置打开数据库的参数
* @return 返回数据库对象
* @throws 数据库创建失败时,抛出 SQLiteException
*/
@NonNull
public static SQLiteDatabase createInMemory(@NonNull OpenParams openParams) {
return openDatabase(SQLiteDatabaseConfiguration.MEMORY_DB_PATH,
openParams.toBuilder().addOpenFlags(CREATE_IF_NECESSARY).build());
}
都执行到 openDatabase 方法,Flags = CREATE_IF_NECESSARY。
1.2 openDatabase
/**
* 根据 flags 打开数据库
*
* @param path 创建或打开数据库的文件路径。可以使用相对地址或绝对地址。
*
相对地址存在应用缓存 database 目录,绝对地址可以存在 sd 卡目录下。
* @param factory 可选参数,建议传入 NULL 或者构建 factory 实例。
* @param flags 控制数据的方式方式,
* @return 返回打开新打开的数据库
* @throws 数据库创建失败时,抛出 SQLiteException
*/
public static SQLiteDatabase openDatabase(@NonNull String path, @Nullable CursorFactory factory,
@DatabaseOpenFlags int flags) {
return openDatabase(path, factory, flags, null);
}
/**
* 根据 flags 打开数据库
*
* @param path 打开或创建数据库的文件
* @param factory 可选参数,建议传入 NULL 或者构建 factory 实例。
* @param flags 控制数据库文件的访问方式
* @param errorHandler 当 SQLite 报出数据库出错时,使用 DatabaseErrorHandler 处理错误。如关闭数据库。
* @return 返回打开新打开的数据库
* @throws 数据库创建失败时,抛出 SQLiteException
*/
public static SQLiteDatabase openDatabase(@NonNull String path, @Nullable CursorFactory factory,
@DatabaseOpenFlags int flags, @Nullable DatabaseErrorHandler errorHandler) {
SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler, -1, -1, -1, null,
null);
db.open();
return db;
}
/**
* 根据指定的参数,打开数据库
*
* @param path 创建或打开数据库的文件路径。
*
使用绝对路径,或者通过context#getDatabasePath(String)
* @param openParams 配置打开数据库的参数
* @return 返回打开新打开的数据库
* @throws 数据库创建失败时,抛出 SQLiteException
*/
public static SQLiteDatabase openDatabase(@NonNull File path,
@NonNull OpenParams openParams) {
return openDatabase(path.getPath(), openParams);
}
private static SQLiteDatabase openDatabase(@NonNull String path,
@NonNull OpenParams openParams) {
Preconditions.checkArgument(openParams != null, "OpenParams cannot be null");
SQLiteDatabase db = new SQLiteDatabase(path, openParams.mOpenFlags,
openParams.mCursorFactory, openParams.mErrorHandler,
openParams.mLookasideSlotSize, openParams.mLookasideSlotCount,
openParams.mIdleConnectionTimeout, openParams.mJournalMode, openParams.mSyncMode);
db.open();
return db;
}
四个方法中,可以分为两类。
- 前两个分别指定 Flags、CursorFactory 和 DatabaseErrorHandler(API 11)支持。
- 后两个统一由 OpenParams 指定打开数据库的参数。包含但不限于上面三个属性。
1.3 openOrCreateDatabase
/**
* 相当于 openDatabase(file.getPath(), factory, CREATE_IF_NECESSARY).
*/
public static SQLiteDatabase openOrCreateDatabase(@NonNull File file,
@Nullable CursorFactory factory) {
return openOrCreateDatabase(file.getPath(), factory);
}
/**
* 相当于 openDatabase(path, factory, CREATE_IF_NECESSARY).
*/
public static SQLiteDatabase openOrCreateDatabase(@NonNull String path,
@Nullable CursorFactory factory) {
return openDatabase(path, factory, CREATE_IF_NECESSARY, null);
}
/**
* 相当于 openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler).
*/
public static SQLiteDatabase openOrCreateDatabase(@NonNull String path,
@Nullable CursorFactory factory, @Nullable DatabaseErrorHandler errorHandler) {
return openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler);
}
创建并打开数据库,三个方法最后都会执行到 SQLiteDatabase openDatabase(String ,CursorFactory,int,DatabaseErrorHandler)
方法。
1.4 SQLiteOpenHelper
SQLiteOpenHelper 是 Android 封装的最人性化的工具。方便开发者自行管理数据库表目录和结构。
/**
* 管理数据库创建和版本
*/
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) {
}
}
// 生成 helper 对象,可以打开数据库文件。文件名可以是相对路径或绝对路径
DBOpenHelper dbHelper = new DBOpenHelper(this, "test.db", null, 1);
// 用读写的方式打开数据库文件
SQLiteDatabase database = dbHelper.getWritableDatabase();
1.5 使用场景
openDatabase 适合打开已经存在的数据库文件。并且表目录和结构固定,一般只有对数据库表的常规操作。如,已有一个全国城市信息数据库文件,需要在项目中打开。
SQLiteOpenHelper 需要自己重新创建数据库文件,根据后期的需求可能需要对数据库的表目录及结构做修改。升级数据版本,在onUpgrade
方法中处理即可。openDatabase 也可以实现这个需求,只是稍微麻烦些。
2 插入数据
插入操作方法总结为两类,insert 和 replace ,最后都执行 insertWithOnConflict 方法。
/**
* 向数据库插入一行数据
*
* @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;
}
}
/**
* 向数据库插入一行数据。只是在插入操作异常时,需要手动捕获异常。
* 参数都同上
* @param table
* @param nullColumnHack
* @param values
* @throws SQLException
* @return
*/
public long insertOrThrow(String table, String nullColumnHack, ContentValues values)
throws SQLException {
return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
}
/**
* 根据主键替换表中的一行数据,如果替换的主键在表中不存在,则插入一行数据。
* 参数都同上
* @param table
* @param nullColumnHack
* @param initialValues
* @return 返回新插入的行序号, 发生错误时,返回 -1
*/
public long replace(String table, String nullColumnHack, ContentValues initialValues) {
try {
return insertWithOnConflict(table, nullColumnHack, initialValues,
CONFLICT_REPLACE);
} catch (SQLException e) {
Log.e(TAG, "Error inserting " + initialValues, e);
return -1;
}
}
/**
* 根据主键替换表中的一行数据,如果替换的主键在表中不存在,则插入一行数据。
* 参数都同上
* @param table
* @param nullColumnHack
* @param initialValues
* @throws SQLException
* @return
*/
public long replaceOrThrow(String table, String nullColumnHack,
ContentValues initialValues) throws SQLException {
return insertWithOnConflict(table, nullColumnHack, initialValues,
CONFLICT_REPLACE);
}
/**
* 向数据库中插入一行数据的通用方法。
* 参数同上。
* @param table
* @param nullColumnHack
* @param initialValues
* @param conflictAlgorithm 解决冲突的处理算法
* @return 返回插入数据的行序号。当 conflictAlgorithm 错误或者发生插入异常时,返回-1。
*/
public long insertWithOnConflict(String table, String nullColumnHack,
ContentValues initialValues, int conflictAlgorithm) {
acquireReference();
try {
StringBuilder sql = new StringBuilder();
sql.append("INSERT");
// 冲突处理算法,插入时只用第一个和最后一个。
// {"", "OR ROLLBACK", "OR ABORT", "OR FAIL", "OR IGNORE", "OR REPLACE"};
sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(" INTO ");
sql.append(table);
sql.append('(');
Object[] bindArgs = null;
int size = (initialValues != null && !initialValues.isEmpty())
? initialValues.size() : 0;
// 拼接 sql 语句
if (size > 0) {
bindArgs = new Object[size];
int i = 0;
for (String colName : initialValues.keySet()) {
sql.append((i > 0) ? "," : "");
// 拼接列名称
sql.append(colName);
// 保存属性值参数
bindArgs[i++] = initialValues.get(colName);
}
sql.append(')');
sql.append(" VALUES (");
// 将所有的值用 ? 做占位符
for (i = 0; i < size; i++) {
sql.append((i > 0) ? ",?" : "?");
}
} else {
// 插入的数据集合是空时,至少需要有一列数据,列名称是 nullColumnHack,值是 NULL
sql.append(nullColumnHack + ") VALUES (NULL");
}
sql.append(')');
// 执行插入操作
SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
try {
return statement.executeInsert();
} finally {
statement.close();
}
} finally {
releaseReference();
}
}
演示代码,不掩饰抛出异常的方法:
ContentValues values = new ContentValues();
values.put("id", 1);
values.put("name", "flueky");
values.put("age", 27);
// 插入完整的数据,id name age
database.insert("user", null, values);
// 插入空数据,将 name 设置为 null
database.insert("user", "name", null);
// 替换 id = 1 的数据
values.put("id", 1);
values.put("name", "xiaofei");
values.put("age", 27);
database.replace("user", null, values);
replace
方法类似于 update
操作。
3 删除数据
删除数据库表数据,只有一个方法。支持指定删除条件
/**
* 删除数据库中一行的方法
*
* @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();
}
}
4 更新数据
更新表数据方法有两个,建议只用第一个,conflictAlgorithm
参数默认 CONFLICT_NONE
。
/**
* 更新数据库表中的一行数据
*
* @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);
}
/**
* 更新数据库表中的一行数据
* 参数同上
*
* @param table
* @param values
* @param whereClause
* @param whereArgs
* @param conflictAlgorithm 决定更新冲突的算法。
* @return
*/
public int updateWithOnConflict(String table, ContentValues values,
String whereClause, String[] whereArgs, int conflictAlgorithm) {
// 官方源码中说,null is valid,此处却抛出异常,有点费解
if (values == null || values.isEmpty()) {
throw new IllegalArgumentException("Empty values");
}
acquireReference();
try {
// 拼接更新的 sql 语句
StringBuilder sql = new StringBuilder(120);
sql.append("UPDATE ");
sql.append(CONFLICT_VALUES[conflictAlgorithm]);
sql.append(table);
sql.append(" SET ");
// 将 values 和 whereArgs 值拼接在一起
int setValuesSize = values.size();
int bindArgsSize = (whereArgs == null) ? setValuesSize : (setValuesSize + whereArgs.length);
Object[] bindArgs = new Object[bindArgsSize];
int i = 0;
// 拼接需要更新的列属性
for (String colName : values.keySet()) {
sql.append((i > 0) ? "," : "");
sql.append(colName);
// 保存属性值
bindArgs[i++] = values.get(colName);
sql.append("=?");
}
// 添加 where 条件的值
if (whereArgs != null) {
for (i = setValuesSize; i < bindArgsSize; i++) {
bindArgs[i] = whereArgs[i - setValuesSize];
}
}
if (!TextUtils.isEmpty(whereClause)) {
sql.append(" WHERE ");
sql.append(whereClause);
}
SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
try {
// 执行 sql 语句
return statement.executeUpdateDelete();
} finally {
statement.close();
}
} finally {
releaseReference();
}
}
5 查询数据
Android 给数据库的表查询操作提供了丰富的 API,一共10个方法,去除重载方法,共四个。下表中列出,四个方法和使用的参数。
- ● 表示必传参数
- ○ 表示可传参数
- - 表示不用的参数
参数 | query | queryWithFactory | rawQuery | rawQueryWithFactory |
---|---|---|---|---|
String table | ● | ● | - | - |
String[] columns | ● | ● | - | - |
String selection | ● | ● | - | - |
String[] selectionArgs | ● | ● | ● | ● |
String groupBy | ● | ● | - | - |
String having | ● | ● | - | - |
String orderBy | ● | ● | - | - |
String limit | ○ | ● | - | - |
boolean distinct | ○ | ● | - | - |
CancellationSignal cancellationSignal | ○ | ○ | ○ | ○ |
CursorFactory cursorFactory | - | ● | - | ● |
String sql | - | - | ● | ● |
String editTable | - | - | - | ● |
参数说明:
- table 需要查询的表名。
- columns 列出需要查询的列。传递
null
将返回所有的列。不建议这样做,防止从不用的存储中读取数据。 - selection 声明需要返回表数据的过滤器,同
where
条件子句格式一样(不包含where
自身)。传入null
将返回表中所有的数据。 - selectionArgs 替换在
selection
中使用的?
或sql
中使用的?
。 - groupBy 申明返回的表数据的分组规则,同
GROUP BY
子句一样(不包含GROUP BY
)传入null
将不分组。 - having 决定哪些分组会包含在
cursor
中。如果使用了分组条件将按照HAVING
子句(不包含HAVING
)去格式化分组。传入null
会将所有的分组都包含在cursor
中。且不使用分组时,必须用null
。 - orderBy 对表数据进行排序。同
ORDER BY
的条件语句一样(不包含ORDER BY
)。传入null
将不排序。 - limit 限定查询数据的条数。同
LIMIT
子句。传入null
不限定查询条数。 - distinct 如果想每行数据不一样,用
true
,否则用false
。 - cancellationSignal 取消查询进程中的操作信号。如果不需要手动取消,使用
null
。如果操作被取消会抛出OperationCanceledException
。 - cursorFactory 生成
Cursor
对象,如果是null
,则使用打开数据库时使用的cursorFactory
。如果打开数据库时也使用null
,那么自动生成Cursor
。 - sql 所有的
query
方法,最后都会合并出sql
执行rawquery
方法。 - editTable 指定需要编辑的表名。
/**
* 最简单也最常用的 query 方法。最后拼接好 sql 语句,去执行 rawQuery 方法。
*/
public Cursor query(String table, String[] columns, String selection,
String[] selectionArgs, String groupBy, String having,
String orderBy) {
return query(false, table, columns, selection, selectionArgs, groupBy,
having, orderBy, null /* limit */);
}
/**
* 参数最全的 query 方法,所有的参数可以手动指定,或使用 null。除 table。
*/
public Cursor queryWithFactory(CursorFactory cursorFactory,
boolean distinct, String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit, CancellationSignal cancellationSignal) {
acquireReference();
try {
String sql = SQLiteQueryBuilder.buildQueryString(
distinct, table, columns, selection, groupBy, having, orderBy, limit);
return rawQueryWithFactory(cursorFactory, sql, selectionArgs,
findEditTable(table), cancellationSignal);
} finally {
releaseReference();
}
}
/**
* 最简单也最常用的 rawQuery 方法。可以自定义好查询的 sql 语句后,自主查询。适用于复杂的关系表关联查询。
*/
public Cursor rawQuery(String sql, String[] selectionArgs) {
return rawQueryWithFactory(null, sql, selectionArgs, null, null);
}
/**
* 参数最全的 rawQuery 方法,使用场景不多。
*/
public Cursor rawQueryWithFactory(
CursorFactory cursorFactory, String sql, String[] selectionArgs,
String editTable, CancellationSignal cancellationSignal) {
acquireReference();
try {
SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable,
cancellationSignal);
return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory,
selectionArgs);
} finally {
releaseReference();
}
}
6 事务
事务(Transaction
)是一个对数据库执行工作单元。事务(Transaction
)是以逻辑顺序完成的工作单位或序列,可以是由用户手动操作完成,也可以是由某种数据库程序自动完成。
Android
中,SQLite
事务主要提供了三个 API。在结束事务前,如想将事务提交到数据库,需要设置事务完成标记。否则,在事务开启时候做的数据库操作将不会保留。
database.beginTransaction();
/* 此处执行数据库操作 */
database.setTransactionSuccessful();
database.endTransaction();
7 结束语
本文到此,已经介绍了 SQLite
常用的操作。如想自定义 sql
语句实现更丰富的数据库操作,使用 database.execSQL
方法。使用自定义 sql
查询数据库请使用 database.rawQuery
方法。
示例代码
为避免篇幅过长, 暂时介绍到这里。关于 SQLite
更多的操作语句和用法,请见后续文章。
觉得有用?那打赏一个呗。[去打赏]({{ site.url }}/donate/)
此处是广告:Flueky的技术小站
最后
以上就是谨慎嚓茶为你收集整理的Android 数据存储——SQLite的全部内容,希望文章能够帮你解决Android 数据存储——SQLite所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复