本篇文章主要介紹以下幾個知識點:
- SQLite 數(shù)據(jù)庫存儲;
- 開源數(shù)據(jù)庫框架 LitePal。

6.3 SQLite 數(shù)據(jù)庫存儲
前面介紹的兩種存儲方式很難應付得了需要存儲大量復雜的關(guān)系型數(shù)據(jù)的時候,而使用數(shù)據(jù)庫就可以做到。
SQLite 是一款輕量級的關(guān)系型數(shù)據(jù)庫,它的運算速度非常快, 占用資源很少,通常只需幾百 K 的內(nèi)存就足夠了,因而特別適合在移動設備上使用。SQLite 不僅支持標準的 SQL 語法,還遵循了數(shù)據(jù)庫的 ACID 事務,只要你以前用過其他的關(guān)系型數(shù)據(jù)庫,就可以很快地上手 SQLite。
6.3.1 創(chuàng)建數(shù)據(jù)庫
Android 為了讓我們更加方便地管理數(shù)據(jù)庫,專門提供了一個 SQLiteOpenHelper 幫助類,借助這個類可以非常簡單地對數(shù)據(jù)庫進行創(chuàng)建和升級。
SQLiteOpenHelper 是一個抽象類,意味著若要使用它的話, 就需要創(chuàng)建一個自己的幫助類去繼承它并且重寫它的兩個抽象方法,即 onCreate() 和 onUpgrade(),去實現(xiàn)創(chuàng)建、升級數(shù)據(jù)庫的邏輯。
SQLiteOpenHelper 中有兩個重要的實例方法 , getReadableDatabase() 和 getWritableDatabase()。它們都可以創(chuàng)建或打開一個現(xiàn)有的數(shù)據(jù)庫(若已存在則打開,否則創(chuàng)建一個新的),并返回一個可對數(shù)據(jù)庫進行讀寫操作的對象。不同的是,當數(shù)據(jù)庫不可寫入的時候(如磁盤空間已滿)getReadableDatabase()方法返回的對象將以只讀的方式去打開數(shù)據(jù)庫,而 getWritableDatabase()方法則將出現(xiàn)異常。
SQLiteOpenHelper 中有兩個構(gòu)造方法可供重寫,一般使用參數(shù)少一點的那個構(gòu)造方法即可。構(gòu)建出 SQLiteOpenHelper 的實例之后,再調(diào)用它的 getReadableDatabase() 或 getWritableDatabase() 方法就能夠創(chuàng)建數(shù)據(jù)庫了,數(shù)據(jù)庫文件會存放在/data/data/<package name>/databases/目錄下。 此時,重寫的 onCreate() 方法也會得到執(zhí)行,在這里去處理一些創(chuàng)建表的邏輯。
說了這么多,還是舉例子實在。這里我們希望創(chuàng)建一個名為 BookStore.db 的數(shù)據(jù)庫,然后在這個數(shù)據(jù)庫中新建一張 book表,表中有 id(主鍵)、作者、價格、頁數(shù)和書名等列。
首先,在你的項目中新建 MyDatabaseHelper類繼承自 SQLiteOpenHelper,代碼如下所示:
/**
* 數(shù)據(jù)庫幫助類
* 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;
/**
* 構(gòu)造方法
* @param context
* @param name 數(shù)據(jù)庫名
* @param factory 允許我們在查詢數(shù)據(jù)的時候返回一個自定義的 Cursor,一般都是傳入 null
* @param version 當前數(shù)據(jù)庫的版本號,可用于對數(shù)據(jù)庫進行升級操作
*/
public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
/**
* 創(chuàng)建數(shù)據(jù)庫
* @param db
*/
@Override
public void onCreate(SQLiteDatabase db) {
// 執(zhí)行建表語句
db.execSQL(CREATE_BOOK);
ToastUtils.showShort("創(chuàng)建成功");
}
/**
* 升級數(shù)據(jù)庫
* @param db
* @param oldVersion
* @param newVersion
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
然后修改布局 activity_sqlite.xml 中代碼,添加個按鈕來創(chuàng)建數(shù)據(jù)庫:
<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="創(chuàng)建數(shù)據(jù)庫"/>
</LinearLayout>
最后修改 activity 中的代碼:
public class SQLiteActivity extends AppCompatActivity {
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sqlite);
// 構(gòu)建MyDatabaseHelper對象,指定數(shù)據(jù)庫名為"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) {
// 創(chuàng)建或打開一個現(xiàn)有的數(shù)據(jù)庫(已存在則打開,否則創(chuàng)建一個新的)
dbHelper.getWritableDatabase();
}
});
}
}
上述代碼,當?shù)谝淮吸c擊按鈕時,會檢測到當前程序中并沒有 BookStore.db 這個數(shù)據(jù)庫,于是會創(chuàng)建該數(shù)據(jù)庫并調(diào)用 MyDatabaseHelper中的 onCreate()方法,創(chuàng)建 book 表,然后彈出一個 Toast 提示創(chuàng)建成功。再次點擊按鈕時,會發(fā)現(xiàn)此時已存在 BookStore.db 數(shù)據(jù)庫了,因此不會再創(chuàng)建一次。
運行代碼,點擊按鈕,結(jié)果如下:

下面檢查 book 表是否創(chuàng)建成功(木有興趣可跳過)。
adb 是 Android SDK 中自帶的一個調(diào)試工具,使用這個工具可以直接對連接在電腦上的手機或模擬器進行調(diào)試操作。它存放在 sdk 的 platform-tools 目錄下,如果想要在命令行中使用這個工具,就要先把它的路徑配置到環(huán)境變量里。
配置好環(huán)境變量后,就可以使用 adb 工具了。打開命令行界面,輸入 adb shell,進入到設備的控制臺,如下:
然后使用 cd 命令進行到/data/data/com.wonderful.myfirstcode/databases/目錄下,并使用 ls命令查看到該目錄里的文件,如下:
此目錄下出現(xiàn)了兩個數(shù)據(jù)庫文件,一個是我們創(chuàng)建的 BookStore.db ,而另一個 BookStore.db-journal 則是為了讓數(shù)據(jù)庫能夠支持事務而產(chǎn)生的臨時日志文件,通常情況下這個文件的大小都是 0 字節(jié)。
接下來借助 sqlite 命令來打開數(shù)據(jù)庫,鍵入 sqlite3,后面加上數(shù)據(jù)庫名即可,如下:
這時已經(jīng)打開了 BookStore.db 數(shù)據(jù)庫,現(xiàn)在可以對這個數(shù)據(jù)庫中的表進行管理了。 首先來看一下目前數(shù)據(jù)庫中有哪些表,鍵入.table 命令,如下:
可以看到,此時數(shù)據(jù)庫中有兩張表,android_metadata 表是每個數(shù)據(jù)庫中都會自動生成的,不用管它,而另外一張 book 表就是我們在 MyDatabaseHelper 中創(chuàng)建的了。這里還可以通過.schema 命令來查看它們的建表語句,如下:
由此證明,BookStore.db 數(shù)據(jù)庫和 book 表已經(jīng)創(chuàng)建成功了。之后鍵入.exit 或.quit命令可以退出數(shù)據(jù)庫的編輯,再鍵入 exit 命令就可以退出設備控制臺了。
6.3.2 升級數(shù)據(jù)庫
MyDatabaseHelper 中的onUpgrade() 方法是用于對數(shù)據(jù)庫進行升級的,它在整個數(shù)據(jù)庫的管理工作當中起著非常重要的作用。
目前項目中已經(jīng)有一張 book 表用于存放書的各種詳細數(shù)據(jù),若我們想再添加一張 category 表用于記錄書籍的分類該怎么做呢?
修改 MyDatabaseHelper 中的代碼,如下所示:
/**
* 數(shù)據(jù)庫幫助類
* 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;
/**
* 構(gòu)造方法
* @param context
* @param name 數(shù)據(jù)庫名
* @param factory 允許我們在查詢數(shù)據(jù)的時候返回一個自定義的 Cursor,一般都是傳入 null
* @param version 當前數(shù)據(jù)庫的版本號,可用于對數(shù)據(jù)庫進行升級操作
*/
public MyDatabaseHelper(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
mContext = context;
}
/**
* 創(chuàng)建數(shù)據(jù)庫
* @param db
*/
@Override
public void onCreate(SQLiteDatabase db) {
// 執(zhí)行建表語句
db.execSQL(CREATE_BOOK);
db.execSQL(CREATE_CATEGORY);
ToastUtils.showShort("創(chuàng)建成功");
}
/**
* 升級數(shù)據(jù)庫
* @param db
* @param oldVersion
* @param newVersion
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// 若發(fā)現(xiàn)數(shù)據(jù)庫中已存在 book 表或 category 表,將這兩張表刪除掉
db.execSQL("drop table if exists book");
db.execSQL("drop table if exists category");
// 重新創(chuàng)建表
onCreate(db);
}
}
接著修改 activity 中的代碼,在 SQLiteOpenHelper 的構(gòu)造方法里接收的第四個參數(shù)傳入一個比之前傳入的版本號 1 大的數(shù),就可以讓 onUpgrade()方法得到執(zhí)行了。如下所示:
public class SQLiteActivity extends AppCompatActivity {
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sqlite);
// 構(gòu)建MyDatabaseHelper對象,指定數(shù)據(jù)庫名為"BookStore.db、版本號為1
//dbHelper = new MyDatabaseHelper(this, "BookStore.db", null, 1);
// 將數(shù)據(jù)庫版本號指定為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) {
// 創(chuàng)建或打開一個現(xiàn)有的數(shù)據(jù)庫(已存在則打開,否則創(chuàng)建一個新的)
dbHelper.getWritableDatabase();
}
});
}
}
現(xiàn)在重新運行程序,并點擊按鈕,這時就會再次彈出創(chuàng)建成功的提示。
為了驗證一下 category 表是 不是已經(jīng)創(chuàng)建成功了,我們在 adb shell 中打開 BookStore.db 數(shù)據(jù)庫,然后鍵入.table 命令, 結(jié)果如下:
接著鍵入.schema 命令查看一下建表語句,如下:
由此表明,category 表已創(chuàng)建成功了,同時也說明升級功能起作用了。
6.3.3 增刪查改
接下來學習一下如何對表中的數(shù)據(jù)進行操作。對數(shù)據(jù)進行的操作無非四種,即 CRUD:添加(Create),查詢(Retrieve),更新(Update),刪除(Delete)。SQLiteOpenHelper 中的 getReadableDatabase() 或 getWritableDatabase() 方法都會返回一個 SQLiteDatabase對象,借助這個對象可以對數(shù)據(jù)進行 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="創(chuàng)建數(shù)據(jù)庫"/>
<Button
android:id="@+id/btn_add_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="添加數(shù)據(jù)"/>
<Button
android:id="@+id/btn_update_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="更新數(shù)據(jù)"/>
<Button
android:id="@+id/btn_delete_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="刪除數(shù)據(jù)"/>
<Button
android:id="@+id/btn_query_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查詢數(shù)據(jù)"/>
</LinearLayout>
修改后是這樣的:

6.3.3.1 添加數(shù)據(jù)
SQLiteDatabase 中提供了一個 insert() 方法用于添加數(shù)據(jù)。它接收三個參數(shù),第一個參數(shù)是表名;第二個參數(shù)用于在未指定添加數(shù)據(jù)的情況下給某些可為空的列自動賦值 NULL,一般直接傳入 null 即可;第三個參數(shù)是一個 ContentValues 對象,它提供了一系列的 put() 方法重載,用于向 ContentValues 中添加數(shù)據(jù),只需要將表中的每個列名以及相應的待添加數(shù)據(jù)傳入即可。
修改 activity 中的代碼,如下所示:
public class SQLiteActivity extends AppCompatActivity {
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sqlite);
. . .
// 添加數(shù)據(jù)
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();
// 開始組裝第一條數(shù)據(jù)
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); //插入第一條數(shù)據(jù)
values.clear();
// 開始組裝第二條數(shù)據(jù)
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); //插入第二條數(shù)據(jù)
}
});
}
}
運行程序,點擊添加數(shù)據(jù)按鈕,此時兩條數(shù)據(jù)應該都已經(jīng)添加成功了。為了證實一下,打開 BookStore.db 數(shù)據(jù)庫,輸入 SQL 查詢語句 “select * from Book;”,結(jié)果如下:
上圖中由于我不小心點了兩次按鈕,所以出現(xiàn)了四條數(shù)據(jù),但可以看出,剛剛組裝的兩條數(shù)據(jù),已經(jīng)添加到 book 表中了。
6.3.3.2 更新數(shù)據(jù)
SQLiteDatabase 中提供了一個 update() 方法用于更新數(shù)據(jù)。它接收四個參數(shù),第一個參數(shù)和 insert()方法一樣,是表名;第二個參數(shù)是 ContentValues 對象,要把更新數(shù)據(jù)在這里組裝進去。第三、第四個參數(shù)用于去約束更新某一行或某幾行中的數(shù)據(jù),不指定的話默認就是更新所有行。
修改 activity 中的代碼,如下所示:
public class SQLiteActivity extends AppCompatActivity {
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sqlite);
. . .
// 更新數(shù)據(jù)
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"});
}
});
}
}
運行程序,點擊更新數(shù)據(jù)按鈕后,再次輸入查詢語句查看表中的數(shù)據(jù)情況,結(jié)果如下:
可以看到,The Da Vinci Code 這本書的價格已經(jīng)被成功改為 666.66 了。
6.3.3.3 刪除數(shù)據(jù)
SQLiteDatabase 中提供了一個 delete()方法用于刪除數(shù)據(jù)。這個方法接收三個參數(shù),第一 個參數(shù)仍然是表名;第二、第三個參數(shù)又是用于去約束刪除某一 行或某幾行的數(shù)據(jù),不指定的話默認就是刪除所有行。
修改 activity 中的代碼,如下所示:
public class SQLiteActivity extends AppCompatActivity {
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sqlite);
. . .
// 刪除數(shù)據(jù)
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();
// 刪除頁數(shù)超過500頁的書
db.delete("book", "pages > ?", new String[]{"500"});
}
});
}
}
運行程序,點擊刪除數(shù)據(jù)按鈕后,再次輸入查詢語句查看表中的數(shù)據(jù)情況,結(jié)果如下:
其中 The Lost Symbol 這本書的頁數(shù)超過了 500 頁,被刪除掉了。
6.3.3.4 查詢數(shù)據(jù)
SQLiteDatabase 中提供了一個 query()方法用于查詢數(shù)據(jù)。 這個方法的參數(shù)非常復雜,最短的一個方法重載也需要傳入七個參數(shù)。
- 第一個參數(shù)不用說,當然還是表名;
- 第二個參數(shù)用于指定去查詢哪幾列,如果不指定則默認查詢所有列;
- 第三、第四個參數(shù)用于去約束查詢某一行或某幾行的數(shù)據(jù),不指定則默認是查詢所有行的數(shù)據(jù);
- 第五個參數(shù)用于指定需要去 group by 的列,不指定則表示不對查詢結(jié)果進行 group by 操作;
- 第六個參數(shù)用于對 group by 之后的數(shù)據(jù)進行進一步的過濾,不指定則表示不進行過濾;
- 第七個參數(shù)用 于指定查詢結(jié)果的排序方式,不指定則表示使用默認的排序方式。
更多詳細的內(nèi)容可以參考下表。其他幾個 query()方法的重載其實也大同小異。
調(diào)用 query() 方法后會返回一個 Cursor 對象,查詢到的所有數(shù)據(jù)都將從這個對象中取出。
修改 activity 中的代碼,如下所示:
public class SQLiteActivity extends AppCompatActivity {
private MyDatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_sqlite);
. . .
// 查詢數(shù)據(jù)
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 表中所有數(shù)據(jù)
Cursor cursor = db.query("book", null,null,null,null,null,null,null);
if (cursor.moveToFirst()){
do {
// 遍歷 Cursor 對象,取出數(shù)據(jù)并打印
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();
}
});
}
}
運行程序,點擊查詢數(shù)據(jù)按鈕后,查看Log的打印內(nèi)容,結(jié)果如下:
可以看到,這里已經(jīng)將 book 表中剩余兩條數(shù)據(jù)成功地讀取出來了。
6.3.4 使用 SQL 操作數(shù)據(jù)庫
下面來簡略演示一下,如何直接使用 SQL 來完成前面幾小節(jié)中學過的 CRUD 操作。
- 添加數(shù)據(jù)的方法如下:
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" });
- 更新數(shù)據(jù)的方法如下:
db.execSQL("update Book set price = ? where name = ?", new String[] { "666.66", "The Da Vinci Code" });
- 刪除數(shù)據(jù)的方法如下:
db.execSQL("delete from Book where pages > ?", new String[] { "500" });
- 查詢數(shù)據(jù)的方法如下:
db.rawQuery("select * from Book", null);
6.4 使用 LitePal 操作數(shù)據(jù)庫
LitePal 是一款開源的 Android 數(shù)據(jù)庫框架,它采用了對象關(guān)系映射(ORM)的模式,并將我們平時開發(fā)最常用到的一些數(shù)據(jù)庫功能進行了封裝,使得不用編寫一行 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,創(chuàng)建一個 assets 目錄,然后在 assets 目錄下再新建一個 litepal.xml 文件,接著編輯 litepal.xml 文件中的內(nèi)容,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<litepal>
<!-- 指定數(shù)據(jù)庫名 -->
<dbname value="MyBookStore"></dbname>
<!-- 指定數(shù)據(jù)庫版本號 -->
<version value="1"></version>
<!-- 指定所有的映射模型 -->
<list>
</list>
</litepal>
最后還要在 Application 中調(diào)用 LitePal 的初始化方法:
/**
* 全局
* Created by KXwon on 2016/12/9.
*/
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 調(diào)用 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>
現(xiàn)在 LitePal 的配置工作完成了,下面正式開始使用它吧。
6.4.2 創(chuàng)建和升級數(shù)據(jù)庫
介紹時說過 LitePal 采取的是對象關(guān)系映射(ORM)的模式,即把面向面向?qū)ο蟮恼Z言和面向關(guān)系的數(shù)據(jù)庫之間建立一種映射關(guān)系。對象關(guān)系映射模式可以用面向?qū)ο蟮乃季S來操作數(shù)據(jù)庫,這樣就不用和 SQL 語句打交道了。
下面用 LitePal 實現(xiàn)和上面幾小節(jié)中同樣的功能,為了方便測試,突出對比,把數(shù)據(jù)庫命名為 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 操作數(shù)據(jù)庫"/>
<Button
android:id="@+id/btn_create_database"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="創(chuàng)建數(shù)據(jù)庫"/>
<Button
android:id="@+id/btn_add_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="添加數(shù)據(jù)"/>
<Button
android:id="@+id/btn_update_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="更新數(shù)據(jù)"/>
<Button
android:id="@+id/btn_delete_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="刪除數(shù)據(jù)"/>
<Button
android:id="@+id/btn_query_data"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="查詢數(shù)據(jù)"/>
</LinearLayout>
長這樣的:

