详解Android中的SQLite数据库存储
Posted CodingEnding
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了详解Android中的SQLite数据库存储相关的知识,希望对你有一定的参考价值。
前言
在android
中存储数据的方式有很多种,其中使用SQLite
数据库是存储结构化数据的最佳选择。幸运的是,Android
中默认提供了对SQLite
的支持,这就使得在Android
中使用SQLite
数据库变得格外方便。
支持的数据类型
SQLite是一款轻量级的数据库,其支持的数据类型也很简单,主要有以下几种:
- text:字符类型
- real:浮点类型
- integer:整数类型
- blob:二进制数据类型
创建数据库
SQLite
数据库的使用始于SQLiteOpenHelper
这个抽象类。我们需要自定义类去继承SQLiteOpenHelper
,并重写它的构造方法
。实际上,有两个构造方法可以重写,一般我们选择重写参数较少的那个方法。
此外,我们还需要实现onCreate
和onUpgrade
这两个抽象方法。在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
。其中,id
由primary key
和autoincrement
修饰,代表它是表的主键且自增。在onCreate
方法中,我们调用了SQLiteDatabase
的execSQL
方法,执行Book
表的建表语句。
在实际使用中,我们需要先构建SQLiteOpenHelper
的实例,然后调用它的getReadableDatabase
或者getWritableDatabase
方法,如果数据库还未创建,系统就会创建数据库并调用SQLiteOpenHelper
的onCreate
方法。实例代码如下:
BookOpenHelper dbOpenHelper=new BookOpenHelper(
MainActivity.this,"SQLiteDemo.db",null,1);
dbOpenHelper.getReadableDatabase();
getReadableDatabase
和getWritableDatabase
方法都会返回一个可以对数据库进行增删改查操作的SQLiteDatabase
对象。它们的区别在于,当手机存储空间已满的时候,前者会返回null,而后者会抛出异常。
更新数据库
在实际开发中,常常需要对数据库进行修改,比如添加新的表或是增加表字段等。为了实现对数据库的更新,我们需要在构建SQLiteOpenHelper
实例的时候传入一个比当前版本号更大的version
参数,这将导致SQLiteOpenHelper
的onUpgrade
方法被调用。在这个方法中,我们就可以进行数据库的更新操作了。以下代码演示在之前的数据库中添加名为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
,用于创建Autho
r表。同时,我们也在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
实例的getReadableDatabase
或getWritableDatabase
方法获得这个对象。
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
表添加了两条数据。需要注意的是,在添加第二条数据之前应该先调用ContentValues
的clear
方法清除旧数据。
查询数据
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
循环中判断cursor
的moveToNext
方法是否为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
表中author
为Tom
的数据,并且只查询name
和author
这两列的数据,最后将返回结果根据name
进行排序。需要注意的是,在完成查询后,我们还应该调用Cursor
的close
方法,释放资源。
更新数据
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
表中所有name
为Android
的数据,并将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
表中所有name
为Java
的数据。
使用SQL操作数据库
除了使用SQLiteDatabase
封装好的insert、delete、update、query
方法对数据库进行增删改查,我们还可以直接使用SQL
语句对数据进行操作。当然,前提是SQL
基础要足够好。SQLiteDatabase
提供了execSQL
和rawQuery
这两个方法用于直接执行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();
}
可以看到,我们首先调用了SQLiteDatabase
的beginTransaction
方法开启事务。然后在try
语句块中,我们先是删除了Book
表中所有name
为Android
的数据,随后向Book
中插入了一条新的数据。在try
语句块的最后,我们调用了SQLiteDatabase
的setTransactionSuccessful
方法,表示事务成功执行。但是在删除数据后,我们手动抛出了一个异常,这将会导致后续的添加操作失败。如果是在普通情况下,最后的结果就是数据被删除了,但是新的数据却没有添加成功。而在这里使用了事务,出现异常后,也将会导致前面的删除操作失败。需要注意的是,我们还需要在finally
语句块中调用endTransaction
方法,表示事务结束。
总结一下,使用事务的关键就是下面这三句,需要使用事务的时候参照上述写法即可。
database.beginTransaction();
database.setTransactionSuccessful();
database.endTransaction();
再次强调:如果开启了事务,一定要记得调用setTransactionSuccessful
和endTransaction
方法,否则在事务中对数据库执行的操作都不会生效。
使用开源库操作数据库
除了通过本文讲解的方式操作SQLite
数据库外,我们还可以借助开源库的力量。比如LitePal、GreenDAO、ActiveAndroid、realm-java、sqlbrite等。对于这些开源库的使用,可以参考它们的Github
主页,也可以参考下列博客:
使用LitePal操作数据库(先挖坑,随后填)
项目demo下载地址
下面给出上述例子的demo
下载地址:SQLiteDemo
以上是关于详解Android中的SQLite数据库存储的主要内容,如果未能解决你的问题,请参考以下文章
10分钟理解Android数据库的创建与使用(附详解和示例代码)
Android基础之十四数据存储 之 SQLite数据库详解
Android 逆向应用数据目录 ( files 数据目录 | lib 应用自带 so 动态库目录 | databases sqlite3 数据库目录 | cache 缓存目录 )(代码片
Android 逆向应用数据目录 ( files 数据目录 | lib 应用自带 so 动态库目录 | databases sqlite3 数据库目录 | cache 缓存目录 )(代码片