创建数据库
Android为了让我们能够更加方便地管理数据库,专门提供了一个SQLiteOpenHelper帮助类,借助这个类就可以非常简单地对数据库进行创建和升级。
首先你要知道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(主键)、作者、价格、页数和书名等列。创建数据库表当然还是需要用建表语句的,这里也是要考验一下你的SQL基本功了,Book表的建表语句如下所示:
create table Book (
id integer primary key autoincrement,
author text,
price real,
pages integer,
name text)
只要你对SQL方面的知识稍微有一些了解,上面的建表语句对你来说应该都不难吧。SQLite不像其他的数据库拥有众多繁杂的数据类型,它的数据类型很简单,integer表示整型,real表示浮点型,text表示文本类型,blob表示二进制类型。另外,上述建表语句中我们还使用了primary key将id列设为主键,并用autoincrement关键字表示id列是自增长的。
然后需要在代码中去执行这条SQL语句,才能完成创建表的操作。新建MyDatabaseHelper类继承自SQLiteOpenHelper,代码如下所示:
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表。
现在修改activity_main.xml中的代码,如下所示:
<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/create_database"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Create database"
/>
</LinearLayout>
布局文件很简单,就是加入了一个按钮,用于创建数据库。最后修改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, 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数据库了,因此不会再创建一次。
现在就可以运行一下代码了,在程序主界面点击Create database按钮,结果如图6.11所示。
图 6.11
此时BookStore.db数据库和Book表应该都已经创建成功了,因为当你再次点击Create database按钮时不会再有Toast弹出。可是又回到了之前的那个老问题,怎样才能证实它们的确是创建成功了?如果还是使用File Explorer,那么最多你只能看到databases目录下出现了一个BookStore.db文件,Book表是无法通过File Explorer看到的。因此这次我们准备换一种查看方式,使用adb shell来对数据库和表的创建情况进行检查。
adb是Android SDK中自带的一个调试工具,使用这个工具可以直接对连接在电脑上的手机或模拟器进行调试操作。它存放在sdk的platform-tools目录下,如果想要在命令行中使用这个工具,就需要先把它的路径配置到环境变量里。
如果你使用的是Windows系统,可以右击我的电脑→属性→高级→环境变量,然后在系统变量里找到Path并点击编辑,将platform-tools目录配置进去,如图6.12所示。
图 6.12
如果你使用的是Linux系统,可以在home路径下编辑.bash_profile文件,将platform-tools目录配置进去即可,如图6.13所示:
图 6.13
配置好了环境变量之后,就可以使用adb工具了。打开命令行界面,输入adb shell,就会进入到设备的控制台,如图6.14所示。
图 6.14
然后使用cd命令进行到/data/data/com.example.databasetest/databases/目录下,并使用ls命令查看到该目录里的文件,如图6.15所示。
图 6.15
这个目录下出现了两个数据库文件,一个正是我们创建的BookStore.db,而另一个BookStore.db-journal则是为了让数据库能够支持事务而产生的临时日志文件,通常情况下这个文件的大小都是0字节。
接下来我们就要借助sqlite命令来打开数据库了,只需要键入sqlite3,后面加上数据库名即可,如图6.16所示。
图 6.16
这时就已经打开了BookStore.db数据库,现在就可以对这个数据库中的表进行管理了。首先来看一下目前数据库中有哪些表,键入.table命令,如图6.17所示。
图 6.17
可以看到,此时数据库中有两张表,android_metadata表是每个数据库中都会自动生成的,不用管它,而另外一张Book表就是我们在MyDatabaseHelper中创建的了。这里还可以通过.schema命令来查看它们的建表语句,如图6.18所示。
图 6.18
由此证明,BookStore.db数据库和Book表确实已经是创建成功了。之后键入.exit或.quit命令可以退出数据库的编辑,再键入exit命令就可以退出设备控制台了。
升级数据库
如果你足够细心,一定会发现MyDatabaseHelper中还有一个空方法呢!没错,onUpgrade()方法是用于对数据库进行升级的,它在整个数据库的管理工作当中起着非常重要的作用,可千万不能忽视它哟。
目前DatabaseTest项目中已经有一张Book表用于存放书的各种详细数据,如果我们想再添加一张Category表用于记录书籍的分类该怎么做呢?
比如Category表中有id(主键)、分类名和分类代码这几个列,那么建表语句就可以写成:
create table Category (
id integer primary key autoincrement,
category_name text,
category_code integer)
接下来我们将这条建表语句添加到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)";
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命令,结果如图6.19所示。
图 6.19
接着键入.schema命令查看一下建表语句,结果如图6.20所示。
图 6.20
由此可以看出,Category表已经创建成功了,同时也说明我们的升级功能的确起到了作用。