Android之數據存儲詳解(二)之SQLite數據庫存儲數據

本博文是《第一行代碼 Android》的讀書筆記摘錄。

上一篇 Android之數據存儲詳解(一)講解了文件存儲數據和使用SharedPreferences存儲數據,接下來講解 SQLite數據庫存儲數據。

一、SQLite數據庫簡介

SQLite是一款輕型的數據庫,是遵守ACID的關聯式數據庫管理系統,它的設計目標是嵌入 式的,而且目前已經在很多嵌入式產品中使用了它,它佔用資源非常的低,在嵌入式設備中,可能只需要幾百K的內存就夠了。它能夠支持 Windows/Linux/Unix等等主流的操作系統,同時能夠跟很多程序語言相結合,比如Tcl、PHP、Java、C++、.Net等,還有ODBC接口,同樣比起 Mysql、PostgreSQL這兩款開源世界著名的數據庫管理系統來講,它的處理速度比他們都快。

二、SQLite數據類型

一般數據採用的固定的靜態數據類型,而SQLite採用的是動態數據類型,會根據存入值自動判斷。SQLite具有以下常用的數據類型:

(1)NULL: 這個值爲空值

(2)VARCHAR(n):長度不固定且其最大長度爲 n 的字串,n不能超過 4000。

(3)CHAR(n):長度固定爲n的字串,n不能超過 254。

(4)INTEGER: 值被標識爲整數,依據值的大小可以依次被存儲爲1,2,3,4,5,6,7,8.

(5)REAL: 所有值都是浮動的數值,被存儲爲8字節的IEEE浮動標記序號.

(6)TEXT: 值爲文本字符串,使用數據庫編碼存儲(TUTF-8, UTF-16BE or UTF-16-LE).

(7)BLOB: 值是BLOB數據塊,以輸入的數據格式進行存儲。如何輸入就如何存儲,不改變格式。

(8)DATA :包含了 年份、月份、日期。

(9)TIME: 包含了 小時、分鐘、秒。

三、Android之SQLiteOpenHelper介紹

Android 爲了讓我們能夠更加方便地管理數據庫,專門提供了一個SQLiteOpenHelper 幫助類,藉助這個類就可以非常簡單地對數據庫進行創建和管理。

public abstract class SQLiteOpenHelper;

SQLiteOpenHelper 是一個抽象類,這意味着如果我們想要使用它的話,就需要創建一個自己的幫助類去繼承它。SQLiteOpenHelper 中有兩個抽象方法,分別是onCreate()和onUpgrade(),我們必須在自己的幫助類裏面重寫這兩個方法,然後分別在這兩個方法中去實現創建、升級數據庫的邏輯。

    @Override
    public void onCreate(SQLiteDatabase db) {

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

SQLiteOpenHelper 中還有兩個非常重要的實例方法, getReadableDatabase() 和getWritableDatabase()。這兩個方法都可以創建或打開一個現有的數據庫(如果數據庫已存在則直接打開,否則創建一個新的數據庫),並返回一個可對數據庫進行讀寫操作的對象。不同的是,當數據庫不可寫入的時候(如磁盤空間已滿)getReadableDatabase()方法返回的對象將以只讀的方式去打開數據庫,而getWritableDatabase()方法則將出現異常。

public SQLiteDatabase getReadableDatabase();

public SQLiteDatabase getWritableDatabase()

SQLiteOpenHelper 中有兩個構造方法可供重寫,一般使用參數少一點的那個構造方法即可。

    public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version);

這個構造方法中接收四個參數:
第一個參數是Context,這個沒什麼好說的,必須要有它才能對數據庫進行操作。
第二個參數是數據庫名,創建數據庫時使用的就是這裏指定的名稱。
第三個參數允許我們在查詢數據的時候返回一個自定義的Cursor,一般都是傳入null。
第四個參數表示當前數據庫的版本號, 可用於對數據庫進行升級操作。

構建出SQLiteOpenHelper 的實例之後,再調用它的getReadableDatabase()或getWritableDatabase()方法就能夠創建數據庫了,數據庫文件會存放在/data/data/<package name>/databases/目錄下。此時,重寫的onCreate()方法也會得到執行,所以通常會在這裏去處理一些創建表的邏輯。

下面,將詳細講解SQLiteOpenHelper的使用操作。

四、創建數據庫

我們希望創建一個名爲BookStore.db 的數據庫,然後在這個數據庫中新建一張Book表,表中有id(主鍵)、作者、價格、頁數和書名等列。創建數據庫表當然還是需要用建表語句的,Book 表的建表語句如下所示:

    create table Book (
        id integer primary key autoincrement,
        author text,
        price real,
        pages integer,
        name text)

