17.SQLite数据库存储
Android系统内置一个SQLite数据库,SQLite是一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,通常只需要几百K的内存就足够了。
SQLite不仅支持标准的SQL语法,还遵循了数据库的ACID事务(Atomic原子性、Consistency一致性、Isolation隔离性、Durability持久性),所以只要以前使用过其他的关系型数据库,例如SQL Server,就可以很快地上手SQLite。而SQLite又比一般的数据库要简单得多,不用设置用户名和密码就可以使用。
前面我们所学的文件存储和SharedPreferences存储只适用于去保存一些简单的数据和键值对,当需要存储大量复杂的关系型数据的时候,就需要使用到SQLite数据库了。
一、数据库操作
1、创建数据库
Android提供了一个SQLiteOpenHelper帮助类,这个类是一个抽象类,如果我们想要使用它的话,就必须创建一个自己的帮助类去继承它。
SQLiteOpenHelper中有两个抽象方法:onCreate()和onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。
SQLiteOpenHelper中还有两个实例方法(非抽象方法):getReadableDatabase()和getWritableDatabase(),这两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。
不同的是,当数据库不可写入的时候(如磁盘空间已满),getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而getWritableDatabase()方法则将出现异常。
SQLiteOpenHelper中还有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。
这个构造方法中接收四个参数:
第一个参数是Context,上下文,必须要有它才能对数据库进行操作;
第二个参数是数据库名,创建数据库时使用的就是这里指定的名称;
第三个参数允许我们在查询数据的时候返回一个自定义的Cursor(光标),一般都是传入null;
第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作。
构建出SQLiteOpenHelper的实例之后,再调用它的getReadableDatabase()或getWritableDatabase()方法就能够创建数据库了,数据库文件会存放在/data/data/<package name>/databases/目录下。
此时,重写的onCreate()方法也会得到执行,所以通常会在这里去处理一些创建表的逻辑。
接下来我们通过实例来学习SQLiteOpenHelper的用法吧,首先新建一个DatabaseTest项目。
这里我们希望创建一个名为BookStore.db的数据库,然后在这个数据库中新建一张Book表,表中有id(主键)、作者、价格、页数和书名等列。
创建数据库表当然还是需要用建表语句的,Book表的建表语句如下所示:
create table Book ( id integer primary key autoincrement, author text, price real, pages integer, name text)
只要有点SQL方面的知识就会很好理解上面的语句。
SQLite不像其他的数据库拥有众多繁杂的数据类型,它的数据类型很简单,有5个原始的数据类型。
名称 |
说明 |
对应Java类型 |
NULL |
表示没有值。 |
null |
INTEGER |
有符号整形,根据值的大小以1,2,3,4,6或8字节存放 |
byte、short、int 和long |
REAL |
浮点型值,以8字节IEEE浮点数存放 |
float、double |
TEXT |
文本字符串,使用数据库编码(UTF-8,UTF-16)存放 |
String |
BLOB |
一个数据块,完全按照输入存放(即没有转换) |
-- |
SQLite没有存储日期和时间的类型,日期和时间可以以TEXT、REAL或INTEGER形式存放:
- TEXT 作为IS08601字符串("YYYY-MM-DD HH:MM:SS.SSS")
- REAL 从格林威治时间11月24日,4174 B.C中午以来的天数
- INTEGER 从 1970-01-01 00:00:00 UTC(协调世界时)以来的秒数
另外,上述建表语句中我们还使用了primary key将id列设为主键,并用autoincrement关键字表示id列是自增长的。
然后需要在代码中去执行这条SQL语句,才能完成创建表的操作。新建MyDatabaseHelper类继承自SQLiteOpenHelper,代码如下所示:
import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.widget.Toast; import androidx.annotation.Nullable; 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(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.CursorFactory factory, int version) { super(context, name, factory, version); mContext = context; } @Override public void onCreate(SQLiteDatabase db) { db.execSQL(CREATE_BOOK); Toast.makeText(mContext, "创建成功", Toast.LENGTH_SHORT).show(); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 升级数据库的方法,稍后会讲 } }
可以看到,我们把建表语句定义成了一个字符串常量,然后在onCreate()方法中又调用了SQLiteDatabase的execSQL()方法去执行这条建表语句,并弹出一个Toast提示创建成功,这样就可以保证在数据库创建完成的同时还能成功创建Book表。
现在修改activity_main.xml中的代码,如下所示:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btnCreateDB" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="创建数据库" /> </LinearLayout>
布局文件很简单,加入一个按钮,用于创建数据库。最后修改MainActivity中的代码,如下所示:
import androidx.appcompat.app.AppCompatActivity; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity { 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 btnCreateDB = (Button) findViewById(R.id.btnCreateDB); btnCreateDB.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); } }); } }
这里我们在onCreate()方法中构建了一个MyDatabaseHelper对象(某个数据库管理员),并且通过构造函数的参数将数据库名指定为BookStore.db,版本号指定为1,然后在“创建数据库”按钮的点击事件里调用了getWritableDatabase()方法。
这样当第一次点击“创建数据库”按钮时,就会检测到当前程序中并没有BookStore.db这个数据库,于是会创建该数据库并调用MyDatabaseHelper中的onCreate()方法,这样Book表也就得到了创建,然后会弹出一个Toast提示创建成功。
再次点击“创建数据库”按钮时,会发现此时已经存在BookStore.db数据库了,因此不会再创建一次。
运行一下代码,在程序主界面点击“创建数据库”按钮,如图所示。
此时BookStore.db数据库和Book表应该都已经创建成功了,因为当你再次点击“创建数据库”按钮时不会再有Toast弹出。
下面我们来验证一下,使用Device File Explorer,只能看到databases目录下出现了一个BookStore.db文件。
如果我们想查看数据库文件的内容,可以通过一下几种方式:
第一种:使用ABD连接数据库查看
adb是Android SDK中自带的一个调试工具,使用这个工具可以直接对连接在电脑上的手机或模拟器进行调试操作。
该命令存放在sdk的platform-tools目录下,如果想要在命令行中使用这个工具,就需要先把它的路径配置到环境变量里。
或者直接到sdk的platform-tools目录下执行。
打开命令行界面,输入 adb shell,就会进入到设备的控制台。
然后使用cd命令进行到/data/data/com.sdbi.databasetest/databases/目录下,如果出现Permission denied再输入su root命令。
再使用ls命令查看到该目录里的文件。
这个目录下生成了3个文件:
BookStore.db:生成的BookStore数据库文件;
BookStore.db-shm:共享内存
BookStore.db-wal:write-ahead log,是保存的日志文件。
接下来我们就要借助sqlite3命令来打开数据库了,只需要键入sqlite3,后面加上数据库名即可。
这时就已经打开了 BookStore.db 数据库,现在就可以对这个数据库中的表进行管理了。
首先来看一下目前数据库中有哪些表,键入.table 命令。
可以看到,此时数据库中有两张表:
Book表:就是我们在MyDatabaseHelper中创建的。
android_metadata表:是每个数据库中都会自动生成的,不用管它。
这里还可以通过.schema 命令来查看它们的建表语句。
由此证明,BookStore.db数据库和Book表确实已经是创建成功了。
之后键入.exit 或.quit命令可以退出数据库的编辑,再键入 exit 命令就可以退出设备控制台了。
步骤总结:
步骤1:adb shell
步骤2:cd 项目路径
步骤3:su root(如果出现Permission denied再输入su root命令)
步骤4:cd 项目路径
步骤5:sqlite3 BookStore.db
步骤6:.table
步骤7:.schema
第二种:可以将数据库文件导出到电脑,使用第三方工具Navicat连接后,就可以打开查看了。
第三种:也可以在Android Studio中安装Database Navigator插件来打开SQLite文件。
安装好插件后,可以从Android Studio左侧“DB Browser”里添加数据库链接。
测试
2、升级数据库
MyDatabaseHelper中还有一个空方法onUpgrade(),这个方法是用于对数据库进行升级的,它在整个数据库的管理工作当中起着非常重要的作用。
目前DatabaseTest项目中已经有一张Book表用于存放书的各种详细数据,如果我们想再添加一张Category表用于记录书籍的分类该怎么做呢?
比如Category表中有id(主键)、分类名和分类代码这几个列,那么建表语句就可以写成:
create table Category ( id integer primary key autoincrement, category_name text, category_code integer)
接下来我们将这条建表语句添加到MyDatabaseHelper中,代码如下所示:
import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.widget.Toast; import androidx.annotation.Nullable; 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(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.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, "创建成功", Toast.LENGTH_SHORT).show(); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // 升级数据库的方法,稍后会讲 } }
看上去好像都挺对的吧,现在我们重新运行一下程序,并点击“创建数据库”按钮,竟然没有弹出创建成功的提示。
当然,你也可以通过第三方工具到数据库中再去检查一下,这样你会更加确认,Category表没有创建成功!
其实没有创建成功的原因不难思考,因为此时BookStore.db数据库已经存在了,之后不管我们怎样点击“创建数据库”按钮,MyDatabaseHelper中的onCreate()方法都不会再次执行,因此新添加的表也就无法得到创建了。
解决这个问题的办法也相当简单,只需要先将程序卸载掉,然后重新运行,这时BookStore.db数据库已经不存在了,如果再点击“创建数据库”按钮,MyDatabaseHelper中的onCreate()方法就会执行,这时Category表就可以创建成功了。
不过通过卸载程序的方式来新增一张表是很极端的做法,也是错误的,其实我们只需要巧妙地运用SQLiteOpenHelper的升级功能就可以很轻松地解决这个问题。
修改MyDatabaseHelper中的代码,如下所示:
import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.widget.Toast; import androidx.annotation.Nullable; 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(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.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, "创建成功", Toast.LENGTH_SHORT).show(); } @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中的代码,如下所示:
import androidx.appcompat.app.AppCompatActivity; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity { 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 btnCreateDB = (Button) findViewById(R.id.btnCreateDB); btnCreateDB.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); } }); } }
这里将数据库版本号指定为2,表示我们对数据库进行升级了。
现在重新运行程序,并点击“创建数据库”按钮,这时就会再次弹出创建成功的提示。
为了验证一下Category表是不是已经创建成功了,我们再次将数据库文件导出到电脑,使用第三方工具打开查看,如下图所示。
由此可以看出,Category表已经创建成功了,同时也说明我们的升级功能的确起到了作用。
但是,这种升级方法是非常不好的。下面我们来详细看一下。
3、升级数据库的最佳写法
在之前我们学习的升级数据库的方式是非常粗暴的,为了保证数据库中的表是最新的,我们只是简单地在onUpgrade()方法中删除掉了当前所有的表,然后强制重新执行了一遍onCreate()方法。
这种方式是绝对不行的。
想象以下场景,比如,你编写的某个APP应用已经成功上线运行,并且还拥有了不错的下载量。现在由于添加新功能的原因,使得数据库也需要一起升级,然后用户更新了这个版本之后发现以前程序中存储的本地数据全部丢失了!那么谁还会愿意使用你的应用程序。
那么,怎么做是最好的数据库升级方式呢?
每一个数据库版本都会对应一个版本号,当指定的数据库版本号大于当前数据库版本号的时候,就会进入到onUpgrade()方法中去执行更新操作。
这里需要为每一个版本号赋予它各自改变的内容,然后在onUpgrade()方法中对当前数据库的版本号进行判断,再执行相应的改变就可以了。
接着就让我们来模拟一个数据库升级的案例,还是由MyDatabaseHelper类来对数据库进行管理。
第一版程序的要求非常简单,只需要创建一张Book表,MyDatabaseHelper中的代码如下所示:
import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.widget.Toast; import androidx.annotation.Nullable; 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(@Nullable Context context, @Nullable String name, @Nullable 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) { } }
不过,一段时间之后又有了新需求,这次需要向数据库中再添加一张Category表。
于是,修改MyDatabaseHelper中的代码,如下所示:
import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.widget.Toast; import androidx.annotation.Nullable; 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(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.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表。
这样当用户是直接安装的第2版的程序时,就会将两张表一起创建。
而当用户是使用第2版的程序覆盖安装第1版的程序时,就会进入到升级数据库的操作中,此时由于Book表已经存在了,因此只需要创建一张Category表即可。
但是没过多久,新的需求又来了,这次要给Book表和Category表之间建立关联,需要在Book表中添加一个category_code的字段。
再次修改MyDatabaseHelper中的代码,如下所示:
import android.content.Context; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.widget.Toast; import androidx.annotation.Nullable; 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_code integer)"; public static final String CREATE_CATEGORY = "create table Category (id integer primary key autoincrement, category_name text, category_code integer)"; public MyDatabaseHelper(@Nullable Context context, @Nullable String name, @Nullable SQLiteDatabase.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_code integer"); default: } } }
可以看到,首先我们在Book表的建表语句中添加了一个category_code列,这样当用户直接安装第3版的程序时,这个新增的列就已经自动添加成功了。
然而,如果用户之前已经安装了某一版本的程序,现在需要覆盖安装,就会进入到升级数据库的操作中。
在onUpgrade()方法里,我们添加了一个新的case,如果当前数据库的版本号是2,就会执行alter命令来为Book表新增一个category_code列。
这里请注意一个非常重要的细节,switch中每一个case的最后都是没有使用break的,为什么呢?
这是为了保证在跨版本升级的时候,每一次的数据库修改都能被全部执行到。
比如用户当前是从第2版程序升级到第3版程序的,那么case 2中的逻辑就会执行。
而如果用户是直接从第1版程序升级到第3版程序的,那么case 1和case 2中的逻辑都会执行(case穿透)。
使用这种方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是最新的,而且表中的数据也完全不会丢失了。
二、数据操作
对表中数据的操作,有四种:增、删、改、查。每一种操作又各自对应了一种SQL命令。
例如添加数据时使用insert,查询数据时使用select,更新数据时使用update,删除数据时使用delete。
此外,Android又提供了一系列的辅助性方法,可以不用去编写SQL语句,也能轻松完成增、删、改、查操作。(两种方法操作数据)
之前我们调用SQLiteOpenHelper(数据库管理认证考试,通过考试派生出来的子类就是数据库管理员类)的getReadableDatabase()或getWritableDatabase()方法是可以用于创建和获取数据库的,这两个方法还都会返回一个SQLiteDatabase对象,借助这个对象就可以对数据进行增、删、改、查操作了。
1、添加数据
使用SQLiteDatabase(数据库)的insert()方法,这个方法接收三个参数:
第一个参数是表名,希望向哪张表里添加数据。
第二个参数指强行插入null值的数据列的列名,一般我们用不到这个功能,直接传入null即可。
第三个参数是一行记录的数据,需要传入一个ContentValues对象,它提供了一系列的put()方法重载,用于向ContentValues中添加数据,只需要将表中的每个列名(字段名)以及相应的待添加数据传入即可(对应SQL语句:INSERT INTO 表名称 (列1, 列2,...) VALUES (值1, 值2,....))。
修改activity_main.xml中的代码,如下所示:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btnCreateDB" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="创建数据库" /> <Button android:id="@+id/btnInsert" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="插入数据" /> </LinearLayout>
在布局文件中新增了一个按钮,在这个按钮的点击事件里编写添加数据的逻辑。
接着修改MainActivity中的代码,如下所示:
import androidx.appcompat.app.AppCompatActivity; import android.content.ContentValues; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private MyDatabaseHelper dbHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 3); Button btnCreateDB = (Button) findViewById(R.id.btnCreateDB); btnCreateDB.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); } }); Button btnInsert = (Button) findViewById(R.id.btnInsert); btnInsert.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); // 开始组装第一条数据 values.put("name", "Android程序设计"); values.put("author", "张三"); values.put("pages", 454); values.put("price", 76.96); db.insert("Book", null, values); // 插入第一条数据 values.clear(); // 清空数据容器 // 开始组装第二条数据 values.put("name", "Java程序设计"); values.put("author", "李四"); values.put("pages", 510); values.put("price", 89.95); db.insert("Book", null, values); // 插入第二条数据 Toast.makeText(MainActivity.this, "插入数据", Toast.LENGTH_SHORT).show(); } }); } }
在“插入数据”按钮的点击事件里面,我们先获取到了SQLiteDatabase对象,然后使用ContentValues来对要添加的数据进行组装。
注意,这里只对Book表里其中四列的数据进行了组装,id那一列没并没给它赋值,这是因为在前面创建表的时候我们就将id列设置为自增长了,它的值会在入库的时候自动生成,所以不需要手动给它赋值了。
接下来调用了insert()方法将数据添加到表当中,注意这里我们添加了两条数据,使用ContentValues分别组装了两次不同的内容,并调用了两次insert()方法。
重新运行一下程序,界面如图所示。
点击一下“插入数据”按钮,此时两条数据应该都已经添加成功了,不过为了证实一下,我们还是使用第三方工具打开BookStore.db数据库查看一下,结果如图所示。
2、更新数据
修改表中已有的数据,SQLiteDatabase中提供了update()方法用于对数据进行更新,这个方法接收四个参数:
第一个参数和insert()方法一样,也是表名,在这里指定去更新哪张表里的数据。
第二个参数是ContentValues对象,要把更新数据在这里组装进去。
第三、第四个参数用于去约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行(where语句,对应SQL语句:UPDATE 表名称 SET 列名称 = 新值 WHERE 列名称 = 某值)。
再来修改activity_main.xml中的代码,如下所示:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btnCreateDB" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="创建数据库" /> <Button android:id="@+id/btnInsert" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="插入数据" /> <Button android:id="@+id/btnUpdate" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="更新数据" /> </LinearLayout>
布局文件中的代码就已经非常简单了,就是添加了一个用于更新数据的按钮。
然后修改MainActivity中的代码,如下所示:
import androidx.appcompat.app.AppCompatActivity; import android.content.ContentValues; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private MyDatabaseHelper dbHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 3); Button btnCreateDB = (Button) findViewById(R.id.btnCreateDB); btnCreateDB.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); } }); Button btnInsert = (Button) findViewById(R.id.btnInsert); btnInsert.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); // 开始组装第一条数据 values.put("name", "Android程序设计"); values.put("author", "张三"); values.put("pages", 454); values.put("price", 76.96); db.insert("Book", null, values); // 插入第一条数据 values.clear(); // 清空数据容器 // 开始组装第二条数据 values.put("name", "Java程序设计"); values.put("author", "李四"); values.put("pages", 510); values.put("price", 89.95); db.insert("Book", null, values); // 插入第二条数据 Toast.makeText(MainActivity.this, "插入数据", Toast.LENGTH_SHORT).show(); } }); Button btnUpdate = (Button) findViewById(R.id.btnUpdate); btnUpdate.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("price", 103.99); db.update("Book", values, "name = ?", new String[] { "Android程序设计" }); Toast.makeText(MainActivity.this, "更新数据", Toast.LENGTH_SHORT).show(); } }); } }
这里在“更新数据”按钮的点击事件里面构建了一个ContentValues对象,并且只给它指定了一组数据,说明我们只是想把价格这一列的数据更新成103.99。
然后调用了SQLiteDatabase的update()方法去执行具体的更新操作,可以看到,这里使用了第三、第四个参数来指定具体更新哪几行。
第三个参数对应的是SQL语句的where部分,表示去更新所有name等于?的行,而?是一个占位符,可以通过第四个参数提供的一个字符串数组为第三个参数中的每个占位符指定相应的内容。
因此上述代码想表达的意图就是,将名字是“Android程序设计”的这本书的价格改成103.99。现在重新运行一下程序。
点击一下Update data按钮后,再次查看表中的数据情况,结果如图所示。
可以看到,“Android程序设计”这本书的价格已经被成功改为103.99了。
3、删除数据
删除数据就更简单了,SQLiteDatabase中提供了一个delete()方法专门用于删除数据,这个方法接收三个参数:
第一个参数仍然是表名,这个已经没什么好说的了,
第二、第三个参数又是用于去约束删除某一行或某几行的数据,不指定的话默认就是删除所有行(where语句。对应SQL语句:DELETE FROM 表名称 WHERE 列名称 = 值)。
修改activity_main.xml中的代码,如下所示:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btnCreateDB" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="创建数据库" /> <Button android:id="@+id/btnInsert" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="插入数据" /> <Button android:id="@+id/btnUpdate" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="更新数据" /> <Button android:id="@+id/btnDelete" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="删除数据" /> </LinearLayout>
仍然是在布局文件中添加了一个按钮,用于删除数据。
然后修改MainActivity中的代码,如下所示:
import androidx.appcompat.app.AppCompatActivity; import android.content.ContentValues; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private MyDatabaseHelper dbHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 3); Button btnCreateDB = (Button) findViewById(R.id.btnCreateDB); btnCreateDB.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); } }); Button btnInsert = (Button) findViewById(R.id.btnInsert); btnInsert.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); // 开始组装第一条数据 values.put("name", "Android程序设计"); values.put("author", "张三"); values.put("pages", 454); values.put("price", 76.96); db.insert("Book", null, values); // 插入第一条数据 values.clear(); // 清空数据容器 // 开始组装第二条数据 values.put("name", "Java程序设计"); values.put("author", "李四"); values.put("pages", 510); values.put("price", 89.95); db.insert("Book", null, values); // 插入第二条数据 Toast.makeText(MainActivity.this, "插入数据", Toast.LENGTH_SHORT).show(); } }); Button btnUpdate = (Button) findViewById(R.id.btnUpdate); btnUpdate.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("price", 103.99); db.update("Book", values, "name = ?", new String[]{"Android程序设计"}); Toast.makeText(MainActivity.this, "更新数据", Toast.LENGTH_SHORT).show(); } }); Button btnDelete = (Button) findViewById(R.id.btnDelete); btnDelete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); db.delete("Book", "pages > ?", new String[]{"500"}); Toast.makeText(MainActivity.this, "删除数据", Toast.LENGTH_SHORT).show(); } }); } }
可以看到,我们在删除按钮的点击事件里指明去删除Book表中的数据,并且通过第二、第三个参数来指定仅删除那些页数超过500页的书籍。
我们可以先查看一下当前Book表里的数据,其中“Java程序设计”这本书的页数超过了500页,也就是说当我们点击删除按钮时,这条记录应该会被删除掉。
现在重新运行一下程序,如图所示。
点击一下“删除数据”按钮后,再次查看表中的数据情况,结果如图所示。
这样就可以明显地看出,“Java程序设计”这本书的数据已经被删除了。
4、查询数据
查询数据是CRUD中最复杂的一种操作。
我们都知道SQL的全称是Structured Query Language,翻译成中文就是结构化查询语言。
它的大部功能都是体现在“查”这个字上的,而“增删改”只是其中的一小部分功能。
SQLiteDatabase中提供了一个query()方法用于对数据进行查询。
这个方法的参数非常复杂,最短的一个方法重载也需要传入7个参数:
第一个参数不用说,当然还是表名,表示我们希望从哪张表中查询数据。
第二个参数用于指定去查询哪几列,如果不指定则默认查询所有列。
第三、第四个参数用于去约束查询某一行或某几行的数据,不指定则默认是查询所有行的数据。
第五个参数用于指定需要去group by的列,不指定则表示不对查询结果进行group by操作。
第六个参数用于对group by之后的数据进行进一步的过滤,不指定则表示不进行过滤。
第七个参数用于指定查询结果的排序方式,不指定则表示使用默认的排序方式。
更多详细的内容可以参考下表。其他几个query()方法的重载其实也大同小异,可以自己去研究一下,这里就不再进行介绍了。
序号 |
方法参数 |
对应SQL部分 |
描述 |
1 |
table |
from table_name |
指定查询的表名 |
2 |
columns |
select column1, column2 |
指定查询的列名 |
3 |
selection |
where column = value |
指定where的约束条件 |
4 |
selectionArgs |
- |
为where中的占位符提供具体的值 |
5 |
groupBy |
group by column |
指定需要group by的列,按照某个字段或者表达式进行分组 |
6 |
having |
having column = value |
对group by后的结果进一步过滤。 |
7 |
orderBy |
order by column1, column2 |
指定查询结果的排序方式 |
虽然query()方法的参数非常多,但是我们不一定为每条查询语句都指定上所有的参数,多数情况下只需要传入少数几个参数就可以完成查询操作了。
调用query()方法后会返回一个Cursor对象,查询到的所有数据都将从这个对象中取出。
下面还是让我们通过例子的方式来熟悉一下查询数据的具体用法,修改activity_main.xml中的代码,如下所示:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btnCreateDB" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="创建数据库" /> <Button android:id="@+id/btnInsert" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="插入数据" /> <Button android:id="@+id/btnUpdate" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="更新数据" /> <Button android:id="@+id/btnDelete" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="删除数据" /> <Button android:id="@+id/btnSelect" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="查询数据" /> </LinearLayout>
这个已经没什么好说的了,添加了一个按钮用于查询数据。
然后修改MainActivity中的代码,如下所示:
import androidx.appcompat.app.AppCompatActivity; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private MyDatabaseHelper dbHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 3); Button btnCreateDB = (Button) findViewById(R.id.btnCreateDB); btnCreateDB.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); } }); Button btnInsert = (Button) findViewById(R.id.btnInsert); btnInsert.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); // 开始组装第一条数据 values.put("name", "Android程序设计"); values.put("author", "张三"); values.put("pages", 454); values.put("price", 76.96); db.insert("Book", null, values); // 插入第一条数据 values.clear(); // 清空数据容器 // 开始组装第二条数据 values.put("name", "Java程序设计"); values.put("author", "李四"); values.put("pages", 510); values.put("price", 89.95); db.insert("Book", null, values); // 插入第二条数据 Toast.makeText(MainActivity.this, "插入数据", Toast.LENGTH_SHORT).show(); } }); Button btnUpdate = (Button) findViewById(R.id.btnUpdate); btnUpdate.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("price", 103.99); db.update("Book", values, "name = ?", new String[]{"Android程序设计"}); Toast.makeText(MainActivity.this, "更新数据", Toast.LENGTH_SHORT).show(); } }); Button btnDelete = (Button) findViewById(R.id.btnDelete); btnDelete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); db.delete("Book", "pages > ?", new String[]{"500"}); Toast.makeText(MainActivity.this, "删除数据", Toast.LENGTH_SHORT).show(); } }); Button btnSelect = (Button) findViewById(R.id.btnSelect); btnSelect.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); // 查询Book表中所有的数据 Cursor cursor = db.query("Book", null, null, null, null, null, null); while (cursor.moveToNext()) { // 遍历Cursor对象,取出数据并打印 int nameIndex = cursor.getColumnIndex("name"); String name = cursor.getString(nameIndex); int authorIndex = cursor.getColumnIndex("author"); String author = cursor.getString(authorIndex); int pagesIndex = cursor.getColumnIndex("pages"); int pages = cursor.getInt(pagesIndex); int priceIndex = cursor.getColumnIndex("price"); double price = cursor.getDouble(priceIndex); 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); } cursor.close(); } }); } }
可以看到,我们首先在查询按钮的点击事件里面调用了SQLiteDatabase的query()方法去查询数据。
这里的query()方法只使用了第一个参数指明去查询Book表,后面的参数全部为null。
这就表示希望查询这张表中的所有数据,虽然这张表中目前只剩下一条数据了。
查询完之后就得到了一个Cursor对象,接着我们调用它的moveToFirst()方法将数据的指针移动到第一行的位置,然后进入了一个循环当中,去遍历查询到的每一行数据。
在这个循环中可以通过Cursor的getColumnIndex()方法获取到某一列在表中对应的位置索引,然后将这个索引传入到相应的取值方法getXXX()中,就可以得到从数据库中读取到的数据了。
注意:
这里不能写成如下形式,两个方法连续调用,会报语法错误!
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"));我们查阅源代码发现,getColumnIndex()方法有个注解“@IntRange(from = -1) ”,表示返回值范围是>=-1的整数;
而getString()、getInt()、getDouble()这类方法要求的参数是一个>=0的整数,有个注解修饰“@IntRange(from = 0) ”。
因此,我们需要提前定义一个临时变量来保存getColumnIndex()方法的返回值,然后对其进行判断,再传入给getString()、getInt()、getDouble()这类方法。
我们这里就不判断是否>=0了,只要不报错就可以了。
我们使用Log的方式将取出的数据打印出来,借此来检查一下读取工作有没有成功完成。最后别忘了调用close()方法来关闭Cursor。
好了,现在再次重新运行程序,如图所示。
点击一下“查询数据”按钮后,查看LogCat的打印内容,结果如图所示。
可以看到,这里已经将Book表中唯一的一条数据成功地读取出来了。
当然这个例子只是对查询数据的用法进行了最简单的示范,在真正的项目中我们可能会遇到比这要复杂得多的查询功能,更多高级的用法还需要我们自己去慢慢摸索,毕竟query()方法中还有那么多的参数我们都还没用到呢。
5、使用SQL操作数据库
虽然Android已经给我们提供了很多非常方便的API用于操作数据库,不过总会有一些人不习惯去使用这些辅助性的方法,而是更加青睐于直接使用SQL来操作数据库。
Android同样提供了一系列的方法,使得可以直接通过SQL来操作数据库。
下面我就来简略演示一下,如何直接使用SQL来完成前面几小节中学过的CRUD操作。
添加数据的方法如下:
db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)", new String[] { "Android程序设计", "张三", "454", "76.96" }); db.execSQL("insert into Book (name, author, pages, price) values(?, ?, ?, ?)", new String[] { "Java程序设计", "李四", "510", "89.95" });
更新数据的方法如下:
db.execSQL("update Book set price = ? where name = ?", new String[] { "103.99", " Android程序设计" });
删除数据的方法如下:
db.execSQL("delete from Book where pages > ?", new String[] { "500" });
查询数据的方法如下:
db.rawQuery("select * from Book", null);
可以看到,除了查询数据的时候调用的是SQLiteDatabase的rawQuery()方法,其他的操作都是调用的execSQL()方法。
选择使用哪一种方式就看你个人的喜好了。
三、事务
SQLite数据库是支持事务的,事务的特性可以保证让某一系列的操作要么全部完成,要么一个都不会完成。
那么在什么情况下才需要使用事务呢?
试想以下场景,比如你正在进行一次转账操作,银行会将转账的金额先从你的账户中扣除,然后再向收款方的账户中增加等量的金额。
可是,如果当你账户中的金额刚刚被扣除,这时由于一些异常原因导致对方收款失败,这一部分钱就凭空消失了!
当然银行肯定会充分考虑这种情况,保证扣钱和收款的操作要么一起成功,要么都不会成功,所使用的技术就是事务了。
下面我们来看一下怎样在Android中使用事务。
还是在DatabaseTest项目的基础上进行修改。
我们来完成以下的业务:Book表中的数据已经很旧了,现在要全部废弃掉替换成新数据,可以先使用delete()方法将Book表中的数据删除,然后再使用insert()方法将新的数据添加到表中。
我们要保证删除旧数据和添加新数据的操作必须一起完成,否则就还要继续保留原来的旧数据。
修改activity_main.xml中的代码,如下所示:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <Button android:id="@+id/btnCreateDB" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="创建数据库" /> <Button android:id="@+id/btnInsert" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="插入数据" /> <Button android:id="@+id/btnUpdate" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="更新数据" /> <Button android:id="@+id/btnDelete" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="删除数据" /> <Button android:id="@+id/btnSelect" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="查询数据" /> <Button android:id="@+id/btnReplace" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="替换数据" /> </LinearLayout>
可以看到,这里又添加了一个按钮,用于进行数据替换操作。
然后修改MainActivity中的代码,如下所示:
import androidx.appcompat.app.AppCompatActivity; import android.content.ContentValues; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { private MyDatabaseHelper dbHelper; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 3); Button btnCreateDB = (Button) findViewById(R.id.btnCreateDB); btnCreateDB.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); } }); Button btnInsert = (Button) findViewById(R.id.btnInsert); btnInsert.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); // 开始组装第一条数据 values.put("name", "Android程序设计"); values.put("author", "张三"); values.put("pages", 454); values.put("price", 76.96); db.insert("Book", null, values); // 插入第一条数据 values.clear(); // 清空数据容器 // 开始组装第二条数据 values.put("name", "Java程序设计"); values.put("author", "李四"); values.put("pages", 510); values.put("price", 89.95); db.insert("Book", null, values); // 插入第二条数据 Toast.makeText(MainActivity.this, "插入数据", Toast.LENGTH_SHORT).show(); } }); Button btnUpdate = (Button) findViewById(R.id.btnUpdate); btnUpdate.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); ContentValues values = new ContentValues(); values.put("price", 103.99); db.update("Book", values, "name = ?", new String[]{"Android程序设计"}); Toast.makeText(MainActivity.this, "更新数据", Toast.LENGTH_SHORT).show(); } }); Button btnDelete = (Button) findViewById(R.id.btnDelete); btnDelete.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); db.delete("Book", "pages > ?", new String[]{"500"}); Toast.makeText(MainActivity.this, "删除数据", Toast.LENGTH_SHORT).show(); } }); Button btnSelect = (Button) findViewById(R.id.btnSelect); btnSelect.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); // 查询Book表中所有的数据 Cursor cursor = db.query("Book", null, null, null, null, null, null); while (cursor.moveToNext()) { // 遍历Cursor对象,取出数据并打印 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); } cursor.close(); } }); Button btnReplace = (Button) findViewById(R.id.btnReplace); btnReplace.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { SQLiteDatabase db = dbHelper.getWritableDatabase(); db.beginTransaction(); // 开启事务 try { db.delete("Book", null, null); // 删除Book表中所有数据 if (true) { // 在这里手动抛出一个异常,让事务失败 throw new NullPointerException(); } ContentValues values = new ContentValues(); values.put("name", "Java编程思想"); values.put("author", "王五"); values.put("pages", 1520); values.put("price", 220.85); db.insert("Book", null, values); db.execSQL("insert into Book (name, author, pages, price) values ('计算机文化基础', '赵六', 720, 106.77)"); db.setTransactionSuccessful(); // 提交事务,事务已经执行成功 } catch (Exception e) { e.printStackTrace(); } finally { db.endTransaction(); // 结束事务 } } }); } }
上述代码就是Android中事务的标准用法,首先调用SQLiteDatabase的beginTransaction()方法来开启一个事务,然后在一个异常捕获的代码块中去执行具体的数据库操作,当所有的操作都完成之后,调用setTransactionSuccessful()表示事务已经执行成功了,最后在finally代码块中调用endTransaction()来结束事务。
注意观察,我们人为的在删除旧数据的操作完成后手动抛出了一个NullPointerException,这样添加新数据的代码就执行不到了。
不过由于事务的存在,中途出现异常会导致事务的失败,此时旧数据应该是删除不掉的。
现在可以运行程序测试一下,点击“替换数据”按钮,你会发现,Book表中存在的还是之前的旧数据。
然后将手动抛出异常的那行代码去除,再重新运行一下程序,此时点击一下“替换数据”按钮就会将Book表中的数据替换成新数据了。