第一行代码读书笔记 6 -- 持久化技术(下)

本篇文章主要介绍以下几个知识点:

  • SQLite 数据库存储;
  • 开源数据库框架 LitePal。
图片来源于网络

6.3 SQLite 数据库存储

前面介绍的两种存储方式很难应付得了需要存储大量复杂的关系型数据的时候,而使用数据库就可以做到。

SQLite 是一款轻量级的关系型数据库,它的运算速度非常快, 占用资源很少,通常只需几百 K 的内存就足够了,因而特别适合在移动设备上使用。SQLite 不仅支持标准的 SQL 语法,还遵循了数据库的 ACID 事务,只要你以前用过其他的关系型数据库,就可以很快地上手 SQLite。

6.3.1 创建数据库

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

SQLiteOpenHelper 是一个抽象类,意味着若要使用它的话, 就需要创建一个自己的帮助类去继承它并且重写它的两个抽象方法,即 onCreate() 和 onUpgrade(),去实现创建、升级数据库的逻辑。

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

SQLiteOpenHelper 中有两个构造方法可供重写,一般使用参数少一点的那个构造方法即可。构建出 SQLiteOpenHelper 的实例之后,再调用它的 getReadableDatabase() 或 getWritableDatabase() 方法就能够创建数据库了,数据库文件会存放在/data/data/<package name>/databases/目录下。 此时,重写的 onCreate() 方法也会得到执行,在这里去处理一些创建表的逻辑。

说了这么多,还是举例子实在。这里我们希望创建一个名为 BookStore.db 的数据库,然后在这个数据库中新建一张 book表,表中有 id(主键)、作者、价格、页数和书名等列。

首先,在你的项目中新建 MyDatabaseHelper类继承自 SQLiteOpenHelper,代码如下所示:

/**
 * 数据库帮助类
 * Created by KXwon on 2016/12/16.
 */

public class MyDatabaseHelper extends SQLiteOpenHelper {

    // Book表的建表语句
    private static final String CREATE_BOOK = "create table book("
            +"id integer primary key autoincrement,"
            +"author text,"
            +"price real,"
            +"pages integer,"
            +"name text)";

    private Context mContext;

    /**
     * 构造方法
     * @param context
     * @param name 数据库名
     * @param factory 允许我们在查询数据的时候返回一个自定义的 Cursor,一般都是传入 null
     * @param version 当前数据库的版本号,可用于对数据库进行升级操作
     */
    public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        mContext = context;
    }

    /**
     * 创建数据库
     * @param db
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        // 执行建表语句
        db.execSQL(CREATE_BOOK);
        ToastUtils.showShort("创建成功");
    }

    /**
     * 升级数据库
     * @param db
     * @param oldVersion
     * @param newVersion
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

然后修改布局 activity_sqlite.xml 中代码,添加个按钮来创建数据库:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="15dp"
    android:orientation="vertical" >

    <Button
        android:id="@+id/btn_create_database"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="创建数据库"/>

</LinearLayout>

最后修改 activity 中的代码:

public class SQLiteActivity extends AppCompatActivity {

    private MyDatabaseHelper dbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sqlite);

        // 构建MyDatabaseHelper对象,指定数据库名为"BookStore.db、版本号为1
        dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);

        Button btn_create_database = (Button) findViewById(R.id.btn_create_database);
        btn_create_database.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 创建或打开一个现有的数据库(已存在则打开,否则创建一个新的)
                dbHelper.getWritableDatabase();
            }
        });
    }
}

上述代码,当第一次点击按钮时,会检测到当前程序中并没有 BookStore.db 这个数据库,于是会创建该数据库并调用 MyDatabaseHelper中的 onCreate()方法,创建 book 表,然后弹出一个 Toast 提示创建成功。再次点击按钮时,会发现此时已存在 BookStore.db 数据库了,因此不会再创建一次。

运行代码,点击按钮,结果如下:

创建数据表成功

下面检查 book 表是否创建成功(木有兴趣可跳过)。

adb 是 Android SDK 中自带的一个调试工具,使用这个工具可以直接对连接在电脑上的手机或模拟器进行调试操作。它存放在 sdk 的 platform-tools 目录下,如果想要在命令行中使用这个工具,就要先把它的路径配置到环境变量里。

配置好环境变量后,就可以使用 adb 工具了。打开命令行界面,输入 adb shell,进入到设备的控制台,如下:

进入设备的控制台

然后使用 cd 命令进行到/data/data/com.wonderful.myfirstcode/databases/目录下,并使用 ls命令查看到该目录里的文件,如下:

查看数据库文件

此目录下出现了两个数据库文件,一个是我们创建的 BookStore.db ,而另一个 BookStore.db-journal 则是为了让数据库能够支持事务而产生的临时日志文件,通常情况下这个文件的大小都是 0 字节。

接下来借助 sqlite 命令来打开数据库,键入 sqlite3,后面加上数据库名即可,如下:

打开BookStore.db 数据库

这时已经打开了 BookStore.db 数据库,现在可以对这个数据库中的表进行管理了。 首先来看一下目前数据库中有哪些表,键入.table 命令,如下:

查看表

可以看到,此时数据库中有两张表,android_metadata 表是每个数据库中都会自动生成的,不用管它,而另外一张 book 表就是我们在 MyDatabaseHelper 中创建的了。这里还可以通过.schema 命令来查看它们的建表语句,如下:

查看建表语句

由此证明,BookStore.db 数据库和 book 表已经创建成功了。之后键入.exit 或.quit命令可以退出数据库的编辑,再键入 exit 命令就可以退出设备控制台了。

6.3.2 升级数据库

MyDatabaseHelper 中的onUpgrade() 方法是用于对数据库进行升级的,它在整个数据库的管理工作当中起着非常重要的作用。

目前项目中已经有一张 book 表用于存放书的各种详细数据,若我们想再添加一张 category 表用于记录书籍的分类该怎么做呢?

修改 MyDatabaseHelper 中的代码,如下所示:

/**
 * 数据库帮助类
 * Created by KXwon on 2016/12/16.
 */