然後需要在代碼中去執行這條SQL 語句,才能完成創建表的操作。新建MyDatabaseHelper類繼承自SQLiteOpenHelper,代碼如下所示:

package com.example.databasetest;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.widget.Toast;

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 表。

在MainActivity 中中點擊Button,創建數據庫:

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 數據庫了,因此不會再創建一次。

如果你已經安裝了adb工具,可以使用cd 命令進行到/data/data/com.example.databasetest/databases/目錄下,並使用ls命令查看到該目錄裏的文件:

1

這個目錄下出現了兩個數據庫文件,一個正是我們創建的BookStore.db,而另一個
BookStore.db-journal 則是爲了讓數據庫能夠支持事務而產生的臨時日誌文件,通常情況下這個文件的大小都是0 字節。
接下來我們就要藉助sqlite 命令來打開數據庫了,只需要鍵入sqlite3,後面加上數據庫名即可:
2

這時就已經打開了BookStore.db 數據庫,現在就可以對這個數據庫中的表進行管理了。首先來看一下目前數據庫中有哪些表,鍵入.table 命令:
3

可以看到,此時數據庫中有兩張表,android_metadata 表是每個數據庫中都會自動生成的,不用管它,而另外一張Book 表就是我們在MyDatabaseHelper 中創建的了。這裏還可以通過.schema 命令來查看它們的建表語句:
4

由此證明,BookStore.db 數據庫和Book 表確實已經是創建成功了。之後鍵入.exit 或.quit命令可以退出數據庫的編輯,再鍵入exit 命令就可以退出設備控制檯了。

五、升級數據庫

我們發現MyDatabaseHelper 中還有一個空方法onUpgrade():

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

這個方法是用於對數據庫進行升級的,它在整個數據庫的管理工作當中起着非常重要的作用。
目前DatabaseTest 項目中已經有一張Book 表用於存放書的各種詳細數據,如果我們想再添加一張Category 表用於記錄書籍的分類該怎麼做呢?
比如Category 表中有id(主鍵)、分類名和分類代碼這幾個列,那麼建表語句就可以寫成:

create table Category (
    id integer primary key autoincrement,
    category_name text,
    category_code integer)

接下來我們將這條建表語句添加到MyDatabaseHelper 中,代碼如下所示:

package com.example.databasetest;

import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.widget.Toast;

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 命令,結果如圖所示:
5

接着鍵入.schema 命令查看一下建表語句,結果如圖 所示:
6

由此可以看出,Category 表已經創建成功了,同時也說明我們的升級功能的確起到了作用。

升級數據庫的最佳寫法

以上的升級數據庫的方式是非常粗暴的,爲了保證數據庫中的表是最新的,我們只是簡單地在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, CursorFactory
            factory, int version) {
        super(context, name, factory, version);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    }
}

不過,幾星期之後又有了新需求,這次需要向數據庫中再添加一張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,
                            CursorFactory factory, int version) {
        super(context, name, factory, version);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_CATEGORY);
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        switch (oldVersion) {
            case 1:
            db.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,
                            CursorFactory factory, int version) {
        super(context, name, factory, version);
    }
    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BOOK);
        db.execSQL(CREATE_CATEGORY);
    }
    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        switch (oldVersion) {
            case 1:
                db.execSQL(CREATE_CATEGORY);
            case 2:
                db.execSQL("alter table Book add column category_id integer");
            default:
        }
    }
}

可以看到,首先我們在Book 表的建表語句中添加了一個category_id 列,這樣當用戶直接安裝第三版的程序時,這個新增的列就已經自動添加成功了。然而,如果用戶之前已經安裝了某一版本的程序,現在需要覆蓋安裝,就會進入到升級數據庫的操作中。在onUpgrade()方法裏,我們添加了一個新的case,如果當前數據庫的版本號是2,就會執行alter 命令來爲Book 表新增一個category_id 列。

這裏注意一個非常重要的細節,switch 中每一個case 的最後都是沒有使用break 的,爲什麼要這麼做呢?

這是爲了保證在跨版本升級的時候,每一次的數據庫修改都能被全部執行到。比如用戶當前是從第二版程序升級到第三版程序的,那麼case 2 中的邏輯就會執行。而如果用戶是直接從第一版程序升級到第三版程序的,那麼case 1 和case 2 中的邏輯都會執行。使用這種方式來維護數據庫的升級,不管版本怎樣更新,都可以保證數據庫的表結構是最新的,而且表中的數據也完全不會丟失了。

六、添加數據

現在已經掌握了創建和升級數據庫的方法,接下來就該學習一下如何對錶中的數據進行操作了。其實我們可以對數據進行的操作也就無非四種,即CRUD。其中C 代表添加(Create),R 代表查詢(Retrieve),U 代表更新(Update),D 代表刪除(Delete)。每一種操作又各自對應了一種SQL 命令,如果你比較熟悉SQL 語言的話,一定會知道添加數據時使用insert,查詢數據時使用select,更新數據時使用update,刪除數據時使用delete。

