Android存储-SQLite数据库存储数据(三)

前言

SQLite数据库一款轻量级关系数据库,轻量型、跨平台、多语言接口、安全、支持事务处理,而且运算速度快,占用资源少。Android数据持久化存储的内部存储主要存在在内部存储设备上,恰恰Android内部设备存储空间往往是有限的,如果不有效的管理内部存储空间,很有可能导致手机直接崩溃无法使用。而且SQLite轻量级及相关有点正好符合了Android本身的特性。其实Android另外两个存储方式:文件存储和SharePreferences存储中的SharePreferences存储就属于轻量型存储机智。而对于文件存储要去也是尽量存储到外部存储设备中。因此SQLite本身的优点特别适合在移动设备上使用
本博客主要围绕SQLite展开讲述,其实已经非常便捷和成熟的SQLite封装的工具,有兴趣的可以可以网上搜索一下几个框架去了解:


		开源库 ---OrmLite
		开源库 ---greenDAO
		开源库 ---SugarORM
		开源库 ---ActiveAndroid
		开源库 ---LitePal
		开源库 ---realm
		

重述SQLite数据优点

在这里重复简述一下SQLite的特点:


	1、SQLite作为一个轻型的关系型数据库;
	
	2、运算速度非常快,占用资源很少;
	
	3、SQLite不仅支持标准的SQL语法,还遵守了数据库的 ACID 事务,上手快;
	
	4、提供了零配置运行模式;
	
	5、无需服务进程,轻量、跨平台、多语言接口及安全性等;
	

SQLite数据库的使用

一、创建数据库方式

创建数据主要有四种方式:

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//databases/下,则这里只需要提供数据库名称);
第二个参数: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对象

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存储、文件存储、数据库存储)之数据库存储就介绍到这了。

你可能感兴趣的:(Android,存储)