26
2017
09

2.1 SQLite数据库的创建和升级

点此进入:从零快速构建APP系列目录导图
点此进入:UI编程系列目录导图
点此进入:四大组件系列目录导图
点此进入:数据网络和线程系列目录导图
本节例程下载地址:WillFlowDatabast

一、SQLite简介

**SQLite 是一款轻量级的关系型数据库,它的运算速度非常快,占用资源很少,通常只需要几百 K 的内存就足够了,因而特别适合在移动设备上使用。**SQLite 不仅支持标准的 SQL 语法,还遵循了数据库的 ACID 事务,所以只要你以前使用过其他的关系型数据库,就可以很快地上手 SQLite。而 SQLite 又比一般的数据库要简单得多,它甚至不用设置用户名和密码就可以使用。 Android 正是把这个功能极为强大的数据库嵌入到了系统当中,使得本地持久化的功能有了一次质的飞跃。

前面我们所学的文件存储和 SharedPreferences 存储毕竟只适用于去保存一些简单的数据和键值对,当需要存储大量复杂的关系型数据的时候,你就会发现以上两种存储方式很难应付得了。比如我们手机的短信程序中可能会有很多个会话,每个会话中又包含了很多条信息内容,并且大部分会话还可能各自对应了电话簿中的某个联系人。很难想象如何用文件或者 SharedPreferences 来存储这些数据量大、结构性复杂的数据吧?但是使用数据库就可以做得到。那么我们就赶快来看一看, Android 中的 SQLite 数据库到底是如何使用的。

二、创建数据库

Android 为了让我们能够更加方便地管理数据库,专门提供了一个 SQLiteOpenHelper 帮助类,借助这个类就可以非常简单地对数据库进行创建和升级。

首先我们要知道 SQLiteOpenHelper 是一个抽象类,这意味着如果我们想要使用它的话,就需要创建一个自己的帮助类去继承它。 SQLiteOpenHelper 中有两个抽象方法,分别是 onCreate() 和 onUpgrade(),我们必须在自己的帮助类里面重写这两个方法,然后分别在这两个方法中去实现创建、升级数据库的逻辑。

SQLiteOpenHelper 中还有两个非常重要的实例方法,getReadableDatabase() 和 getWritableDatabase()。这两个方法都可以创建或打开一个现有的数据库(如果数据库已存在则直接打开,否则创建一个新的数据库),并返回一个可对数据库进行读写操作的对象。不同的是,当数据库不可写入的时候(如磁盘空间已满) getReadableDatabase()方法返回的对象将以只读的方式去打开数据库,而 getWritableDatabase()方法则将出现异常。

SQLiteOpenHelper 中有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。这个构造方法中接收四个参数:
第一个参数是 Context,这个没什么好说的,必须要有它才能对数据库进行操作。
第二个参数是数据库名,创建数据库时使用的就是这里指定的名称。
第三个参数允许我们在查询数据的时候返回一个自定义的 Cursor,一般都是传入 null。
第四个参数表示当前数据库的版本号,可用于对数据库进行升级操作。

构建出 SQLiteOpenHelper 的实例之后,再调用它的 getReadableDatabase() 或 getWritableDatabase()方法就能够创建数据库了,数据库文件会存放在/data/data//databases/目录下。此时,重写的 onCreate() 方法也会得到执行,所以通常会在这里去处理一些创建表的逻辑。接下来还是让我们通过例子的方式来更加直观地体会 SQLiteOpenHelper 的用法吧。

这里我们希望创建一个名为 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,代码如下:
/** * Created by : WGH. */
public class MyDatabaseHelper extends SQLiteOpenHelper {
    private Context mContext;

    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);
        this.mContext = context;
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL(CREATE_BOOK);
        Toast.makeText(mContext, "创建数据库成功", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {

    }
}

我们把建表语句定义成了一个字符串常量,然后在 onCreate() 方法中又调用了 SQLiteDatabase 的 execSQL() 方法去执行这条建表语句,并弹出一个 Toast 提示创建成功,这样就可以保证在数据库创建完成的同时还能成功创建 Book 表。

修改 activity_main.xml 中的代码,如下所示:
    <Button
        android:id="@+id/buttonDataBase"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="创建数据库"
        android:textColor="#0babf5"
        android:textSize="25dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
最后修改 MainActivity 中的代码,如下所示:
public class MainActivity extends AppCompatActivity {
    private MyDatabaseHelper myDatabaseHelper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.buttonDataBase);
        myDatabaseHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);

        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                myDatabaseHelper.getWritableDatabase();
            }
        });
    }
}

