详解Android中的SQLite数据库存储

Posted CodingEnding

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详解Android中的SQLite数据库存储相关的知识,希望对你有一定的参考价值。

前言

android中存储数据的方式有很多种,其中使用SQLite数据库是存储结构化数据的最佳选择。幸运的是,Android中默认提供了对SQLite的支持,这就使得在Android中使用SQLite数据库变得格外方便。

支持的数据类型

SQLite是一款轻量级的数据库,其支持的数据类型也很简单,主要有以下几种:

  • text:字符类型
  • real:浮点类型
  • integer:整数类型
  • blob:二进制数据类型

创建数据库

SQLite数据库的使用始于SQLiteOpenHelper这个抽象类。我们需要自定义类去继承SQLiteOpenHelper,并重写它的构造方法。实际上,有两个构造方法可以重写,一般我们选择重写参数较少的那个方法。

此外,我们还需要实现onCreateonUpgrade这两个抽象方法。在onCreate方法中,我们对数据库进行创建。而在onUpgrade方法中,我们则对数据库进行升级、更新。基本代码如下:

 public class BookOpenHelper extends SQLiteOpenHelper{
    private static final String CREATE_BOOK="create table Book(" +
            "id integer primary key autoincrement," +
            "name text," +
            "price text," +
            "author text)";

    public BookOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

可以看到,在构造方法中,我们只是简单地调用了父类的构造方法。在构造方法的参数列表中,第一个参数是Context对象,第二个参数是数据库名称,第三个参数一般传入null,第四个参数是数据库的版本号。

此外,我们定义了一个字符串常量。很明显,这是一条建表语句,用于创建名为Book的表。在这个表中,我们定义了四个字段,分别是id、name、price、author。其中,idprimary keyautoincrement修饰,代表它是表的主键且自增。在onCreate方法中,我们调用了SQLiteDatabaseexecSQL方法,执行Book表的建表语句。

在实际使用中,我们需要先构建SQLiteOpenHelper的实例,然后调用它的getReadableDatabase或者getWritableDatabase方法,如果数据库还未创建,系统就会创建数据库并调用SQLiteOpenHelperonCreate方法。实例代码如下:

BookOpenHelper dbOpenHelper=new BookOpenHelper(
            MainActivity.this,"SQLiteDemo.db",null,1);
dbOpenHelper.getReadableDatabase();

getReadableDatabasegetWritableDatabase方法都会返回一个可以对数据库进行增删改查操作的SQLiteDatabase对象。它们的区别在于,当手机存储空间已满的时候,前者会返回null,而后者会抛出异常。

更新数据库

在实际开发中,常常需要对数据库进行修改,比如添加新的表或是增加表字段等。为了实现对数据库的更新,我们需要在构建SQLiteOpenHelper实例的时候传入一个比当前版本号更大的version参数,这将导致SQLiteOpenHelperonUpgrade方法被调用。在这个方法中,我们就可以进行数据库的更新操作了。以下代码演示在之前的数据库中添加名为Author的表:

public class BookOpenHelper extends SQLiteOpenHelper{
    .......
    //创建Author表
    private static final String CREATE_AUTHOR="create table Author(" +
            "id integer primary key autoincrement," +
            "name text," +
            "age integer)";

    public BookOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_AUTHOR);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        switch (oldVersion){
            case 1:
                db.execSQL(CREATE_AUTHOR);
            default:
                break;
        }
    }
}

我们定义了一个新的字符串常量CREATE_AUTHOR,用于创建Author表。同时,我们也在onCreate方法中执行了db.execSQL(CREATE_AUTHOR);。如果应用是首次安装,就可以正常地创建两张表。但是,如果应用之前已经被安装了。由于数据库已经存在,onCreate方法就不会被执行了。在这种情况下,我们将补救措施写在了onUpgrade方法中。通过对oldVersion、即旧版本号的判断,我们执行了后续的建表语句。只要在创建SQLiteOpenHelper的时候传入了比当前版本号更大的version参数,onUpgrade方法就会被调用,Author表也就会被创建了。

如果现在我们又想要为Author表添加一个新的字段country,用于记录作者的名字,又该如何操作呢?实际上做法和前面一样,只需要在onUpgrade方法中执行更新操作即可,具体代码如下:

public class BookOpenHelper extends SQLiteOpenHelper{
    .......
    //为Author表添加新的字段
    private static final String ALTER_AUTHOR="alter table Author add column country text";

    public BookOpenHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_AUTHOR);
        db.execSQL(ALTER_AUTHOR);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        switch (oldVersion){
            case 1:
                db.execSQL(CREATE_AUTHOR);
            case 2:
                db.execSQL(ALTER_AUTHOR);
            default:
                break;
        }
    }
}