但是開發者的水平總會是參差不齊的,未必每一個人都能非常熟悉地使用SQL 語言,因此Android也是提供了一系列的輔助性方法,使得在Android 中即使不去編寫SQL 語句,也能輕鬆完成所有的CRUD 操作。

SQLiteDatabase 中提供了一個insert()方法,這個方法就是專門用於添加數據的:

public long insert(String table, String nullColumnHack, ContentValues values)

它接收三個參數:
第一個參數是表名,我們希望向哪張表裏添加數據,這裏就傳入該表的名字。
第二個參數用於在未指定添加數據的情況下給某些可爲空的列自動賦值NULL,一般我們用不到這個功能,直接傳入null 即可。
第三個參數是一個ContentValues 對象,它提供了一系列的put()方法重載,用於向ContentValues 中添加數據,只需要將表中的每個列名以及相應的待添加數據傳入即可。

接下來還是讓我們通過例子的方式來親身體驗一下如何添加數據:

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); // 插入第二條數據

我們先獲取到了SQLiteDatabase 對象,然後使用ContentValues 來對要添加的數據進行組裝。如果你比較細心的話應該會發現,這裏只對Book表裏其中四列的數據進行了組裝,id 那一列沒並沒給它賦值。這是因爲在前面創建表的時候我們就將id 列設置爲自增長了,它的值會在入庫的時候自動生成,所以不需要手動給它賦值了。接下來調用了insert()方法將數據添加到表當中,注意這裏我們實際上添加了兩條數據,上述代碼中使用ContentValues 分別組裝了兩次不同的內容,並調用了兩次insert()方法。

打開BookStore.db 數據庫,輸入SQL 查詢語句select * from Book,結果如圖:
8

由此可以看出,我們剛剛組裝的兩條數據,都已經準確無誤地添加到Book 表中了。

七、更新數據

SQLiteDatabase 中也是提供了一個非常好用的update()方法用於對數據進行更新:

public int update(String table, ContentValues values, String whereClause, String[] whereArgs);

這個方法接收四個參數:
第一個參數和insert()方法一樣,也是表名,在這裏指定去更新哪張表裏的數據;
第二個參數是ContentValues 對象,要把更新數據在這裏組裝進去;
第三個參數是更新條件(where字句);
第四個參數是更新條件數組。

其中第三、第四個參數用於去約束更新某一行或某幾行中的數據,不指定的話默認就是更新所有行。

接下來我們仍然是在DatabaseTest 項目的基礎上修改書的價格:

SQLiteDatabase db = dbHelper.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("price", 10.99);
        db.update("Book", values, "name = ?",
        new String[] { "The Da Vinci Code" });

這裏構建了一個ContentValues 對象,並且只給它指定了一組數據,說明我們只是想把價格這一列的數據更新成10.99。然後調用了SQLiteDatabase的update()方法去執行具體的更新操作,可以看到,這裏使用了第三、第四個參數來指定具體更新哪幾行。第三個參數對應的是SQL 語句的where 部分,表示去更新所有name 等於?的行,而?是一個佔位符,可以通過第四個參數提供的一個字符串數組爲第三個參數中的每個佔位符指定相應的內容。因此上述代碼想表達的意圖就是,將名字是The Da Vinci Code的這本書的價格改成10.99。

八、刪除數據

SQLiteDatabase 中提供了一個delete()方法專門用於刪除數據:

public int delete(String table, String whereClause, String[] whereArgs) ;

這個方法接收三個參數,第一個參數仍然是表名,這個已經沒什麼好說的了,第二、第三個參數又是用於去約束刪除某一行或某幾行的數據,不指定的話默認就是刪除所有行。

我們去刪除Book 表中的數據,並且通過第二、第三個參數來指定僅刪除那些頁數超過500 頁的書籍:

SQLiteDatabase db = dbHelper.getWritableDatabase();
db.delete("Book", "pages > ?", new String[] { "500" });

九、查詢數據

SQLiteDatabase 中還提供了一個query()方法用於對數據進行查詢:

public Cursor query(String table, String[] columns, String selection,String[] selectionArgs, String groupBy, String having,String orderBy)

第一個參數不用說,當然還是表名,表示我們希望從哪張表中查
詢數據。
第二個參數用於指定去查詢哪幾列,如果不指定則默認查詢所有列。
第三、第四個參數用於去約束查詢某一行或某幾行的數據,不指定則默認是查詢所有行的數據。
第五個參數用於指定需要去group by 的列,不指定則表示不對查詢結果進行group by 操作。
第六個參數用於對group by 之後的數據進行進一步的過濾,不指定則表示不進行過濾。
第七個參數用於指定查詢結果的排序方式,不指定則表示使用默認的排序方式