首先,為了創(chuàng)建一張 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 類添加到映射關(guān)系列表中,修改 litepal.xml 中的代碼如下:
<litepal>
<!-- 指定數(shù)據(jù)庫名 -->
<dbname value="MyBookStore"></dbname>
<!-- 指定數(shù)據(jù)庫版本號 -->
<version value="1"></version>
<!-- 指定所有的映射模型 -->
<list>
<mapping class="com.wonderful.myfirstcode.chapter6.litepal_persistence.Book"></mapping>
</list>
</litepal>
這里使用<mapping>標簽來聲明我們要配置的映射模型類,注意一定要使用完整的類名。不管有多少模型類型需要映射,都使用同樣的方式配置在<list>標簽下即可。
這樣就已經(jīng)把所有工作完成了,現(xiàn)只要進行任意一次數(shù)據(jù)庫的操作,MyBookStore.db 數(shù)據(jù)庫就會自動創(chuàng)建出來。修改 activity 中的代碼如下:
public class LitePalActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lite_pal);
// 創(chuàng)建數(shù)據(jù)庫
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();
}
});
}
}
其中調(diào)用 Connector.getDatabase() 方法就是一次最簡單的數(shù)據(jù)庫操作。運行程序并點擊一下創(chuàng)建數(shù)據(jù)庫按鈕數(shù)據(jù)庫就自動創(chuàng)建完成了,通過adb shell 查看數(shù)據(jù)庫創(chuàng)建情況,如下:
可以看到,數(shù)據(jù)庫文件已經(jīng)創(chuàng)建成功了(前面兩張表是上面例子創(chuàng)建的)。
接下來使用 sqlite3 命令打開 MyBookStore.db 文件,然后用 .schema 命令來查看見表語句,如下:
可以看到,這有3張建表語句,其中 android_metadata 不用管,table_schema 表是 LitePal 內(nèi)部使用的,也可忽視,book 表就是根據(jù)定義的 Book 類以及類中的字段生成的。
前面使用 SQLiteOpenHelper 升級數(shù)據(jù)庫時,會把之前的表 drop 掉,造成數(shù)據(jù)丟失(當然也可已通過復雜的邏輯來避免),而使用 LitePal 這些就不再是問題了。使用 LitePal 升級數(shù)據(jù)庫很簡單,不用思考任何的邏輯,只需要改你想改的任何內(nèi)容,然后版本加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>
<!-- 指定數(shù)據(jù)庫名 -->
<dbname value="MyBookStore"></dbname>
<!-- 指定數(shù)據(jù)庫版本號 -->
<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,并把新的模型類添加到映射列表中。重新運行下程序,然后點擊創(chuàng)建數(shù)據(jù)庫按鈕,再查看一下新的建表語句,如下:
這樣數(shù)據(jù)庫修改或升級就完成了,并且保留了之前表中的所有數(shù)據(jù)。
6.4.3 使用 LitePal 增刪查改
6.4.3.1 添加數(shù)據(jù)
使用 LitePal 添加數(shù)據(jù),只需要創(chuàng)建出模型類的實例,再將所有要存儲的數(shù)據(jù)設置好,最后調(diào)用一下 save() 方法就可以了。
LitePal 進行表管理操作時不需要模型類有任何繼承結(jié)構(gòu),但進行 CRUD 操作時必須繼承 DataSupport 類才行,因此需要修改 Book 類的代碼如下:
public class Book extends DataSupport{
. . .
}
接著向 Book 表中添加數(shù)據(jù),修改 activity 中的代碼如下:
public class LitePalActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lite_pal);
. . .
// 添加數(shù)據(jù)
Button btn_add_data = (Button) findViewById(R.id.btn_add_data);
btn_add_data.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// 創(chuàng)建 Book 實例
Book book = new Book();
// 設置數(shù)據(jù)
book.setName("第一行代碼");
book.setAuthor("郭霖");
book.setPages(570);
book.setPrice(79.00);
book.setPress("人民郵電出版社");
// 添加數(shù)據(jù)
book.save();
}
});
}
}
現(xiàn)重新運行程序,點擊 添加數(shù)據(jù) 按鈕,此時數(shù)據(jù)就已經(jīng)添加了。同樣打開 MyBookStore.db,輸入 select*from Book;結(jié)果如下:
6.4.3.2 更新數(shù)據(jù)
使用 LitePal 更新數(shù)據(jù),它的接口比較多,這里只介紹幾種常用的。
-
方式1
??對已存儲的對象重新設值,然后調(diào)用 save() 方法即可。修改 activity 中的代碼如下:
public class LitePalActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lite_pal);
. . .
// 更新數(shù)據(jù)
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
??對需要更新的數(shù)據(jù)進行設值,最后調(diào)用 updateAll() 方法去執(zhí)行更新操作。修改 activity 中的代碼如下:
public class LitePalActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lite_pal);
. . .
// 更新數(shù)據(jù)
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 = ?","第一行代碼","郭霖");
}
});
}
}
現(xiàn)重新運行程序,點擊 更新數(shù)據(jù) 按鈕,查詢表中的數(shù)據(jù)情況,結(jié)果如下:
注意事項:在使用 updateAll() 方法時,若你把一個字段的值更新為默認值時,是不可以使用上面的方式來 set 數(shù)據(jù)的。對于想要將數(shù)據(jù)更新為默認值,LitePal 統(tǒng)一提供了一個 setToDefault() 方法,然后傳入相應的列名就可以了。如將所有書的頁數(shù)都更新為默認值 0,直接調(diào)用 book.setPages(0) 是不可以的,但可以這樣寫:
Book book = new Book();
book.setToDefault("pages");
book.updateAll();
6.4.3.3 刪除數(shù)據(jù)
使用 LitePal 刪除數(shù)據(jù)的方式主要有兩種:第一種就是直接調(diào)用已存儲的對象的 delete() 方法就可以了;第二種是調(diào)用 DataSupport.deleteAll() 方法來刪除。
這里演示下第二種方式,修改 activity 中的代碼如下:
public class LitePalActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lite_pal);
. . .
// 刪除數(shù)據(jù)
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() 方法若不指定約束條件,就意味著刪除表中的所有數(shù)據(jù)。
6.4.3.4 查詢數(shù)據(jù)
使用 LitePal 查詢數(shù)據(jù)一點都不復雜。
比如想要實現(xiàn)上節(jié)中的查詢 Book 表,修改 activity 的代碼如下:
public class LitePalActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lite_pal);
. . .
// 查詢數(shù)據(jù)
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());
}
}
});
}
}
沒錯,就是這么簡單。現(xiàn)重新運行程序,點擊 查詢數(shù)據(jù) 按鈕,查看Log信息如下:
除了 findAll() 方法之外,LitePal 還提供了其他非常有用的 API。比如:
- 查詢 Book 表中的第一條數(shù)據(jù):
Book firstBook = DataSupport.findFirst(Book.class);
- 查詢 Book 表中的最后一條數(shù)據(jù):
Book LastBook = DataSupport.findLast(Book.class);
還可以通過連綴查詢來制定更多的查詢功能:
- select() 方法用于指定查詢哪幾列的數(shù)據(jù)。
如查詢 name 和 author 這兩列數(shù)據(jù):
List<Book> books = DataSupport.select("name","author").find(Book.class);
- where() 方法用于指定查詢的約束條件。
如查詢頁數(shù)大450的數(shù)據(jù):
List<Book> books = DataSupport.where("pages > ?","450").find(Book.class);
- order() 方法用于指定結(jié)果的排序方式。
如查詢結(jié)果按書價從高到低排列:
// 其中 desc 表示降序,asc 或者不寫表示升序
List<Book> books = DataSupport.order("price desc").find(Book.class);
- limit() 方法用于指定查詢結(jié)果的數(shù)量。
如查詢表中前3條數(shù)據(jù):
List<Book> books = DataSupport.limit(3).find(Book.class);
- offset() 方法用于指定查詢結(jié)果的偏移量。
如查詢表中的第2、3、4條數(shù)據(jù):
List<Book> books = DataSupport.limit(3).offset(1).find(Book.class);
當然你也可以對這5個方法進行任意的連綴組合,如:
// 查詢 Book 表中第11-20跳滿足頁數(shù)大于450這個條件的 name、author 和 pages 這3列數(shù)據(jù),
// 并將查詢結(jié)果按照頁數(shù)升序排序
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");
關(guān)于 LitePal 就介紹到這。
有木有感覺 LitePal 很強大?
哎呦我去,這章終于結(jié)束了,雖然不難,但介紹起來真繁瑣。明天又到周末了,可以愉快得玩耍了!