【SQLite数据库存储】事务、升级数据库
事务
SQLite数据库是支持事务的,事务的特性可以保证让某一系列的操 作要么全部完成,要么一个都不会完成
那么在什么情况下才需要使用事务呢?想象以下场 景,比如你正在进行一次转账操作,银行会将转账的金额先从你的账户中扣除,然后再向收 款方的账户中添加等量的金额。看上去好像没什么问题吧?可是,如果当你账户中的金额刚 刚被扣除,这时由于一些异常原因导致对方收款失败,这一部分钱就凭空消失了!当然银行 肯定已经充分考虑到了这种情况,它会保证扣钱和收款的操作要么一起成功,要么都不会成 功,而使用的技术当然就是事务了
接下来我们看一看如何在 Android中使用事务吧,仍然是在上一篇文章中的项目的基础上 进行修改http://blog.csdn.net/u010356768/article/details/79391064
比如 Book表中的数据都已经很老了,现在准备全部废弃掉替换成新数据,可以 先使用delete()方法将Book表中的数据删除,然后再使用insert()方法将新的数据添加到表中。 我们要保证的是,删除旧数据和添加新数据的操作必须一起完成,否则就还要继续保留原来 的旧数据
修改 activity_main.xml中的代码
<Button android:id="@+id/replace_data" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="Replace data" />
修改 MainActivity中 的代码
Button replaceData = (Button) findViewById(R.id.replace_data);
replaceData.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
SQLiteDatabase db = myDatabaseHelper.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,这样添加新数据的代码就执行不到了。不过由于事务的存 在,中途出现异常会导致事务的失败,此时旧数据应该是删除不掉的
现在可以运行一下程序并点击 Replacedata按钮,你会发现,Book表中存在的还是之前 的旧数据
然后将手动抛出异常的那行代码去除,再重新运行一下程序,此时点击一下 Replacedata按钮就会将 Book表中的数据替换成新数据了
升级数据库的最佳写法
我们学习的升级数据库的方式是非常粗暴的,为了保证数据库中的表是最 新的,我们只是简单地在 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, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(CREATE_BOOK);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
}
}
几星期之后又有了新需求,这次需要向数据库中再添加一张 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, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(CREATE_BOOK);
sqLiteDatabase.execSQL(CREATE_CATEGORY);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
switch (oldVersion){
case 1:
sqLiteDatabase.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, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
@Override
public void onCreate(SQLiteDatabase sqLiteDatabase) {
sqLiteDatabase.execSQL(CREATE_BOOK);
sqLiteDatabase.execSQL(CREATE_CATEGORY);
}
@Override
public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) {
switch (oldVersion){
case 1:
sqLiteDatabase.execSQL(CREATE_CATEGORY);
case 2:
sqLiteDatabase.execSQL("alter table Book add column category_id integer");
default:
}
}
}
可以看到,首先我们在 Book表的建表语句中添加了一个 category_id列,这样当用户直 接安装第三版的程序时,这个新增的列就已经自动添加成功了
然而,如果用户之前已经安 装了某一版本的程序,现在需要覆盖安装,就会进入到升级数据库的操作中。在 onUpgrade() 方法里,我们添加了一个新的 case,如果当前数据库的版本号是 2,就会执行 alter命令来为 Book表新增一个 category_id列
这里请注意一个非常重要的细节,switch中每一个 case的最后都是没有使用 break的, 为什么要这么做呢?这是为了保证在跨版本升级的时候,每一次的数据库修改都能被全部执 行到。比如用户当前是从第二版程序升级到第三版程序的,那么 case2中的逻辑就会执行。 而如果用户是直接从第一版程序升级到第三版程序的,那么 case1和 case2中的逻辑都会执 行。使用这种方式来维护数据库的升级,不管版本怎样更新,都可以保证数据库的表结构是 最新的,而且表中的数据也完全不会丢失了