Android上SQLite的使用

SQLite.jpg

SQLite簡介

結(jié)構(gòu)化查詢語言(Structured Query Language)簡稱SQL,是一種特殊目的的編程語言,專為數(shù)據(jù)庫操作而設(shè)計,用于存取數(shù)據(jù)以及查詢、更新和管理關(guān)系數(shù)據(jù)庫系統(tǒng)。同時,sql也通常是數(shù)據(jù)庫腳本文件的擴展名。

SQLite是一個實現(xiàn)了自給自足的、無服務(wù)器的、零配置的、事務(wù)性的SQL數(shù)據(jù)庫引擎,并且是一個開源項目。Android上自帶SQLite,因此是Android項目上常用的數(shù)據(jù)庫。

對大部分?jǐn)?shù)據(jù)庫來說,大體可以看做是一個較大的Excel文件。數(shù)據(jù)庫中TABLE(表)的概念,對應(yīng)Excel中Sheet;數(shù)據(jù)庫中row(行)、column(列)的概念,對應(yīng)Excel的行(被標(biāo)記為1234的那些)和列(被標(biāo)記為ABCD的那些)的概念。

數(shù)據(jù)庫常用的六個操作,建表、丟表(刪除表)、增、刪、改、查,其SQL語句如下所示。

建表

CREATE TABLE 數(shù)據(jù)表名稱(字段1 類型1(長度),字段2 類型2(長度) …… );

CREATE TABLE IF NOT EXISTS 數(shù)據(jù)表名稱(字段1 類型1(長度),字段2 類型2(長度) …… );

丟表

DROP TABLE 數(shù)據(jù)表名稱;

DROP TABLE IF EXISTS 數(shù)據(jù)表名稱;

INSERT INTO 數(shù)據(jù)表 (字段1,字段2,字段3 …) valuess (值1,值2,值3 …);

INSERT INTO 目標(biāo)數(shù)據(jù)表 SELECT * FROM 源數(shù)據(jù)表;

DELETE FROM 數(shù)據(jù)表 WHERE 條件表達式;

DELETE FROM 數(shù)據(jù)表;

UPDATE 數(shù)據(jù)表 SET 字段名=字段值 WHERE 條件表達式;

UPDATE 數(shù)據(jù)表 SET 字段1=值1,字段2=值2 …… 字段n=值n WHERE 條件表達式;

SELECT * FROM 數(shù)據(jù)表 WHERE 字段名=字段值 ORDER BY 字段名 [DESC];

SELECT * FROM 數(shù)據(jù)表 WHERE 字段名 LIKE '%字段值%' ORDER BY 字段名 [DESC];

SELECT TOP 10 * from 數(shù)據(jù)表 WHERE 字段名 ORDER BY 字段名 [DESC];

SELECT * FROM 數(shù)據(jù)表 WHERE 字段名 IN ('值1','值2','值3');

SELECT * FROM 數(shù)據(jù)表 WHERE 字段名 BETWEEN 值1 AND 值2;

Android常用數(shù)據(jù)庫功能代碼塊

在Android上,如果用Java的方式來使用SQLite,同樣需要對數(shù)據(jù)庫有一定了解。

常用代碼塊示例如下。

建表

建表,由于要定制其各列的字段名及數(shù)據(jù)類型,所以仍然只能使用原始的SQL語句,通過SQLiteDatabase.execSQL(String)來執(zhí)行。

        db.execSQL("CREATE TABLE IF NOT EXISTS "
                + TABLE_NAME + " ( "
                + KEY1 + "類型1(長度)" + ", "
                + KEY2+ "類型2(長度)"
                + " );");

丟表

丟表(此為英文直譯,通常翻譯為刪除表),也是執(zhí)行SQL。

        db.execSQL("DROP TABLE IF NOT EXISTS " + TABLE_NAME);

另外,刪除數(shù)據(jù)庫是通過Context.deleteDatabase(String name)來執(zhí)行。而刪除一個表中的所有數(shù)據(jù),則可以用SQLiteDatabase.delete(String, String, String[])

        // Delete all data in the table named $TABLE_NAME
        db.delete(TABLE_NAME, null, null);

數(shù)據(jù)庫的增和刪,都是針對行的。

        ContentValues cv = new ContentValues();
        cv.put(key1, value1);
        cv.put(key2, value2);

        SQLiteDatabase db = getWritableDatabase();
        db.insert(TABLE_NAME, null, cv);

上面代碼的意義是,給TABLE_NAME這個表中增加一行。在這一行中,把value1放到key1這一列,把value2放到key2這一列。