其他幾個query()方法的重載其實也大同小異,你可以自己去研究一下,這裏就不再進行介紹了。

雖然query()方法的參數非常多,但是不要對它產生畏懼,因爲我們不必爲每條查詢語句都指定上所有的參數,多數情況下只需要傳入少數幾個參數就可以完成查詢操作了。調用query()方法後會返回一個Cursor 對象,查詢到的所有數據都將從這個對象中取出。

下面還是通過例子的方式來體驗一下查詢數據的具體用法:

SQLiteDatabase db = dbHelper.getWritableDatabase();
        Cursor cursor = db.query("Book", null, null, null, null, null,
        null);
        if (cursor.moveToFirst()) {
        do {
        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("MainActivity", "book name is " + name);
        Log.d("MainActivity", "book author is " + author);
        Log.d("MainActivity", "book pages is " + pages);
        Log.d("MainActivity", "book price is " + price);
        } while (cursor.moveToNext());
        }
        cursor.close();

這裏的query()方法非常簡單,只是使用了第一個參數指明去查詢Book 表,後面的參數全部爲null。這就表示希望查詢這張表中的所有數據,雖然這張表中目前只剩下一條數據了。查詢完之後就得到了一個Cursor 對象,接着我們調用它的moveToFirst()方法將數據的指針移動到第一行的位置,然後進入了一個循環當中,去遍歷查詢到的每一行數據。

在這個循環中可以通過Cursor 的getColumnIndex()方法獲取到某一列在表中對應的位置索引,然後將這個索引傳入到相應的取值方法中,就可以得到從數據庫中讀取到的數據了。接着我們使用Log 的方式將取出的數據打印出來,藉此來檢查一下讀取工作有沒有成功完成。最後別忘了調用close()方法來關閉Cursor。

查看LogCat 的打印內容,結果如圖所示:
9

當然這個例子只是對查詢數據的用法進行了最簡單的示範,在真正的項目中你可能會遇到比這要複雜得多的查詢功能,更多高級的用法還需要你自己去慢慢摸索,畢竟query()方法中還有那麼多的參數我們都還沒用到呢。

十、使用SQL 操作數據庫

雖然Android 已經給我們提供了很多非常方便的API 用於操作數據庫,Android 充分考慮到了大家編程習慣,同樣提供了一系列的方法,使得可以直接通過SQL 來操作數據庫。

下面演示如何直接使用SQL 來完成前面學過的CRUD 操作:

1、添加數據的方法

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" });

2、更新數據的方法

db.execSQL("update Book set price = ? where name = ?", new String[] { "10.99",
"The Da Vinci Code" });

3、刪除數據的方法

db.execSQL("delete from Book where pages > ?", new String[] { "500" });

4、查詢數據的方法

db.rawQuery("select * from Book", null);

可以看到,除了查詢數據的時候調用的是SQLiteDatabase 的rawQuery()方法,其他的操作都是調用的execSQL()方法。

十一、SQLite 數據庫使用事務

SQLite 數據庫是支持事務的,事務的特性可以保證讓某一系列的操
作要麼全部完成,要麼一個都不會完成。那麼在什麼情況下才需要使用事務呢?

想象以下場景,比如你正在進行一次轉賬操作,銀行會將轉賬的金額先從你的賬戶中扣除,然後再向收款方的賬戶中添加等量的金額。看上去好像沒什麼問題吧?可是,如果當你賬戶中的金額剛剛被扣除,這時由於一些異常原因導致對方收款失敗,這一部分錢就憑空消失了!當然銀行肯定已經充分考慮到了這種情況,它會保證扣錢和收款的操作要麼一起成功,要麼都不會成功,而使用的技術當然就是事務了。

接下來我們看一看如何在Android 中使用事務吧,仍然是在DatabaseTest 項目的基礎上進行修改。比如Book 表中的數據都已經很老了,現在準備全部廢棄掉替換成新數據,可以先使用delete()方法將Book表中的數據刪除,然後再使用insert()方法將新的數據添加到表中。我們要保證的是,刪除舊數據和添加新數據的操作必須一起完成,否則就還要繼續保留原來的舊數據。如下所示:

SQLiteDatabase db=dbHelper.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,這樣添加新數據的代碼就執行不到了。不過由於事務的存在,中途出現異常會導致事務的失敗,此時舊數據應該是刪除不掉的。

可以運行一下程序,你會發現,Book 表中存在的還是之前的舊數據。然後將手動拋出異常的那行代碼去除,再重新運行一下程序,就會將Book 表中的數據替換成新數據了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章