public class MyDatabaseHelper extends SQLiteOpenHelper {

    // Book表的建表语句
    private static final String CREATE_BOOK = "create table book("
            +"id integer primary key autoincrement,"
            +"author text,"
            +"price real,"
            +"pages integer,"
            +"name text)";

    // Category表的建表语句
    private static final String CREATE_CATEGORY = "create table category("
            +"id integer primary key autoincrement,"
            +"category_name text,"
            +"category_code integer)";
    
    private Context mContext;

    /**
     * 构造方法
     * @param context
     * @param name 数据库名
     * @param factory 允许我们在查询数据的时候返回一个自定义的 Cursor,一般都是传入 null
     * @param version 当前数据库的版本号,可用于对数据库进行升级操作
     */
    public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
        super(context, name, factory, version);
        mContext = context;
    }

    /**
     * 创建数据库
     * @param db
     */
    @Override
    public void onCreate(SQLiteDatabase db) {
        // 执行建表语句
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_CATEGORY);
        ToastUtils.showShort("创建成功");
    }

    /**
     * 升级数据库
     * @param db
     * @param oldVersion
     * @param newVersion
     */
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // 若发现数据库中已存在 book 表或 category 表,将这两张表删除掉
        db.execSQL("drop table if exists book");
        db.execSQL("drop table if exists category");
        // 重新创建表
        onCreate(db);
    }
}

接着修改 activity 中的代码,在 SQLiteOpenHelper 的构造方法里接收的第四个参数传入一个比之前传入的版本号 1 大的数,就可以让 onUpgrade()方法得到执行了。如下所示:

public class SQLiteActivity extends AppCompatActivity {

    private MyDatabaseHelper dbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sqlite);

        // 构建MyDatabaseHelper对象,指定数据库名为"BookStore.db、版本号为1
        //dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);
        // 将数据库版本号指定为2
        dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 2);

        Button btn_create_database = (Button) findViewById(R.id.btn_create_database);
        btn_create_database.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 创建或打开一个现有的数据库(已存在则打开,否则创建一个新的)
                dbHelper.getWritableDatabase();
            }
        });
    }
}

现在重新运行程序,并点击按钮,这时就会再次弹出创建成功的提示。

为了验证一下 category 表是 不是已经创建成功了,我们在 adb shell 中打开 BookStore.db 数据库,然后键入.table 命令, 结果如下:

查看新增表

接着键入.schema 命令查看一下建表语句,如下:

查看新增建表语句

由此表明,category 表已创建成功了,同时也说明升级功能起作用了。

6.3.3 增删查改

接下来学习一下如何对表中的数据进行操作。对数据进行的操作无非四种,即 CRUD:添加(Create),查询(Retrieve),更新(Update),删除(Delete)。SQLiteOpenHelper 中的 getReadableDatabase() 或 getWritableDatabase() 方法都会返回一个 SQLiteDatabase对象,借助这个对象可以对数据进行 CRUD 操作。

首先修改下布局 activity_sqlite.xml 的代码如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="15dp"
    android:orientation="vertical" >

    <Button
        android:id="@+id/btn_create_database"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="创建数据库"/>

    <Button
        android:id="@+id/btn_add_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="添加数据"/>

    <Button
        android:id="@+id/btn_update_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="更新数据"/>

    <Button
        android:id="@+id/btn_delete_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="删除数据"/>

    <Button
        android:id="@+id/btn_query_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="查询数据"/>

</LinearLayout>

修改后是这样的:

修改后的布局

6.3.3.1 添加数据

SQLiteDatabase 中提供了一个 insert() 方法用于添加数据。它接收三个参数,第一个参数是表名;第二个参数用于在未指定添加数据的情况下给某些可为空的列自动赋值 NULL,一般直接传入 null 即可;第三个参数是一个 ContentValues 对象,它提供了一系列的 put() 方法重载,用于向 ContentValues 中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可。

修改 activity 中的代码,如下所示:

public class SQLiteActivity extends AppCompatActivity {

    private MyDatabaseHelper dbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sqlite);

        . . .

        // 添加数据
        Button btn_add_data = (Button) findViewById(R.id.btn_add_data);
        btn_add_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                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); //插入第二条数据
            }
        });
    }
}

运行程序,点击添加数据按钮,此时两条数据应该都已经添加成功了。为了证实一下,打开 BookStore.db 数据库,输入 SQL 查询语句 “select * from Book;”,结果如下:

查看添加的数据

上图中由于我不小心点了两次按钮,所以出现了四条数据,但可以看出,刚刚组装的两条数据,已经添加到 book 表中了。

6.3.3.2 更新数据

SQLiteDatabase 中提供了一个 update() 方法用于更新数据。它接收四个参数,第一个参数和 insert()方法一样,是表名;第二个参数是 ContentValues 对象,要把更新数据在这里组装进去。第三、第四个参数用于去约束更新某一行或某几行中的数据,不指定的话默认就是更新所有行。

修改 activity 中的代码,如下所示:

public class SQLiteActivity extends AppCompatActivity {

    private MyDatabaseHelper dbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sqlite);

        . . . 

        // 更新数据
        Button btn_update_data = (Button) findViewById(R.id.btn_update_data);
        btn_update_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                ContentValues values = new ContentValues();
                // 把名字为 The Da Vinci Code 的这本书的更新成666.66
                values.put("price",666.66);
                db.update("book",values, "name = ?", new String[]{"The Da Vinci Code"});
            }
        });
    }
}

运行程序,点击更新数据按钮后,再次输入查询语句查看表中的数据情况,结果如下:

查看更新后的数据

可以看到,The Da Vinci Code 这本书的价格已经被成功改为 666.66 了。

6.3.3.3 删除数据

SQLiteDatabase 中提供了一个 delete()方法用于删除数据。这个方法接收三个参数,第一 个参数仍然是表名;第二、第三个参数又是用于去约束删除某一 行或某几行的数据,不指定的话默认就是删除所有行。

修改 activity 中的代码,如下所示:

public class SQLiteActivity extends AppCompatActivity {

    private MyDatabaseHelper dbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sqlite);

        . . . 

        // 删除数据
        Button btn_delete_data = (Button) findViewById(R.id.btn_delete_data);
        btn_delete_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                // 删除页数超过500页的书
                db.delete("book", "pages > ?", new String[]{"500"});
            }
        });
    }
}

运行程序,点击删除数据按钮后,再次输入查询语句查看表中的数据情况,结果如下:

查看删除后的数据

其中 The Lost Symbol 这本书的页数超过了 500 页,被删除掉了。

6.3.3.4 查询数据

SQLiteDatabase 中提供了一个 query()方法用于查询数据。 这个方法的参数非常复杂,最短的一个方法重载也需要传入七个参数。

  • 第一个参数不用说,当然还是表名;
  • 第二个参数用于指定去查询哪几列,如果不指定则默认查询所有列;
  • 第三、第四个参数用于去约束查询某一行或某几行的数据,不指定则默认是查询所有行的数据;
  • 第五个参数用于指定需要去 group by 的列,不指定则表示不对查询结果进行 group by 操作;
  • 第六个参数用于对 group by 之后的数据进行进一步的过滤,不指定则表示不进行过滤;
  • 第七个参数用 于指定查询结果的排序方式,不指定则表示使用默认的排序方式。

更多详细的内容可以参考下表。其他几个 query()方法的重载其实也大同小异。

query() 方法参数内容

调用 query() 方法后会返回一个 Cursor 对象,查询到的所有数据都将从这个对象中取出。

修改 activity 中的代码,如下所示:

public class SQLiteActivity extends AppCompatActivity {

    private MyDatabaseHelper dbHelper;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_sqlite);

        . . . 

        // 查询数据
        Button btn_query_data = (Button) findViewById(R.id.btn_query_data);
        btn_query_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                SQLiteDatabase db = dbHelper.getWritableDatabase();
                // 查询 book 表中所有数据
                Cursor cursor = db.query("book", null,null,null,null,null,null,null);
                if (cursor.moveToFirst()){
                    do {
                        // 遍历 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("SQLiteActivity_query", "book name is: " + name);
                        Log.d("SQLiteActivity_query", "book author is: " + author);
                        Log.d("SQLiteActivity_query", "book pages are: " + pages);
                        Log.d("SQLiteActivity_query", "book price is: " + price);
                    }while (cursor.moveToNext());
                }
                cursor.close();
            }
        });
    }
}

运行程序,点击查询数据按钮后,查看Log的打印内容,结果如下:

打印查询的数据

可以看到,这里已经将 book 表中剩余两条数据成功地读取出来了。

6.3.4 使用 SQL 操作数据库

下面来简略演示一下,如何直接使用 SQL 来完成前面几小节中学过的 CRUD 操作。

  • 添加数据的方法如下:
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" });
  • 更新数据的方法如下:
db.execSQL("update Book set price = ? where name = ?", new String[] { "666.66", "The Da Vinci Code" });
  • 删除数据的方法如下:
db.execSQL("delete from Book where pages > ?", new String[] { "500" });
  • 查询数据的方法如下:
db.rawQuery("select * from Book", null);

6.4 使用 LitePal 操作数据库

LitePal 是一款开源的 Android 数据库框架,它采用了对象关系映射(ORM)的模式,并将我们平时开发最常用到的一些数据库功能进行了封装,使得不用编写一行 SQL 语句就可以完成各种建表和增删查改的操作。LitePal 的项目主页上也有详细的使用文档,地址是:https://github.com/LitePalFramework/LitePal

6.4.1 配置 LitePal

使用 LitePal 的第一步,在 app/build.gradle 文件中引入 LitePal 的最新版本:

compile 'org.litepal.android:core:1.4.1'

项目中引入 LitePal 成功后,需要配置 litepal.xml 文件。右击 app/src/main 目录→New→Directory,创建一个 assets 目录,然后在 assets 目录下再新建一个 litepal.xml 文件,接着编辑 litepal.xml 文件中的内容,如下所示:

<?xml version="1.0" encoding="utf-8"?>
<litepal>
    <!-- 指定数据库名 -->
    <dbname value="MyBookStore"></dbname>

    <!-- 指定数据库版本号 -->
    <version value="1"></version>

    <!-- 指定所有的映射模型 -->
    <list>

    </list>
</litepal>

最后还要在 Application 中调用 LitePal 的初始化方法:

/**
 * 全局
 * Created by KXwon on 2016/12/9.
 */
public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        // 调用 LitePal 的初始化方法
        LitePal.initialize(this);
    }
}

