Android数据库帮助类SQLiteOpenHelper
SQLiteOpenHelper是Android提供的帮助程序,用于数据库创建和版本管理。
1、SQLiteOpenHelper
SQLiteOpenHelper是抽象类,在Android Q中SQLiteOpenHelper实现了AutoCloseable接口。
1.1、SQLiteOpenHelper介绍
该帮助类简化开发人员连接建议数据库的繁琐操作,通过方法即可获取到数据库对象,进行数据库增删改查操作。开发者只需要继承SQLiteOpenHelper抽象类,实现其中的onCreate()和onUpgrade()抽象方法。
1.2、构造函数
再提供至少一个构造函数即可完成自定义的数据库帮助类。目前SQLiteOpenHelper有三个构造函数:
/**
*
* @param context 上下文Context,最好传入Application的Context
* @param name 数据库名称
* @param factory Cursor工厂,一般传null
* @param version 数据库版本,整形单调递增
*/
public MySQLiteDataBaseOpenHelper(Context context,String name,SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
public MySQLiteDataBaseOpenHelper(Context context,String name,SQLiteDatabase.CursorFactory factory, int version,DatabaseErrorHandler errorHandler) {
super(context, name, factory, version, errorHandler);
}
@RequiresApi(api = Build.VERSION_CODES.P)
public MySQLiteDataBaseOpenHelper(Context context,String name, int version,SQLiteDatabase.OpenParams openParams) {
super(context, name, version, openParams);
}一般只需要调用super父类的构造函数,自定义类的构造函数不需要那么多参数,只需要一个Context即可。比如:
public MySQLiteDataBaseOpenHelper(Context context) {
super(context, "people.db", null, 11);
}甚至最简单的调用方式,都不需要任何参数,直接内部全部封装好了,Context也使用应用全局的Context。获取全局Context技巧见如何获取全局Context?
public MySQLiteDataBaseOpenHelper() {
super(Application.getContext(), "people.db", null, 11);
}注意,创建SQLiteOpenHelper类实例,并没有真正创建数据库,只是保存了一些参数,上面三个构造函数最后都是调用这个内部构造函数,当调用getReadableDatabase()和getWritableDatabase()获取数据库时才会创建SQLiteDatabase数据库。
private SQLiteOpenHelper(Context context,String name, int version,
int minimumSupportedVersion,SQLiteDatabase.OpenParams.Builder openParamsBuilder) {
Preconditions.checkNotNull(openParamsBuilder);
if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);
mContext = context;
mName = name;
mNewVersion = version;
mMinimumSupportedVersion = Math.max(0, minimumSupportedVersion);
setOpenParamsBuilder(openParamsBuilder);
}1.3、单例
一般自定义的SQLiteOpenHelper写成单例,防止创建了多个实例对象,引起内存泄漏。单例模式详见单例的五种方式。
public class MySQLiteDataBaseOpenHelper extends SQLiteOpenHelper {
private static SQLiteOpenHelper sqLiteOpenHelper;
public static SQLiteOpenHelper getInstance(){
if (null == sqLiteOpenHelper) {
synchronized (MySQLiteDataBaseOpenHelper.class) {
if (null == sqLiteOpenHelper) {
sqLiteOpenHelper = new MySQLiteDataBaseOpenHelper();
}
}
}
return sqLiteOpenHelper;
}
...
}1.4、数据库连接配置
SQLiteDatabase.OpenParams用于包装数据库连接参数,用于打开SQLiteDatabase。封装了一系列的参数配置变量:
public static final class OpenParams {
private final int mOpenFlags;
private final CursorFactory mCursorFactory;
private final DatabaseErrorHandler mErrorHandler;
private final int mLookasideSlotSize;
private final int mLookasideSlotCount;
private final long mIdleConnectionTimeout;
private final String mJournalMode;
private final String mSyncMode;
...
}2、SQLiteOpenHelper方法
SQLiteOpenHelper中的方法并不多,很简单明了。
2.1、创建和打开
①onCreate(SQLiteDatabase db):需要在自定义SQLiteOpenHelper中实现该方法用来创建数据表,传入的数据库参数就是构造函数中创建的数据库。
//sql to create news table
private static final String CREATE_TABLE = "create table if not exists " + DB_TABLE
+ "(uniquekey text ,type text, title text, date text, category text, "
+ "author_name text, url text, thumbnail_pic_s text, thumbnail_pic_s02 text, thumbnail_pic_s03 text)";
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE);
}②onOpen(SQLiteDatabase db):SQLiteOpenHelper类中为空方法,打开数据库是调用,需要检查数据库的话可以在该方法中校验。
③close():关闭SQLiteOpenHelper内部持有的SQLiteDatabase数据库mDatabase成员变量
/**
* Close any open database object.
*/
public synchronized void close() {
if (mIsInitializing) throw new IllegalStateException("Closed during initialization");
if (mDatabase != null && mDatabase.isOpen()) {
mDatabase.close();
mDatabase = null;
}
}2.2、升降级
①onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion):
②onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion):数据库降级的时候用,新版本数据库比当前版本低。此方法不是抽象的,默认实现将拒绝降级并抛出SQLiteException!
/**
*
* 此方法在事务内执行。 如果引发异常,则所有更改将自动回滚。
*
* @param db The database.
* @param oldVersion The old database version.
* @param newVersion The new database version.
*/
public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
throw new SQLiteException("Can't downgrade database from version " +
oldVersion + " to " + newVersion);
}2.3、获取数据库
①SQLiteDatabase getReadableDatabase():获取只读数据库,可以用这个方法获取数据库对象进行查询
public SQLiteDatabase getReadableDatabase() {
synchronized (this) {
return getDatabaseLocked(false);
}
}②SQLiteDatabase getWritableDatabase():获取可读写的数据库对象
public SQLiteDatabase getWritableDatabase() {
synchronized (this) {
return getDatabaseLocked(true);
}
}上面两个获取数据库的方法都是调用内部的getDatabaseLocked(boolean writable),创建数据库,这里不仔细颇析源码了,内部会复用已经创建的SQLiteDatabase对象。
注意:这两个方法是耗时操作,数据库操作都必须放到子线程中进行。尤其注意不能在ContentProvider的onCreate()方法中调用这些方法,因为是在应用程序的主线程中。当然ContentProvider的其它增删改查方法可以,因为那些是运行在Binder线程池中。
2.4、其它方法
①onConfigure(SQLiteDatabase db):配置数据库连接时调用,该方法虽然传入数据库参数,但是仅允许设置数据库配置,不应该操作数据库。调用配置数据库连接的参数的方法,如SQLiteDatabase#enableWriteAheadLogging SQLiteDatabase#setForeignKeyConstraintsEnabled,SQLiteDatabase#setLocale, SQLiteDatabase#setMaximumSize
②String getDatabaseName():获取数据库名,也就是构造函数中调用super所传入的数据库名。
③setOpenParams(SQLiteDatabase.OpenParams openParams)
④setWriteAheadLoggingEnabled(boolean enabled):启用或禁用对数据库的预写日志记录的使用。预写日志记录不能用于只读数据库,因此如果以只读方式打开数据库,则忽略此标志的值。
3、SQLiteDatabase数据库操作
创建了SQLiteOpenHelper帮助类,可以方便的获取SQLiteDatabase数据库实例进行数据库操作,接下来看看数据库的常用操作。
3.1、查询
查询有很多个方法,主要是以下几种方法及其对应的重载函数。查询需要的参数很多至于具体参数可以查看API文档,以前不会看API文档,觉得像天书,现在觉得API文档才是最正确的第一手资料。
①Cursor query(...):查询返回包含结果的Cursor集合
Cursor query(boolean distinct, String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit)
Cursor query(boolean distinct, String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit, CancellationSignal cancellationSignal)
Cursor query(String table, String[] columns, String selection,
String[] selectionArgs, String groupBy, String having,
String orderBy)
Cursor query(String table, String[] columns, String selection,
String[] selectionArgs, String groupBy, String having,
String orderBy, String limit)②使用指定的Cursor工厂查询,或者为默认为null
Cursor queryWithFactory(CursorFactory cursorFactory,
boolean distinct, String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit)
Cursor queryWithFactory(CursorFactory cursorFactory,
boolean distinct, String table, String[] columns,
String selection, String[] selectionArgs, String groupBy,
String having, String orderBy, String limit, CancellationSignal cancellationSignal)③此外还有一些raw查询,直接使用SQL语句进行查询
Cursor rawQuery(String sql, String[] selectionArgs)
Cursor rawQuery(String sql, String[] selectionArgs,CancellationSignal cancellationSignal)
Cursor rawQueryWithFactory(
CursorFactory cursorFactory, String sql, String[] selectionArgs,
String editTable)
Cursor rawQueryWithFactory(
CursorFactory cursorFactory, String sql, String[] selectionArgs,
String editTable, CancellationSignal cancellationSignal)3.2、插入
插入数据库,需要借助ContentValues对象携带插入行的数据。
/**
* Convenience method for inserting a row into the database.
*
* @param table 要插入数据的表名
* @param nullColumnHack 可选; 可为null。 SQL不允许在不命名至少一个列名的情况下插入完全空的行。
* 如果提供的values为空,则没有已知的列名,也不能插入空行。
* 如果未设置为null,则nullColumnHack参数提供可为空的名称。
* @param values 该映射包含该行的初始列值。 键是列名称,值是列值
* @return 新插入的行的行ID;如果发生错误,则返回-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;
}
}内部进行了SQL异常捕获,此外还有另一个会抛出异常的insert方法insertOrThrow(...)会抛出插入出现的异常
public long insertOrThrow(String table, String nullColumnHack, ContentValues values)
throws SQLException {
return insertWithOnConflict(table, nullColumnHack, values, CONFLICT_NONE);
}插入方法都是调用这个insertWithOnConflict(...)最终执行插入的方法,第四个参数是冲突策略选项。
public long insertWithOnConflict(String table, String nullColumnHack,
ContentValues initialValues, int conflictAlgorithm) {
acquireReference();
try {
StringBuilder sql = new StringBuilder();
sql.append("INSERT");
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;
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 {
sql.append(nullColumnHack + ") VALUES (NULL");
}
sql.append(')');
SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs);
try {
return statement.executeInsert();
} finally {
statement.close();
}
} finally {
releaseReference();
}
}3.3、删除
删除数据库操作。
/**
* Convenience method for deleting rows in the database.
*
* @param table 要删除数据的表名
* @param whereClause 删除时要应用的可选WHERE子句。传递null将删除所有行。
* @param whereArgs 可以在where子句中包含?通配符,它将替换为whereArgs中的值。这些值将绑定为字符串。
* @return 如果传入whereClause则返回受影响的行数,否则为0。 要删除所有行并获得计数,请将"1"作为whereClause。
*/
public int delete(String table, String whereClause, String[] whereArgs)3.4、更新
更新数据操作。
/**
* Convenience method for updating rows in the database.
*
* @param table 要更新数据的表名
* @param values 从列名到新列值的映射。 null是将数据表中的数据转换为NULL的有效值。
* @param whereClause 在更新时适用的可选WHERE子句。传递null将更新所有行。
* @param whereArgs 您可以在where子句中包含?,它将替换为whereArgs中的值。 这些值将绑定为字符串。
* @return 返回更新的行数
*/
public int update(String table, ContentValues values, String whereClause, String[] whereArgs)3.5、数据库操作冲突
数据库操作发生冲突是有如下6种策略可选:
/**
* 当未指定冲突操作时,使用该选项。
*/
public static final int CONFLICT_NONE = 0;
/**
* 冲突回滚
*/
public static final int CONFLICT_ROLLBACK = 1;
/**
* 冲突是不会回滚,同一事物中已进行的修改保留。默认选项
*/
public static final int CONFLICT_ABORT = 2;
/**
* 发生约束冲突时,该命令将中止,返回码为SQLITE_CONSTRAINT。
* 但是,命令在遇到约束违例之前对数据库所做的任何更改都将保留,并且不会撤消。
*/
public static final int CONFLICT_FAIL = 3;
/**
* 发生约束违例时,不会插入或更改包含约束违例的一行。
* 但是该命令继续正常执行。 包含约束违例的行之前和之后的其他行将继续正常插入或更新。 没有错误返回。
*/
public static final int CONFLICT_IGNORE = 4;
/**
* 当发生UNIQUE约束违规时,会在插入或更新当前行之前删除导致约束违规的现有行。
* 因此,插入或更新总是发生。 该命令继续正常执行。 没有错误返回。
* 如果发生NOT NULL约束违例,则用该列的默认值替换NULL值。
* 如果该列没有默认值,则使用ABORT算法。
* 如果发生CHECK约束违例,则使用IGNORE算法。
* 当此冲突解决策略为了满足约束条件删除行时,不会在这些行上调用删除触发器。 在将来的版本中,此行为可能会更改。
*/
public static final int CONFLICT_REPLACE = 5;3.6、事务
①开启数据库事务
//以独占模式开始事务
beginTransaction()
//以立即模式开始事务,事务可以嵌套。
beginTransactionNonExclusive()
//以独占模式开始事务。当事务开始,提交或回滚时,通知该侦听器
beginTransactionWithListener()
//以立即模式开始事务。当事务开始,提交或回滚时,通知该侦听器
beginTransactionWithListenerNonExclusive()②endTransaction():结束事务
③setTransactionSuccessful():将当前事务标记为成功。在调用此方法和调用endTransaction之间,不要再做任何数据库工作。
//一般如下操作,开启和结束事务
db.beginTransaction();
try {
...
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}3.7、其它操作
①execSQL(String sql):执行SQL语句
execSQL(String sql)
execSQL(String sql, Object[] bindArgs)②setVersion(int version):设置数据库版本
③enableWriteAheadLogging():启用预写日志,运行同一个数据库被多个线程查询
④disableWriteAheadLogging():禁用上面的功能
4、游标Cursor
游标Cursor主要在数据库查询中用到,返回的Cursor类型结果可以获取数据,挨个遍历获取。Cursor是接口,数据库中的SQLiteCursor类实现该接口。注意最后遍历完成需要调用close()关闭对象,以免内存泄漏。方法不多,主要分为以下几类,了解一下:
4.1、移动游标
①move(int offset):从当前位置向前或向后移动光标一个offset相对量。
②moveToPosition(int position):将光标移到指定位置
③moveToFirst():将光标移到第一行
④moveToLast():将光标移到最后一行
⑤moveToNext():将光标移到下一行
⑥moveToPrevious():将光标移到上一行
4.2、获取数据
①getColumnIndex(String columnName):获取数据列索引位置
②getColumnIndexOrThrow(String columnName)
③getColumnName(int columnIndex):获取数据列名
④String[] getColumnNames():返回一个字符串数组,其中保存结果集中所有列的名称(按在结果中列出的顺序)
⑤getColumnCount():查询到的数据数量
⑥getBlob(int columnIndex)
⑦getString(int columnIndex)
⑧getShort(int columnIndex)
⑨getInt(int columnIndex)
⑩getLong(int columnIndex)
⑪getFloat(int columnIndex)
⑫getDouble(int columnIndex)
⑬getType(int columnIndex)
4.3、判断
①isFirst():返回光标是否指向第一行
②isLast():返回光标是否指向最后一行
③isBeforeFirst():返回光标是否指向第一行之前的位置。即刚开始遍历
④isAfterLast():返回光标是否指向最后一行之后的位置。即遍历完成
⑤isNull(int columnIndex):如果列columnIndex中的值为空,则返回true
⑥isClosed():关闭游标,释放其所有资源并使其完全无效。
4.4、数据库监控
竟然还能通过游标监控数据内容的变化。
registerContentObserver(ContentObserver observer)
unregisterContentObserver(ContentObserver observer)
registerDataSetObserver(DataSetObserver observer)
unregisterDataSetObserver(DataSetObserver observer)
setNotificationUri(ContentResolver cr, Uri uri)
setNotificationUris(ContentResolver cr,List<Uri> uris)
Uri getNotificationUri()
List<Uri> getNotificationUris()5、注意点
①数据库操作都是耗时操作,全部在子线程中执行
②注意不要在onCreate()、onOpen()等回调方法中获取数据库,否则可能会出现死循环。从源码就可以看见,getReadableDatabase()和getWriteableDatabase()方法创建数据库的时候使用getDatabaseLocked(boolean writable)方法会回调上面的两个回调。如果在回调方法中再调用getReadableDatabase()和getWriteableDatabase()会陷入循环。
private SQLiteDatabase getDatabaseLocked(boolean writable) {
...
onConfigure(db);
final int version = db.getVersion();
if (version != mNewVersion) {
...
onCreate(db);
}
onOpen(db);
...
} finally {
mIsInitializing = false;
if (db != null && db != mDatabase) {
db.close();
}
}
}③应用数据库不要降级!技术上可以降级,重写onDowngrade(...)即可,默认实现是抛出异常。
相关资料:
Docs > Reference > SQLiteOpenHelper">Android Developers > Reference > SQLiteOpenHelper
Reference > Cursor">Android Developers > Reference > Cursor
Android:SQLiteOpenHelper类(SQLlite数据库操作)详细解析

