本博文是《第一行代码 Android》的读书笔记摘录。
上一篇 Android之数据存储详解(一)讲解了文件存储数据和使用SharedPreferences存储数据,接下来讲解 SQLite数据库存储数据。
一、SQLite数据库简介
SQLite是一款轻型的数据库,是遵守ACID的关联式数据库管理系统,它的设计目标是嵌入 式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了。它能够支持 Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如Tcl、PHP、Java、C++、.Net等,还有ODBC接口,同样比起 Mysql、PostgreSQL这两款开源世界著名的数据库管理系统来讲,它的处理速度比他们都快。
二、SQLite数据类型
一般数据采用的固定的静态数据类型,而SQLite采用的是动态数据类型,会根据存入值自动判断。SQLite具有以下常用的数据类型:
(1)NULL: 这个值为空值
(2)VARCHAR(n):长度不固定且其最大长度为 n 的字串,n不能超过 4000。
(3)CHAR(n):长度固定为n的字串,n不能超过 254。
(4)INTEGER: 值被标识为整数,依据值的大小可以依次被存储为1,2,3,4,5,6,7,8.
(5)REAL: 所有值都是浮动的数值,被存储为8字节的IEEE浮动标记序号.
(6)TEXT: 值为文本字符串,使用数据库编码存储(TUTF-8, UTF-16BE or UTF-16-LE).
(7)BLOB: 值是BLOB数据块,以输入的数据格式进行存储。如何输入就如何存储,不改变格式。
(8)DATA :包含了 年份、月份、日期。
(9)TIME: 包含了 小时、分钟、秒。
三、Android之SQLiteOpenHelper介绍
Android 为了让我们能够更加方便地管理数据库,专门提供了一个SQLiteOpenHelper 帮助类,借助这个类就可以非常简单地对数据库进行创建和管理。
public abstract class SQLiteOpenHelper;
SQLiteOpenHelper 是一个抽象类,这意味着如果我们想要使用它的话,就需要创建一个自己的帮助类去继承它。SQLiteOpenHelper 中有两个抽象方法,分别是onCreate()和onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。
@Override
public void onCreate(SQLiteDatabase db) {
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
SQLiteOpenHelper 中还有两个非常重要的实例方法, getReadableDatabase() 和getWritableDatabase()。这两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满)getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase()方法则将出现异常。
public SQLiteDatabase getReadableDatabase();
public SQLiteDatabase getWritableDatabase()
SQLiteOpenHelper 中有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。
public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version);
这个构造方法中接收四个参数:
第一个参数是Context,这个没什么好说的,必须要有它才能对数据库进行操作。
第二个参数是数据库名,创建数据库时使用的就是这里指定的名称。
第三个参数允许我们在查询数据的时候返回一个自定义的Cursor,一般都是传入null。
第四个参数表示当前数据库的版本号, 可用于对数据库进行升级操作。
构建出SQLiteOpenHelper 的实例之后,再调用它的getReadableDatabase()或getWritableDatabase()方法就能够创建数据库了,数据库文件会存放在/data/data/
目录下。此时,重写的onCreate()方法也会得到执行,所以通常会在这里去处理一些创建表的逻辑。
下面,将详细讲解SQLiteOpenHelper的使用操作。
四、创建数据库
我们希望创建一个名为BookStore.db 的数据库,然后在这个数据库中新建一张Book表,表中有id(主键)、作者、价格、页数和书名等列。创建数据库表当然还是需要用建表语句的,Book 表的建表语句如下所示:
create table Book (
id integer primary key autoincrement,
author text,
price real,
pages integer,
name text)
然后需要在代码中去执行这条SQL 语句,才能完成创建表的操作。新建MyDatabaseHelper类继承自SQLiteOpenHelper,代码如下所示:
package com.example.databasetest;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.widget.Toast;
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text)";
private Context mContext;
public MyDatabaseHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
可以看到,我们把建表语句定义成了一个字符串常量,然后在onCreate()方法中又调用了SQLiteDatabase 的execSQL()方法去执行这条建表语句,并弹出一个Toast 提示创建成功,这样就可以保证在数据库创建完成的同时还能成功创建Book 表。
在MainActivity 中中点击Button,创建数据库:
public class MainActivity extends Activity {
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);
Button createDatabase = (Button) findViewById(R.id.create_database);
createDatabase.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dbHelper.getWritableDatabase();
}
});
}
}
这里我们在onCreate()方法中构建了一个MyDatabaseHelper 对象,并且通过构造函数的参数将数据库名指定为BookStore.db,版本号指定为1,然后在Create database 按钮的点击事件里调用了getWritableDatabase()方法。
这样当第一次点击Create database 按钮时,就会检测到当前程序中并没有BookStore.db 这个数据库,于是会创建该数据库并调用MyDatabaseHelper中的onCreate()方法,这样Book 表也就得到了创建,然后会弹出一个Toast 提示创建成功。再次点击Create database 按钮时,会发现此时已经存在BookStore.db 数据库了,因此不会再创建一次。
如果你已经安装了adb工具,可以使用cd 命令进行到/data/data/com.example.databasetest/databases/目录下,并使用ls命令查看到该目录里的文件:
这个目录下出现了两个数据库文件,一个正是我们创建的BookStore.db,而另一个
BookStore.db-journal 则是为了让数据库能够支持事务而产生的临时日志文件,通常情况下这个文件的大小都是0 字节。
接下来我们就要借助sqlite 命令来打开数据库了,只需要键入sqlite3,后面加上数据库名即可:
这时就已经打开了BookStore.db 数据库,现在就可以对这个数据库中的表进行管理了。首先来看一下目前数据库中有哪些表,键入.table 命令:
可以看到,此时数据库中有两张表,android_metadata 表是每个数据库中都会自动生成的,不用管它,而另外一张Book 表就是我们在MyDatabaseHelper 中创建的了。这里还可以通过.schema 命令来查看它们的建表语句:
由此证明,BookStore.db 数据库和Book 表确实已经是创建成功了。之后键入.exit 或.quit命令可以退出数据库的编辑,再键入exit 命令就可以退出设备控制台了。
五、升级数据库
我们发现MyDatabaseHelper 中还有一个空方法onUpgrade():
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
这个方法是用于对数据库进行升级的,它在整个数据库的管理工作当中起着非常重要的作用。
目前DatabaseTest 项目中已经有一张Book 表用于存放书的各种详细数据,如果我们想再添加一张Category 表用于记录书籍的分类该怎么做呢?
比如Category 表中有id(主键)、分类名和分类代码这几个列,那么建表语句就可以写成:
create table Category (
id integer primary key autoincrement,
category_name text,
category_code integer)
接下来我们将这条建表语句添加到MyDatabaseHelper 中,代码如下所示:
package com.example.databasetest;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.widget.Toast;
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text)";
public static final String CREATE_CATEGORY = "create table Category ("
+ "id integer primary key autoincrement, "
+ "category_name text, "
+ "category_code integer)";
private Context mContext;
public MyDatabaseHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
Toast.makeText(mContext, "Create succeeded", Toast.LENGTH_SHORT).show();
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
现在我们重新运行一下程序,并点击Create database 按钮,竟然没有弹出创建成功的提示。当然,你也可以通过adb 工具到数据库中再去检查一下,这样你会更加地确认,Category 表没有创建成功!因为此时BookStore.db 数据库已经存在了,之后不管我们怎样点击Create database 按钮,MyDatabaseHelper 中的onCreate()方法都不会再次执行,因此新添加的表也就无法得到创建了。
解决这个问题的办法也相当简单,只需要先将程序卸载掉,然后重新运行,这时BookStore.db 数据库已经不存在了,如果再点击Create database 按钮,MyDatabaseHelper 中的onCreate()方法就会执行,这时Category 表就可以创建成功了。
不过通过卸载程序的方式来新增一张表毫无疑问是很极端的做法,其实我们只需要巧妙地运用SQLiteOpenHelper 的升级功能就可以很轻松地解决这个问题。修改MyDatabaseHelper中的代码,如下所示:
public class MyDatabaseHelper extends SQLiteOpenHelper {
……
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("drop table if exists Book");
db.execSQL("drop table if exists Category");
onCreate(db);
}
}
可以看到,我们在onUpgrade()方法中执行了两条DROP 语句,如果发现数据库中已经存在Book 表或Category 表了,就将这两张表删除掉,然后再调用onCreate()方法去重新创建。
接下来的问题就是如何让onUpgrade()方法能够执行了,还记得SQLiteOpenHelper 的构造方法里接收的第四个参数吗?它表示当前数据库的版本号,之前我们传入的是1,现在只要传入一个比1 大的数,就可以让onUpgrade()方法得到执行了。修改MainActivity 中的代码,如下所示:
public class MainActivity extends Activity {
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);
Button createDatabase = (Button) findViewById(R.id.create_database);
createDatabase.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dbHelper.getWritableDatabase();
}
});
}
}
这里将数据库版本号指定为2,表示我们对数据库进行升级了。现在重新运行程序,并点击Create database 按钮,这时就会再次弹出创建成功的提示。为了验证一下Category 表是不是已经创建成功了,我们在adb shell 中打开BookStore.db 数据库,然后键入.table 命令,结果如图所示:
接着键入.schema 命令查看一下建表语句,结果如图 所示:
由此可以看出,Category 表已经创建成功了,同时也说明我们的升级功能的确起到了作用。
升级数据库的最佳写法
以上的升级数据库的方式是非常粗暴的,为了保证数据库中的表是最新的,我们只是简单地在onUpgrade()方法中删除掉了当前所有的表,然后强制重新执行了一遍onCreate()方法。这种方式在产品的开发阶段确实可以用,但是当产品真正上线了之后就绝对不行了。由于添加新功能的原因,使得数据库也需要一起升级,然后用户更新了这个版本之后发现以前程序中存储的本地数据全部丢失了!
难道说在产品发布出去之后还不能升级数据库了?当然不是,其实只需要进行一些合理的控制,就可以保证在升级数据库的时候数据并不会丢失了。
下面我们就来学习一下如何实现这样的功能,我们知道,每一个数据库版本都会对应一个版本号,当指定的数据库版本号大于当前数据库版本号的时候,就会进入到onUpgrade()方法中去执行更新操作。这里需要为每一个版本号赋予它各自改变的内容,然后在onUpgrade()方法中对当前数据库的版本号进行判断,再执行相应的改变就可以了。
接着就让我们来模拟一个数据库升级的案例,还是由MyDatabaseHelper 类来对数据库进行管理。第一版的程序要求非常简单,只需要创建一张Book 表,MyDatabaseHelper 中的代码如下所示:
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text)";
public MyDatabaseHelper(Context context, String name, 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) {
}
}
不过,几星期之后又有了新需求,这次需要向数据库中再添加一张Category 表。于是,修改MyDatabaseHelper 中的代码,如下所示:
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text)";
public static final String CREATE_CATEGORY = "create table Category ("
+ "id integer primary key autoincrement, "
+ "category_name text, "
+ "category_code integer)";
public MyDatabaseHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (oldVersion) {
case 1:
db.execSQL(CREATE_CATEGORY);
default:
}
}
}
可以看到,在onCreate()方法里我们新增了一条建表语句,然后又在onUpgrade()方法中添加了一个switch 判断,如果用户当前数据库的版本号是1,就只会创建一张Category 表。这样当用户是直接安装的第二版的程序时,就会将两张表一起创建。而当用户是使用第二版的程序覆盖安装第一版的程序时,就会进入到升级数据库的操作中,此时由于Book 表已经存在了,因此只需要创建一张Category 表即可。
但是没过多久,新的需求又来了,这次要给Book 表和Category 表之间建立关联,需要在Book 表中添加一个category_id 的字段。再次修改MyDatabaseHelper 中的代码,如下所示:
public class MyDatabaseHelper extends SQLiteOpenHelper {
public static final String CREATE_BOOK = "create table Book ("
+ "id integer primary key autoincrement, "
+ "author text, "
+ "price real, "
+ "pages integer, "
+ "name text, "
+ "category_id integer)";
public static final String CREATE_CATEGORY = "create table Category ("
+ "id integer primary key autoincrement, "
+ "category_name text, "
+ "category_code integer)";
public MyDatabaseHelper(Context context, String name,
CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (oldVersion) {
case 1:
db.execSQL(CREATE_CATEGORY);
case 2:
db.execSQL("alter table Book add column category_id integer");
default:
}
}
}
可以看到,首先我们在Book 表的建表语句中添加了一个category_id 列,这样当用户直接安装第三版的程序时,这个新增的列就已经自动添加成功了。然而,如果用户之前已经安装了某一版本的程序,现在需要覆盖安装,就会进入到升级数据库的操作中。在onUpgrade()方法里,我们添加了一个新的case,如果当前数据库的版本号是2,就会执行alter 命令来为Book 表新增一个category_id 列。
这里注意一个非常重要的细节,switch 中每一个case 的最后都是没有使用break 的,为什么要这么做呢?
这是为了保证在跨版本升级的时候,每一次的数据库修改都能被全部执行到。比如用户当前是从第二版程序升级到第三版程序的,那么case 2 中的逻辑就会执行。而如果用户是直接从第一版程序升级到第三版程序的,那么case 1 和case 2 中的逻辑都会执行。使用这种方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是最新的,而且表中的数据也完全不会丢失了。
六、添加数据
现在已经掌握了创建和升级数据库的方法,接下来就该学习一下如何对表中的数据进行操作了。其实我们可以对数据进行的操作也就无非四种,即CRUD。其中C 代表添加(Create),R 代表查询(Retrieve),U 代表更新(Update),D 代表删除(Delete)。每一种操作又各自对应了一种SQL 命令,如果你比较熟悉SQL 语言的话,一定会知道添加数据时使用insert,查询数据时使用select,更新数据时使用update,删除数据时使用delete。
但是开发者的水平总会是参差不齐的,未必每一个人都能非常熟悉地使用SQL 语言,因此Android也是提供了一系列的辅助性方法,使得在Android 中即使不去编写SQL 语句,也能轻松完成所有的CRUD 操作。
SQLiteDatabase 中提供了一个insert()方法,这个方法就是专门用于添加数据的:
public long insert(String table, String nullColumnHack, ContentValues values)
它接收三个参数:
第一个参数是表名,我们希望向哪张表里添加数据,这里就传入该表的名字。
第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般我们用不到这个功能,直接传入null 即可。
第三个参数是一个ContentValues 对象,它提供了一系列的put()方法重载,用于向ContentValues 中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。
接下来还是让我们通过例子的方式来亲身体验一下如何添加数据:
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
// 开始组装第一条数据
values.put("name", "The Da Vinci Code");
values.put("author", "Dan Brown");
values.put("pages", 454);
values.put("price", 16.96);
db.insert("Book", null, values); // 插入第一条数据
values.clear();
// 开始组装第二条数据
values.put("name", "The Lost Symbol");
values.put("author", "Dan Brown");
values.put("pages", 510);
values.put("price", 19.95);
db.insert("Book", null, values); // 插入第二条数据
我们先获取到了SQLiteDatabase 对象,然后使用ContentValues 来对要添加的数据进行组装。如果你比较细心的话应该会发现,这里只对Book表里其中四列的数据进行了组装,id 那一列没并没给它赋值。这是因为在前面创建表的时候我们就将id 列设置为自增长了,它的值会在入库的时候自动生成,所以不需要手动给它赋值了。接下来调用了insert()方法将数据添加到表当中,注意这里我们实际上添加了两条数据,上述代码中使用ContentValues 分别组装了两次不同的内容,并调用了两次insert()方法。
打开BookStore.db 数据库,输入SQL 查询语句select * from Book,结果如图:
由此可以看出,我们刚刚组装的两条数据,都已经准确无误地添加到Book 表中了。
七、更新数据
SQLiteDatabase 中也是提供了一个非常好用的update()方法用于对数据进行更新:
public int update(String table, ContentValues values, String whereClause, String[] whereArgs);
这个方法接收四个参数:
第一个参数和insert()方法一样,也是表名,在这里指定去更新哪张表里的数据;
第二个参数是ContentValues 对象,要把更新数据在这里组装进去;
第三个参数是更新条件(where字句);
第四个参数是更新条件数组。
其中第三、第四个参数用于去约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。
接下来我们仍然是在DatabaseTest 项目的基础上修改书的价格:
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentValues values = new ContentValues();
values.put("price", 10.99);
db.update("Book", values, "name = ?",
new String[] { "The Da Vinci Code" });
这里构建了一个ContentValues 对象,并且只给它指定了一组数据,说明我们只是想把价格这一列的数据更新成10.99。然后调用了SQLiteDatabase的update()方法去执行具体的更新操作,可以看到,这里使用了第三、第四个参数来指定具体更新哪几行。第三个参数对应的是SQL 语句的where 部分,表示去更新所有name 等于?的行,而?是一个占位符,可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应的内容。因此上述代码想表达的意图就是,将名字是The Da Vinci Code的这本书的价格改成10.99。
八、删除数据
SQLiteDatabase 中提供了一个delete()方法专门用于删除数据:
public int delete(String table, String whereClause, String[] whereArgs) ;
这个方法接收三个参数,第一个参数仍然是表名,这个已经没什么好说的了,第二、第三个参数又是用于去约束删除某一行或某几行的数据,不指定的话默认就是删除所有行。
我们去删除Book 表中的数据,并且通过第二、第三个参数来指定仅删除那些页数超过500 页的书籍:
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "pages > ?", new String[] { "500" });
九、查询数据
SQLiteDatabase 中还提供了一个query()方法用于对数据进行查询:
public Cursor query(String table, String[] columns, String selection,String[] selectionArgs, String groupBy, String having,String orderBy)
第一个参数不用说,当然还是表名,表示我们希望从哪张表中查
询数据。
第二个参数用于指定去查询哪几列,如果不指定则默认查询所有列。
第三、第四个参数用于去约束查询某一行或某几行的数据,不指定则默认是查询所有行的数据。
第五个参数用于指定需要去group by 的列,不指定则表示不对查询结果进行group by 操作。
第六个参数用于对group by 之后的数据进行进一步的过滤,不指定则表示不进行过滤。
第七个参数用于指定查询结果的排序方式,不指定则表示使用默认的排序方式
其他几个query()方法的重载其实也大同小异,你可以自己去研究一下,这里就不再进行介绍了。
虽然query()方法的参数非常多,但是不要对它产生畏惧,因为我们不必为每条查询语句都指定上所有的参数,多数情况下只需要传入少数几个参数就可以完成查询操作了。调用query()方法后会返回一个Cursor 对象,查询到的所有数据都将从这个对象中取出。
下面还是通过例子的方式来体验一下查询数据的具体用法:
SQLiteDatabase db = dbHelper.getWritableDatabase();
Cursor cursor = db.query("Book", null, null, null, null, null,
null);
if (cursor.moveToFirst()) {
do {
String name = cursor.getString(cursor
.getColumnIndex("name"));
String author = cursor.getString(cursor
.getColumnIndex("author"));
int pages = cursor.getInt(cursor
.getColumnIndex("pages"));
double price = cursor.getDouble(cursor
.getColumnIndex("price"));
Log.d("MainActivity", "book name is " + name);
Log.d("MainActivity", "book author is " + author);
Log.d("MainActivity", "book pages is " + pages);
Log.d("MainActivity", "book price is " + price);
} while (cursor.moveToNext());
}
cursor.close();
这里的query()方法非常简单,只是使用了第一个参数指明去查询Book 表,后面的参数全部为null。这就表示希望查询这张表中的所有数据,虽然这张表中目前只剩下一条数据了。查询完之后就得到了一个Cursor 对象,接着我们调用它的moveToFirst()方法将数据的指针移动到第一行的位置,然后进入了一个循环当中,去遍历查询到的每一行数据。
在这个循环中可以通过Cursor 的getColumnIndex()方法获取到某一列在表中对应的位置索引,然后将这个索引传入到相应的取值方法中,就可以得到从数据库中读取到的数据了。接着我们使用Log 的方式将取出的数据打印出来,借此来检查一下读取工作有没有成功完成。最后别忘了调用close()方法来关闭Cursor。
当然这个例子只是对查询数据的用法进行了最简单的示范,在真正的项目中你可能会遇到比这要复杂得多的查询功能,更多高级的用法还需要你自己去慢慢摸索,毕竟query()方法中还有那么多的参数我们都还没用到呢。
十、使用SQL 操作数据库
虽然Android 已经给我们提供了很多非常方便的API 用于操作数据库,Android 充分考虑到了大家编程习惯,同样提供了一系列的方法,使得可以直接通过SQL 来操作数据库。
下面演示如何直接使用SQL 来完成前面学过的CRUD 操作:
1、添加数据的方法
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)",
new String[] { "The Da Vinci Code", "Dan Brown", "454", "16.96" });
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)",
new String[] { "The Lost Symbol", "Dan Brown", "510", "19.95" });
2、更新数据的方法
db.execSQL("update Book set price = ? where name = ?", new String[] { "10.99",
"The Da Vinci Code" });
3、删除数据的方法
db.execSQL("delete from Book where pages > ?", new String[] { "500" });
4、查询数据的方法
db.rawQuery("select * from Book", null);
可以看到,除了查询数据的时候调用的是SQLiteDatabase 的rawQuery()方法,其他的操作都是调用的execSQL()方法。
十一、SQLite 数据库使用事务
SQLite 数据库是支持事务的,事务的特性可以保证让某一系列的操
作要么全部完成,要么一个都不会完成。那么在什么情况下才需要使用事务呢?
想象以下场景,比如你正在进行一次转账操作,银行会将转账的金额先从你的账户中扣除,然后再向收款方的账户中添加等量的金额。看上去好像没什么问题吧?可是,如果当你账户中的金额刚刚被扣除,这时由于一些异常原因导致对方收款失败,这一部分钱就凭空消失了!当然银行肯定已经充分考虑到了这种情况,它会保证扣钱和收款的操作要么一起成功,要么都不会成功,而使用的技术当然就是事务了。
接下来我们看一看如何在Android 中使用事务吧,仍然是在DatabaseTest 项目的基础上进行修改。比如Book 表中的数据都已经很老了,现在准备全部废弃掉替换成新数据,可以先使用delete()方法将Book表中的数据删除,然后再使用insert()方法将新的数据添加到表中。我们要保证的是,删除旧数据和添加新数据的操作必须一起完成,否则就还要继续保留原来的旧数据。如下所示:
SQLiteDatabase db=dbHelper.getWritableDatabase();
db.beginTransaction();// 开启事务
try{
db.delete("Book",null,null);
if(true){
// 在这里手动抛出一个异常,让事务失败
throw new NullPointerException();
}
ContentValues values=new ContentValues();
values.put("name","Game of Thrones");
values.put("author","George Martin");
values.put("pages",720);
values.put("price",20.85);
db.insert("Book",null,values);
db.setTransactionSuccessful();// 事务已经执行成功
}catch(Exception e){
e.printStackTrace();
}finally{
db.endTransaction();// 结束事务
}
上述代码就是Android 中事务的标准用法,首先调用SQLiteDatabase 的beginTransaction()方法来开启一个事务,然后在一个异常捕获的代码块中去执行具体的数据库操作,当所有的操作都完成之后,调用setTransactionSuccessful()表示事务已经执行成功了,最后在finally代码块中调用endTransaction()来结束事务。
注意观察,我们在删除旧数据的操作完成后手动抛出了一个NullPointerException,这样添加新数据的代码就执行不到了。不过由于事务的存在,中途出现异常会导致事务的失败,此时旧数据应该是删除不掉的。
可以运行一下程序,你会发现,Book 表中存在的还是之前的旧数据。然后将手动抛出异常的那行代码去除,再重新运行一下程序,就会将Book 表中的数据替换成新数据了。