当然别忘了在 AndroidManifest.xml 中配置 Application:

<application
    android:name=".MyApplication"
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    . . .
 </application>

现在 LitePal 的配置工作完成了,下面正式开始使用它吧。

6.4.2 创建和升级数据库

介绍时说过 LitePal 采取的是对象关系映射(ORM)的模式,即把面向面向对象的语言和面向关系的数据库之间建立一种映射关系。对象关系映射模式可以用面向对象的思维来操作数据库,这样就不用和 SQL 语句打交道了。

下面用 LitePal 实现和上面几小节中同样的功能,为了方便测试,突出对比,把数据库命名为 MyBookStore 。修改布局 activity_lite_pal.xm 中的代码和上面例子的布局类似:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="15dp"
    android:orientation="vertical" >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="10dp"
        android:text="LitePal 操作数据库"/>

    <Button
        android:id="@+id/btn_create_database"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="创建数据库"/>

    <Button
        android:id="@+id/btn_add_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="添加数据"/>

    <Button
        android:id="@+id/btn_update_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="更新数据"/>

    <Button
        android:id="@+id/btn_delete_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="删除数据"/>

    <Button
        android:id="@+id/btn_query_data"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="查询数据"/>

</LinearLayout>

长这样的:

LitePal操作数据库布局

首先,为了创建一张 Book 表,先定义一个 Book 类,如下:

/**
 * Book 实体类
 * Created by KXwon on 2016/12/16.
 */

public class Book {
    private int id;
    private String author;
    private double price;
    private int pages;
    private String name;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    public int getPages() {
        return pages;
    }

    public void setPages(int pages) {
        this.pages = pages;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

接下来将 Book 类添加到映射关系列表中,修改 litepal.xml 中的代码如下:

<litepal>
    <!-- 指定数据库名 -->
    <dbname value="MyBookStore"></dbname>

    <!-- 指定数据库版本号 -->
    <version value="1"></version>

    <!-- 指定所有的映射模型 -->
    <list>
        <mapping class="com.wonderful.myfirstcode.chapter6.litepal_persistence.Book"></mapping>
    </list>
</litepal>

这里使用<mapping>标签来声明我们要配置的映射模型类,注意一定要使用完整的类名。不管有多少模型类型需要映射,都使用同样的方式配置在<list>标签下即可。

这样就已经把所有工作完成了,现只要进行任意一次数据库的操作,MyBookStore.db 数据库就会自动创建出来。修改 activity 中的代码如下:

public class LitePalActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_lite_pal);

        // 创建数据库
        Button btn_create_database = (Button) findViewById(R.id.btn_create_database);
        btn_create_database.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Connector.getDatabase();
            }
        });
    }
}

其中调用 Connector.getDatabase() 方法就是一次最简单的数据库操作。运行程序并点击一下创建数据库按钮数据库就自动创建完成了,通过adb shell 查看数据库创建情况,如下:

查看数据库文件

可以看到,数据库文件已经创建成功了(前面两张表是上面例子创建的)。

接下来使用 sqlite3 命令打开 MyBookStore.db 文件,然后用 .schema 命令来查看见表语句,如下:

查看建表语句

可以看到,这有3张建表语句,其中 android_metadata 不用管,table_schema 表是 LitePal 内部使用的,也可忽视,book 表就是根据定义的 Book 类以及类中的字段生成的。

前面使用 SQLiteOpenHelper 升级数据库时,会把之前的表 drop 掉,造成数据丢失(当然也可已通过复杂的逻辑来避免),而使用 LitePal 这些就不再是问题了。使用 LitePal 升级数据库很简单,不用思考任何的逻辑,只需要改你想改的任何内容,然后版本加1就行了。

比如想要向 Book 表中添加一个press(出版社)列,直接在 Book 类中添加一个 press 字段即可,如下所示:

public class Book {
    . . .
    private String press;

    public String getPress() {
        return press;
    }

    public void setPress(String press) {
        this.press = press;
    }
    . . .
}

与此同时,若还想在添加一张 Category 表,那么只需要新建一个 Category 类就可以了,代码如下:

/**
 * Category 实体类
 * Created by KXwon on 2016/12/16.
 */