onCreate方法中,我们依次执行了各个SQL操作,这样当应用首次安装时就可以正常地创建所有的表。而在onUpgrade方法中,我们通过判断当前数据库的版本号,来决定应该做哪些更新操作。若当前版本号为2,则只需要为Author表添加新的字段;若当前版本号为1,则需要先创建Author表,再为Author表添加新的字段。这也是为什么每个case都不加break的原因,这样才能保证应用跨版本升级的时候能够依次执行所有的更新操作。这一点尤其需要注意。

基本操作

解决了如何创建数据库和更新数据库的问题,接下来就可以正式接触SQLite中的各项数据操作了。一般可以简称为CRUD操作,即增删改查。首先,我们需要获得一个SQLiteDatabase对象,它封装了多种数据操作方法。可以通过SQLiteOpenHelper实例的getReadableDatabasegetWritableDatabase方法获得这个对象。

SQLiteDatabase database=dbOpenHelper.getWritableDatabase();
SQLiteDatabase database=dbOpenHelper.getReadableDatabase();

添加数据

SQLiteDatabase通过insert方法实现数据的添加,其方法原型如下:

public long insert(String table, String nullColumnHack, ContentValues values)

insert方法需要传入三个参数,第一个参数是表名,第二个参数一般传入null,第三个参数是一个ContentValues对象。ContentValues通过键值对的方式存储需要写入表中的数据。插入完成后,这个方法还会返回插入数据在表中的id号,如果插入失败,则返回-1。示例代码如下:

SQLiteDatabase database= dbOpenHelper.getWritableDatabase();
ContentValues values=new ContentValues();
values.put("name","Android");
values.put("author","Bill");
values.put("price","60");
database.insert("Book",null,values);
values.clear();
values.put("name","Java");
values.put("author","Tom");
values.put("price","50");
database.insert("Book",null,values);

可以看到,我们通过同一个ContentValues对象向Book表添加了两条数据。需要注意的是,在添加第二条数据之前应该先调用ContentValuesclear方法清除旧数据。

查询数据

SQLiteDatabase通过query方法实现数据的添加,常用版本的方法原型如下:

public Cursor query(String table, String[] columns, String selection,
        String[] selectionArgs, String groupBy, String having,
        String orderBy)

query方法需要传入7个参数。第一个参数是表名。第二个参数是需要查询的列名,如果传入null则代表查询所有列。第三个参数和第四个参数是查询条件,用于限制查询的行,相当于SQL中的where部分,如果都传入null表示查询所有行。第五参数是分组的列名,相当于SQL中的group by部分,如果传入null表示不对结果进行分组。第六个参数则相当于SQL中的having部分。第七个参数是排序的列名,传入null表示对查询结果进行默认排序。

这个方法在查询完成后会返回Cursor对象,这是一个游标,可以通过遍历的方式去取出查询到的数据。示例代码如下:

SQLiteDatabase database= dbOpenHelper.getReadableDatabase();
Cursor cursor=database.query("Book",null,null,null,null,null,null);
if(cursor.moveToFirst()){
    do {
        Log.d(TAG,cursor.getString(cursor.getColumnIndex("id")));
        Log.d(TAG,cursor.getString(cursor.getColumnIndex("name")));
        Log.d(TAG,cursor.getString(cursor.getColumnIndex("author")));
        Log.d(TAG,cursor.getString(cursor.getColumnIndex("price")));
    }
    while(cursor.moveToNext());
}
cursor.close();

我们仅为query方法传入了一个表名,其他参数均传入null,表示查询Book表中的所有数据。对于这个方法返回的Cursor对象,我们先调用moveToFirst方法移动到数据的第一行。随后,在一个do-while循环中判断cursormoveToNext方法是否为true,并通过getString方法取出表中的数据。getColumnIndex方法用于获取表中指定列名对应的索引号。这是一个最简单的查询操作,现在我们再尝试一次限制条件更多的查询:

SQLiteDatabase database= dbOpenHelper.getReadableDatabase();
Cursor cursor=database.query("Book",new String[]{"name","author"},
                    "author=?",new String[]{"Tom"},null,null,"name");
if(cursor.moveToFirst()){
    do {
        Log.d(TAG,cursor.getString(cursor.getColumnIndex("name")));
        Log.d(TAG,cursor.getString(cursor.getColumnIndex("author")));
    }
    while(cursor.moveToNext());
}
cursor.close();

以上代码的目的是查询Book表中authorTom的数据,并且只查询nameauthor这两列的数据,最后将返回结果根据name进行排序。需要注意的是,在完成查询后,我们还应该调用Cursorclose方法,释放资源。

更新数据

SQLiteDatabase通过query方法实现数据的添加,其方法原型如下:

public int update(String table, ContentValues values, String whereClause, String[] whereArgs) 

update方法需要传入4个参数。第一个参数是表名。第二个参数是ContentValues对象,存储更新后的数据。第三个参数和第四个参数是限制条件,都传入null就代表更新表中的所有数据。更新完成后,这个方法会返回受影响的行数。示例代码如下:

SQLiteDatabase database= dbOpenHelper.getWritableDatabase();
ContentValues values=new ContentValues();
values.put("name","Android Plus");
database.update("Book",values,"name=?",new String[]{"Android"});

以上代码查询Book表中所有nameAndroid的数据,并将name字段更新为Android Plus。需要注意的是,我们只需要在ContentValues中存储需要更新的数据,不需要存储其他不变的数据。

删除数据

SQLiteDatabase通过delete方法实现数据的删除,其方法原型如下:

public int delete(String table, String whereClause, String[] whereArgs)

delete方法需要传入三个参数。第一个参数是表名。第二个和第三个参数是限制条件,如果都传入null就表示删除表中所有的数据。示例代码如下:

SQLiteDatabase database=dbOpenHelper.getWritableDatabase();
database.delete("Book","name=?",new String[]{"Java"});

以上代码将会删除Book表中所有nameJava的数据。

使用SQL操作数据库

除了使用SQLiteDatabase封装好的insert、delete、update、query方法对数据库进行增删改查,我们还可以直接使用SQL语句对数据进行操作。当然,前提是SQL基础要足够好。SQLiteDatabase提供了execSQLrawQuery这两个方法用于直接执行SQL语句。不同之处在于,后者用于执行查询操作,前者则执行其他操作。这两个方法常用版本的原型如下:

public void execSQL(String sql, Object[] bindArgs)
public Cursor rawQuery(String sql, String[] selectionArgs)

示例代码如下:

//插入数据
database.execSQL("insert into Book(name,price,author) values(?,?,?)",new String[]{"book","110","Bill"});
//查询数据
Cursor cursor=database.rawQuery("select * from Book",null);

使用事务

在SQLiteDatabase中,Android还为开发者集成了事务功能。那么什么是事务呢?想象这样一个场景,由于应用的版本更新,我们需要删除一部分数据,然后插入新的数据。但是要求删除操作和插入操作必须一起成功,否则就一起失败。这样做可以避免在删除数据后,由于某些原因应用出错,导致插入操作没能完成,而影响用户的正常使用。使用事务,就可以顺利地完成这一要求。示例代码如下:

SQLiteDatabase database=dbOpenHelper.getWritableDatabase();
database.beginTransaction();
try{
    database.delete("Book","name=?",new String[]{"Android"});
    if(true){//手动抛出异常演示在执行事务的过程中操作失败
        throw new Exception("手动抛出异常");
    }
    ContentValues values=new ContentValues();
    values.put("name","Java");
    values.put("author","Jimmy");
    values.put("price","500");
    database.insert("Book",null,values);
    database.setTransactionSuccessful();
}catch(Exception e){
    Log.d(TAG,"执行事务过程中出现错误!");
    e.printStackTrace();
}finally {
    database.endTransaction();
}

可以看到,我们首先调用了SQLiteDatabasebeginTransaction方法开启事务。然后在try语句块中,我们先是删除了Book表中所有nameAndroid的数据,随后向Book中插入了一条新的数据。在try语句块的最后,我们调用了SQLiteDatabasesetTransactionSuccessful方法,表示事务成功执行。但是在删除数据后,我们手动抛出了一个异常,这将会导致后续的添加操作失败。如果是在普通情况下,最后的结果就是数据被删除了,但是新的数据却没有添加成功。而在这里使用了事务,出现异常后,也将会导致前面的删除操作失败。需要注意的是,我们还需要在finally语句块中调用endTransaction方法,表示事务结束。

总结一下,使用事务的关键就是下面这三句,需要使用事务的时候参照上述写法即可。

database.beginTransaction();
database.setTransactionSuccessful();
database.endTransaction();

再次强调:如果开启了事务,一定要记得调用setTransactionSuccessfulendTransaction方法,否则在事务中对数据库执行的操作都不会生效。

使用开源库操作数据库

除了通过本文讲解的方式操作SQLite数据库外,我们还可以借助开源库的力量。比如LitePal、GreenDAO、ActiveAndroid、realm-java、sqlbrite等。对于这些开源库的使用,可以参考它们的Github主页,也可以参考下列博客:

使用LitePal操作数据库(先挖坑,随后填)

项目demo下载地址

下面给出上述例子的demo下载地址:SQLiteDemo

以上是关于详解Android中的SQLite数据库存储的主要内容,如果未能解决你的问题,请参考以下文章

10分钟理解Android数据库的创建与使用(附详解和示例代码)

Android基础之十四数据存储 之 SQLite数据库详解

Android中的数据存储方式--重点讲SQLite

Android中的数据存储方式--重点讲SQLite

Android 逆向应用数据目录 ( files 数据目录 | lib 应用自带 so 动态库目录 | databases sqlite3 数据库目录 | cache 缓存目录 )(代码片

Android 逆向应用数据目录 ( files 数据目录 | lib 应用自带 so 动态库目录 | databases sqlite3 数据库目录 | cache 缓存目录 )(代码片