刪除雖然不需要執(zhí)行SQL,但是也要用類似SQL的方式來指定刪除的位置。

        String whereClause = key1 + "=?";
        String[] whereArgs = new String[]{String.valueOf(value1)};
        db.delete(TABLE_NAME, whereClause, whereArgs);

以上代碼中,whereClause這個位置,填入SQL的WHERE后面的選擇方式;whereArgs則是替換whereClause中的問號?,有幾個問號,這個String數(shù)組中就應(yīng)該給出幾個參數(shù)。

通常是選擇=作為判斷。上面代碼的意義是,刪除key1這一列中,值為value1的所有行。

和刪除類似,指定位置要用到類似SQL的方式。SQLiteDatabaseupdate()delete()多了一個ContentValues參數(shù),表示那一行需要更新的值。

        ContentValues values = new ContentValues();
        values.put(key1, value2);

        SQLiteDatabase db = getWritableDatabase();
        String whereClause = key1 + "=?";
        String[] whereArgs = new String[]{String.valueOf(value1)};
        db.update(TABLE_NAME, values, whereClause, whereArgs);

以上代碼的意義是,把key1這一列等于value1的所有行,其key1的位置都替換為value2

這是參數(shù)最繁瑣的一個操作,SQLiteDatabase.query()一共7個參數(shù),是糟糕的代碼設(shè)計典范。而且部分操作在大部分情況下,都會被設(shè)為null

首先拿一個Cursor出來。

        SQLiteDatabase db = getWritableDatabase();
        // Get all columns
        try (Cursor cursor = db.query(TABLE_NAME, null, null, null, null, null, null)) {
            // Deal with the cursor.
        }

只傳入表名,其它參數(shù)都給null,這樣可以把整個表的數(shù)據(jù)都讀出來。

其實這個Cursor并非是一個游標(biāo)一樣的東西,而更像是數(shù)據(jù)庫的一個片段。在數(shù)據(jù)庫比較大時,每次查詢都讀整張表,會造成非常大的IO壓力。因此,若非必要,讀取時只應(yīng)該選取需要的片段。

query()的參數(shù)如下:

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

其中,columns代表需要被選取的列,以字符數(shù)組的形式傳遞;selectionselectionArgs,類似于whereClausewhereArgs,是在SQL的WHERE關(guān)鍵字后的語句;groupBy是SQL的GROUP BY后面的語句;having是SQL的HAVING后面的語句;orderBy是SQL的ORDER BY后面的語句。

在拿到Cursor后,可以用類似迭代器(Iterator)的方式去使用它。比如,遍歷整個Cursor的某一列的代碼如下。

        cursor.moveToFirst();
        do {
            int column = cursor.getColumnIndex(KEY1);
            String value = cursor.getString(column);
            // Do sth with the `value`.
        } while (cursor.moveToNext());

以上代碼,如果放到之前// Deal with the cursor.的位置,就可以遍歷整張表。

這里try () {}是Java v1.7新加入的try with resource用法,會確保cursor.close()被調(diào)用。

Android相關(guān)類介紹

在Android上使用SQLite,通過SQLiteDatabase.execSQL(String)接口,可以把SQL的語句轉(zhuǎn)換為字符串,傳入其中執(zhí)行。這樣雖然簡單,但是難以調(diào)試。

另一方面也可以使用它的各種包裝類。由于各種類的層次關(guān)系復(fù)雜,網(wǎng)絡(luò)教程少,這種方法難以使用。

這些功能包裝類基本上都在以下位置:

import android.database.*;
import android.database.sqlite.*;

下面做出一些介紹。

SQLiteOpenHelper

import android.database.sqlite.SQLiteOpenHelper;