public class Category {
    private int id;
    private String categoryName;
    private int categoryCode;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getCategoryName() {
        return categoryName;
    }

    public void setCategoryName(String categoryName) {
        this.categoryName = categoryName;
    }

    public int getCategoryCode() {
        return categoryCode;
    }

    public void setCategoryCode(int categoryCode) {
        this.categoryCode = categoryCode;
    }
}

改完了想改的东西,修改下 litepal.xml 中的代码如下:

<litepal>
    <!-- 指定数据库名 -->
    <dbname value="MyBookStore"></dbname>

    <!-- 指定数据库版本号 -->
    <version value="2"></version>

    <!-- 指定所有的映射模型 -->
    <list>
        <mapping class="com.wonderful.myfirstcode.chapter6.litepal_persistence.Book"></mapping>
        <mapping class="com.wonderful.myfirstcode.chapter6.litepal_persistence.Category"></mapping>
    </list>
</litepal>

将版本号加1,并把新的模型类添加到映射列表中。重新运行下程序,然后点击创建数据库按钮,再查看一下新的建表语句,如下:

升级数据库后的建表语句

这样数据库修改或升级就完成了,并且保留了之前表中的所有数据。

6.4.3 使用 LitePal 增删查改

6.4.3.1 添加数据

使用 LitePal 添加数据,只需要创建出模型类的实例,再将所有要存储的数据设置好,最后调用一下 save() 方法就可以了。

LitePal 进行表管理操作时不需要模型类有任何继承结构,但进行 CRUD 操作时必须继承 DataSupport 类才行,因此需要修改 Book 类的代码如下:

public class Book extends DataSupport{
    . . .
}

接着向 Book 表中添加数据,修改 activity 中的代码如下:

public class LitePalActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_lite_pal);

        . . .
        
        // 添加数据
        Button btn_add_data = (Button) findViewById(R.id.btn_add_data);
        btn_add_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 创建 Book 实例
                Book book = new Book();
                // 设置数据
                book.setName("第一行代码");
                book.setAuthor("郭霖");
                book.setPages(570);
                book.setPrice(79.00);
                book.setPress("人民邮电出版社");
                // 添加数据
                book.save();
            }
        });
    }
}

现重新运行程序,点击 添加数据 按钮,此时数据就已经添加了。同样打开 MyBookStore.db,输入 select*from Book;结果如下:

查看添加的数据

6.4.3.2 更新数据

使用 LitePal 更新数据,它的接口比较多,这里只介绍几种常用的。

  • 方式1
      对已存储的对象重新设值,然后调用 save() 方法即可。修改 activity 中的代码如下:
public class LitePalActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_lite_pal);

        . . .
        
       // 更新数据
        Button btn_update_data = (Button) findViewById(R.id.btn_update_data);
        btn_update_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Book book = new Book();
                book.setName("第一行代码");
                book.setAuthor("郭霖");
                book.setPages(570);
                book.setPrice(79.00);
                book.setPress("人民邮电出版社");
                book.save();
                // 修改书的名称和价格
                book.setName("第二行代码");
                book.setPrice(88.88);
                book.save();
            }
        });
    }
}
  • 方式2
      对需要更新的数据进行设值,最后调用 updateAll() 方法去执行更新操作。修改 activity 中的代码如下:
public class LitePalActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_lite_pal);

        . . .
        
        // 更新数据
        Button btn_update_data = (Button) findViewById(R.id.btn_update_data);
        btn_update_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Book book = new Book();
                book.setName("第二行代码");
                book.setPrice(88.88);
                book.updateAll("name = ? and author = ?","第一行代码","郭霖");
            }
        });
    }
}

现重新运行程序,点击 更新数据 按钮,查询表中的数据情况,结果如下:

查看更新后的数据

注意事项:在使用 updateAll() 方法时,若你把一个字段的值更新为默认值时,是不可以使用上面的方式来 set 数据的。对于想要将数据更新为默认值,LitePal 统一提供了一个 setToDefault() 方法,然后传入相应的列名就可以了。如将所有书的页数都更新为默认值 0,直接调用 book.setPages(0) 是不可以的,但可以这样写:

Book book = new Book();
book.setToDefault("pages");
book.updateAll();