这里我们在 onCreate() 方法中构建了一个 MyDatabaseHelper 对象,并且通过构造函数的参数将数据库名指定为 BookStore.db,版本号指定为 1,然后在 Create database 按钮的点击事件里调用了 getWritableDatabase() 方法。这样当第一次点击 Create database 按钮时,就会检测到当前程序中并没有 BookStore.db 这个数据库,于是会创建该数据库并调用 MyDatabaseHelper 中的 onCreate() 方法,这样 Book 表也就得到了创建,然后会弹出一个 Toast 提示创建成功。再次点击 Create database 按钮时,会发现此时已经存在 BookStore.db 数据库了,因此不会再创建一次。

编译运行看效果:

此时 BookStore.db 数据库和 Book 表应该都已经创建成功了,因为当我们再次点击按钮时不会再有 Toast 弹出。可是又回到了之前的那个老问题,怎样才能证实它们的确是创建成功了?如果还是使用 File Explorer,那么最多你只能看到 databases 目录下出现了一个 BookStore.db 文件, Book 表是无法通过 File Explorer 看到的。因此这次我们准备换一种查看方式,使用 adb shell 来对数据库和表的创建情况进行检查。

adb 是 Android SDK 中自带的一个调试工具, 使用这个工具可以直接对连接在电脑上的手机或模拟器进行调试操作。更详细的介绍你可以参看这里:。。。

打开命令行界面,输入 adb shell,就会进入到设备的控制台,然后使用 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() 方法,它是用于对数据库进行升级的,它在整个数据库的管理工作当中起着非常重要的作用。目前我们已经有一张 Book 表用于存放书的各种详细数据,如果我们想再添加一张 Category 表用于记录书籍的分类比如: Category 表中有 id(主键)、分类名和分类代码这几个列,那么建表语句就可以写成:

    create table Category ( id integer primary key autoincrement, category_name text, category_code integer)
将这条建表语句添加到 MyDatabaseHelper 中,代码如下:
    private static final String CREATE_BOOK = "create table book ("
            + "id integer primary key autoincrement, "
            + "author text, "
            + "price real, "
            + "pages integer, "
            + "name text)";

    private 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);
        this.mContext = context;
    }

    @Override
    public void onCreate(SQLiteDatabase sqLiteDatabase) {
        sqLiteDatabase.execSQL(CREATE_BOOK);
        sqLiteDatabase.execSQL(CREATE_CATEGORY);
        Toast.makeText(mContext, "创建数据库成功", Toast.LENGTH_SHORT).show();
    }

其实这个时候我们去点击创建数据库按钮是不能成功的,因为此时 BookStore.db 数据库已经存在了,之后不管我们怎样点击创建数据库按钮, MyDatabaseHelper 中的 onCreate() 方法都不会再次执行,因此新添加的表也就无法得到创建了。所以我们需要运用 SQLiteOpenHelper 的升级功能解决这个问题。

修改 MyDatabaseHelper 中的代码,如下所示:
    @Override
    public void onUpgrade(SQLiteDatabase sqLiteDatabase, int i, int i1) {
        sqLiteDatabase.execSQL("drop table if exists Book");
        sqLiteDatabase.execSQL("drop table if exists Category");
        onCreate(sqLiteDatabase);
    }

可以看到,我们在 onUpgrade() 方法中执行了两条 DROP 语句,如果发现数据库中已经存在 Book 表或 Category 表了,就将这两张表删除掉,然后再调用 onCreate() 方法去重新创建。这里先将已经存在的表删除掉,是因为如果在创建表时发现这张表已经存在了,就会直接报错。

接下来我们让 onUpgrade( )方法能够执行:看一下 SQLiteOpenHelper 的构造方法里接收的第四个参数,它表示当前数据库的版本号,之前我们传入的是 1,现在只要传入一个比 1 大的数,就可以让 onUpgrade() 方法得到执行了。

修改 MainActivity 中的代码,如下所示:
public class MainActivity extends AppCompatActivity {
    private MyDatabaseHelper myDatabaseHelper;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        myDatabaseHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);
        ...
    }
}

这里将数据库版本号指定为 2,表示我们对数据库进行升级了。现在重新运行程序,并点击按钮,这时就会再次弹出和刚才一样的创建成功提示。为了验证一下 Category 表是不是已经创建成功了,我们在 adb shell 中打开 BookStore.db 数据库,然后键入.table 命令,结果如图所示:

由此可以看出, Category 表已经创建成功了,同时也说明我们的升级功能的确起到了作用。

点此进入:GitHub开源项目“爱阅”,下面是“爱阅”的效果图:


联系方式:

简书:WillFlow
CSDN:WillFlow
微信公众号:WillFlow

微信公众号:WillFlow

上一篇:1.2 SharedPreference 的使用技巧 下一篇:3.3 基于 Socket 协议的网络通信详解