這是一個抽象類,需要被繼承后使用。構(gòu)造函數(shù),和兩個Method一定要被override。

    public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version) {
        this(context, name, factory, version, null);
    }

    /**
     * Create a helper object to create, open, and/or manage a database.
     * The database is not actually created or opened until one of
     * {@link #getWritableDatabase} or {@link #getReadableDatabase} is called.
     *
     * <p>Accepts input param: a concrete instance of {@link DatabaseErrorHandler} to be
     * used to handle corruption when sqlite reports database corruption.</p>
     *
     * @param context to use to open or create the database
     * @param name of the database file, or null for an in-memory database
     * @param factory to use for creating cursor objects, or null for the default
     * @param version number of the database (starting at 1); if the database is older,
     *     {@link #onUpgrade} will be used to upgrade the database; if the database is
     *     newer, {@link #onDowngrade} will be used to downgrade the database
     * @param errorHandler the {@link DatabaseErrorHandler} to be used when sqlite reports database
     * corruption, or null to use the default error handler.
     */
    public SQLiteOpenHelper(Context context, String name, CursorFactory factory, int version,
            DatabaseErrorHandler errorHandler) {
        if (version < 1) throw new IllegalArgumentException("Version must be >= 1, was " + version);

        mContext = context;
        mName = name;
        mFactory = factory;
        mNewVersion = version;
        mErrorHandler = errorHandler;
    }

    /**
     * Called when the database is created for the first time. This is where the
     * creation of tables and the initial population of the tables should happen.
     *
     * @param db The database.
     */
    public abstract void onCreate(SQLiteDatabase db);

    /**
     * Called when the database needs to be upgraded. The implementation
     * should use this method to drop tables, add tables, or do anything else it
     * needs to upgrade to the new schema version.
     * <p/>
     * <p>
     * The SQLite ALTER TABLE documentation can be found
     * <a >here</a>. If you add new columns
     * you can use ALTER TABLE to insert them into a live table. If you rename or remove columns
     * you can use ALTER TABLE to rename the old table, then create the new table and then
     * populate the new table with the contents of the old table.
     * </p><p>
     * This method executes within a transaction.  If an exception is thrown, all changes
     * will automatically be rolled back.
     * </p>
     *
     * @param db         The database.
     * @param oldVersion The old database version.
     * @param newVersion The new database version.
     */
    public abstract void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion);

onCreate()中,通常進行建表(table)的工作;onUpgrade()中,往往處理一些升級操作,比如要換表(先DROP再CREATE)。這兩個回調(diào)的觸發(fā),與構(gòu)造函數(shù)中傳遞的version相關(guān)。

以下代碼塊是上述兩個回調(diào),加上onDowngrade(),根據(jù)version不同的調(diào)用情況。另外,onConfigure()onOpen()的回調(diào)情況也在其中。

            onConfigure(db);

            db.beginTransaction();
            try {
                if (version == 0) {
                    onCreate(db);
                } else {
                    if (version > mNewVersion) {
                        onDowngrade(db, version, mNewVersion);
                    } else {
                        onUpgrade(db, version, mNewVersion);
                    }
                }
                db.setVersion(mNewVersion);
                db.setTransactionSuccessful();
            } finally {
                db.endTransaction();
            }

            onOpen(db);

通常,對于一個自用的小數(shù)據(jù)庫,在不需要更新表的情況下,version可以萬年不變。但是,為了預(yù)留在未來調(diào)整數(shù)據(jù)庫結(jié)構(gòu)的可操作空間,用SQLiteOpenHelper進行一層封裝總是沒錯。

SQLiteDatabase

import android.database.sqlite.SQLiteDatabase;

SQLiteDatabase是Android對SQLite數(shù)據(jù)庫的包裝類,可以實現(xiàn)其增刪改查的主要功能。其開關(guān),通常由SQLiteOpenHelper來負(fù)責(zé)。

常用接口如下,基本實現(xiàn)了數(shù)據(jù)庫的常用功能。

    public long insert(String table, String nullColumnHack, ContentValues values);
    public long replace(String table, String nullColumnHack, ContentValues initialValues);
    public int delete(String table, String whereClause, String[] whereArgs);
    public int update(String table, ContentValues values, String whereClause, String[] whereArgs);
    public Cursor rawQuery(String sql, String[] selectionArgs);
    public Cursor query(boolean distinct, String table, String[] columns,
            String selection, String[] selectionArgs, String groupBy,
            String having, String orderBy, String limit);

    public void execSQL(String sql) throws SQLException;
    public void execSQL(String sql, Object[] bindArgs) throws SQLException;

    public int getVersion();
    public void setVersion(int version);
    public long getMaximumSize();
    public long getPageSize();
    public void setPageSize(long numBytes);
    public static String findEditTable(String tables);
    public SQLiteStatement compileStatement(String sql) throws SQLException;
    public boolean isReadOnly();
    public boolean isInMemoryDatabase();
    public boolean isOpen();
    public boolean needUpgrade(int newVersion);
    public final String getPath();
    public void setLocale(Locale locale);
    public long setMaximumSize(long numBytes);

其常用的增刪改查功能,接口實現(xiàn)的內(nèi)部已經(jīng)對引用進行了獲取與釋放,所以通常不需要手動close()

DatabaseErrorHandler

這是一個回調(diào)接口,在SQLiteOpenHelper的構(gòu)造函數(shù)中傳入,通過實現(xiàn)void onCorruption(SQLiteDatabase dbObj);來使用。

/**
 * An interface to let apps define an action to take when database corruption is detected.
 */
public interface DatabaseErrorHandler {

    /**
     * The method invoked when database corruption is detected.
     * @param dbObj the {@link SQLiteDatabase} object representing the database on which corruption
     * is detected.
     */
    void onCorruption(SQLiteDatabase dbObj);
}

