Android数据库帮助类SQLiteOpenHelper

QuibblerQuibbler 2020-10-11 约 40 分钟 1613 次阅读

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数据库操作)详细解析

        使用SQLiteOpenHelper的正确姿势

        

相关推荐

精选
ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager
Android

ViewPager和PagerAdapter、FragmentPagerAdapter、FragmentStatePager

ViewPager1、ViewPagerandroidx.viewpager.widget.ViewPager,Android中使用非常广泛的控件,可以说是APP必备:首次打开引导页、页面Banner广告等。常用方法:setAdapter() 设置适配器setOffscreenPageLimit() 设置缓存的页面个数,默认是 1setCurrentItem() 跳转到特定的页面setOnPage

1.1k
获取Android内置WebView内核版本
Android

获取Android内置WebView内核版本

获取Android内置WebView内核版本竟然能遇到这样奇葩的事情,网页用的技术过于新颖,以至于只支持高版本Chromium内核的,低版本安卓系统中内置的内核版本较低,无法加载前端页面。1、设置查看在系统设置里 > 应用 > 应用管理 > 显示系统应用,查看WebView组件:2、页面查看通过WebView发起的网络请求,都会带上浏览器的UA,通常页面都可以通过UA判断浏览器的内核版本。这里有两

1.1w
Android 14适配总结
Android

Android 14适配总结

Android 14适配总结毕业工作至今已经适配了三个Android大版本,从Android 11到Android 12、再到Android 13。2023年,Google即将推出的Android 14,上半年已经开始第一批适配。现在,第四个Android版本已经适配完,总结记录一下。1、Android 14计划Google一般会在2月份对外发布预告,同时放出开发者预览版。“拉通”各大平台、厂商以

1.1w