6.4.3.3 删除数据

使用 LitePal 删除数据的方式主要有两种:第一种就是直接调用已存储的对象的 delete() 方法就可以了;第二种是调用 DataSupport.deleteAll() 方法来删除。

这里演示下第二种方式,修改 activity 中的代码如下:

public class LitePalActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_lite_pal);

        . . .
        
        // 删除数据
        Button btn_delete_data = (Button) findViewById(R.id.btn_delete_data);
        btn_delete_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 删除 Book 表中价格低于15的书
                DataSupport.deleteAll(Book.class, "price < ?", "15");
            }
        });
    }
}

另外,deleteAll() 方法若不指定约束条件,就意味着删除表中的所有数据。

6.4.3.4 查询数据

使用 LitePal 查询数据一点都不复杂。

比如想要实现上节中的查询 Book 表,修改 activity 的代码如下:

public class LitePalActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_lite_pal);

        . . .
        
        // 查询数据
        Button btn_query_data = (Button) findViewById(R.id.btn_query_data);
        btn_query_data.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                // 查询 Book 表
                List<Book> books = DataSupport.findAll(Book.class);
                // 打印 Book 表中的信息
                for (Book book: books){
                    Log.d("LitePalActivity_query", "book name is: " + book.getName());
                    Log.d("LitePalActivity_query", "book author is: " + book.getAuthor());
                    Log.d("LitePalActivity_query", "book pages are: " + book.getPages());
                    Log.d("LitePalActivity_query", "book price is: " + book.getPrice());
                    Log.d("LitePalActivity_query", "book press is: " + book.getPress());
                }
            }
        });
    }
}

没错,就是这么简单。现重新运行程序,点击 查询数据 按钮,查看Log信息如下:

打印查询的数据

除了 findAll() 方法之外,LitePal 还提供了其他非常有用的 API。比如:

  • 查询 Book 表中的第一条数据:
Book firstBook = DataSupport.findFirst(Book.class);
  • 查询 Book 表中的最后一条数据:
Book LastBook = DataSupport.findLast(Book.class);

还可以通过连缀查询来制定更多的查询功能:

  • select() 方法用于指定查询哪几列的数据。
    如查询 name 和 author 这两列数据:
List<Book> books = DataSupport.select("name","author").find(Book.class);
  • where() 方法用于指定查询的约束条件。
    如查询页数大450的数据:
List<Book> books = DataSupport.where("pages > ?","450").find(Book.class);
  • order() 方法用于指定结果的排序方式。
    如查询结果按书价从高到低排列:
// 其中 desc 表示降序,asc 或者不写表示升序
List<Book> books = DataSupport.order("price desc").find(Book.class);
  • limit() 方法用于指定查询结果的数量。
    如查询表中前3条数据:
List<Book> books = DataSupport.limit(3).find(Book.class);
  • offset() 方法用于指定查询结果的偏移量。
    如查询表中的第2、3、4条数据:
List<Book> books = DataSupport.limit(3).offset(1).find(Book.class);

当然你也可以对这5个方法进行任意的连缀组合,如:

    // 查询 Book 表中第11-20跳满足页数大于450这个条件的 name、author 和 pages 这3列数据,
    // 并将查询结果按照页数升序排序
    List<Book> books = DataSupport.select("name","author","pages")
            .where("pages > ?","450")
            .order("pages")
            .limit(10)
            .offset(10)
            .find(Book.class);

LitePal 也可以支持使用原生的 SQL 语句来进行查询:

Cursor c = DataSupport.findBySQL("select * from Book where pages > ? and price < ?","450","20");

关于 LitePal 就介绍到这。

有木有感觉 LitePal 很强大?

哎呦我去,这章终于结束了,虽然不难,但介绍起来真繁琐。明天又到周末了,可以愉快得玩耍了!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,100评论 18 139
  • 第 6 章 数据存储全方案,详解持久化技术 一:文件存储 将数据存储到文件中(使用 Java 流的方式将数据写入到...
    jesse0阅读 349评论 0 2
  • 图为文章结构思维导图 生活不是用来妥协的,明白要趁早,否则就一切成为零。 一个人越善良,带人底线应该...
    kly在路上阅读 2,202评论 6 49