通常情況下,不需要使用這個回調(diào),設(shè)置為null即可。

ContentValues

import android.content.ContentValues;

基本上可以看做是一個HashMap的包裝類,所不同的是,實現(xiàn)了Paracable接口,多了一些Method。

public final class ContentValues implements Parcelable {
    public static final String TAG = "ContentValues";

    /** Holds the actual values */
    private HashMap<String, Object> mValues;

    /**
     * Creates an empty set of values using the default initial size
     */
    public ContentValues() {
        // Choosing a default size of 8 based on analysis of typical
        // consumption by applications.
        mValues = new HashMap<String, Object>(8);
    }

    // Omitted...
}

Cursor

import android.database.Cursor;

Cursor(游標(biāo))是SQLiteDatabase.query()后返回的一個對象,也是數(shù)據(jù)庫查詢的一個通用概念。

由于數(shù)據(jù)庫往往是一個二維表格的形式,不能直接給個鍵就返回值,所以在訪問時需要一個中間類進行細微的定位。Cursor會默認(rèn)停留在某一個row(行),同時可訪問這個row的任意一個column(列);但如果要訪問另一個row的某column,只能先換行。move*()系列操作就是換行用的,而get*()系列操作則是按行取列用的。

Cursor的主要接口如下:

    int getCount();
    int getPosition();
    boolean move(int offset);
    boolean moveToPosition(int position);
    boolean moveToFirst();
    boolean moveToLast();
    boolean moveToNext();
    boolean moveToPrevious();
    boolean isFirst();
    boolean isLast();
    boolean isBeforeFirst();
    boolean isAfterLast();
    int getColumnIndex(String columnName);
    int getColumnIndexOrThrow(String columnName) throws IllegalArgumentException;
    String getColumnName(int columnIndex);
    String[] getColumnNames();
    int getColumnCount();
    byte[] getBlob(int columnIndex);
    String getString(int columnIndex);
    void copyStringToBuffer(int columnIndex, CharArrayBuffer buffer);
    short getShort(int columnIndex);
    int getInt(int columnIndex);
    long getLong(int columnIndex);
    float getFloat(int columnIndex);
    double getDouble(int columnIndex);
    int getType(int columnIndex);
    boolean isNull(int columnIndex);
    void deactivate();
    boolean requery();
    void close();
    boolean isClosed();
    void registerContentObserver(ContentObserver observer);
    void unregisterContentObserver(ContentObserver observer);
    void registerDataSetObserver(DataSetObserver observer);
    void unregisterDataSetObserver(DataSetObserver observer);
    void setNotificationUri(ContentResolver cr, Uri uri);
    Uri getNotificationUri();
    boolean getWantsAllOnMoveCalls();
    void setExtras(Bundle extras);
    Bundle getExtras();
    Bundle respond(Bundle extras);

Cursor的很多接口都可能拋出異常,并且用完后一定要主動調(diào)用close(),所以通常用try catch finally、或try with resource包裹其使用操作。

CursorLoader

import android.content.CursorLoader;

Android自3.0開始提供Loader機制,而CursorLoader是其中一例。

CursorLoader實現(xiàn)了異步數(shù)據(jù)庫查詢,避免了大量同步查詢的主線程阻塞。

主要用法是,在構(gòu)造函數(shù)設(shè)置Cursor.query()用的各種參數(shù),或者在空構(gòu)造函數(shù)后一一設(shè)置,然后用loadInBackground()來獲取Cursor

    /* Runs on a worker thread */
    @Override
    public Cursor loadInBackground() {
        synchronized (this) {
            if (isLoadInBackgroundCanceled()) {
                throw new OperationCanceledException();
            }
            mCancellationSignal = new CancellationSignal();
        }
        try {
            Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
                    mSelectionArgs, mSortOrder, mCancellationSignal);
            if (cursor != null) {
                try {
                    // Ensure the cursor window is filled.
                    cursor.getCount();
                    cursor.registerContentObserver(mObserver);
                } catch (RuntimeException ex) {
                    cursor.close();
                    throw ex;
                }
            }
            return cursor;
        } finally {
            synchronized (this) {
                mCancellationSignal = null;
            }
        }
    }

DatabaseUtils

import android.database.DatabaseUtils;

這是一個工具類,里面有很多常用的數(shù)據(jù)庫操作,比如Cursor轉(zhuǎn)ContentValues等。

使用前,最好細讀對應(yīng)源碼。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,836評論 6 540
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,275評論 3 428
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,904評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,633評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,368評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,736評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,740評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,919評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,481評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,235評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,427評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,968評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,656評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,055評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,348評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,160評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,380評論 2 379

推薦閱讀更多精彩內(nèi)容