Android數(shù)據(jù)保存到數(shù)據(jù)庫(kù)

2018-08-02 17:23 更新

編寫(xiě):kesenhoo - 原文:http://developer.android.com/training/basics/data-storage/databases.html

對(duì)于重復(fù)或者結(jié)構(gòu)化的數(shù)據(jù)(如聯(lián)系人信息)等保存到DB是個(gè)不錯(cuò)的主意。本課假定讀者已經(jīng)熟悉SQL數(shù)據(jù)庫(kù)的常用操作。在Android上可能會(huì)使用到的APIs,可以從android.database.sqlite包中找到。

定義Schema與Contract

SQL中一個(gè)重要的概念是schema:一種DB結(jié)構(gòu)的正式聲明,用于表示database的組成結(jié)構(gòu)。schema是從創(chuàng)建DB的SQL語(yǔ)句中生成的。我們會(huì)發(fā)現(xiàn)創(chuàng)建一個(gè)伴隨類(lèi)(companion class)是很有益的,這個(gè)類(lèi)稱(chēng)為合約類(lèi)(contract class),它用一種系統(tǒng)化并且自動(dòng)生成文檔的方式,顯示指定了schema樣式。

Contract Clsss是一些常量的容器。它定義了例如URIs,表名,列名等。這個(gè)contract類(lèi)允許在同一個(gè)包下與其他類(lèi)使用同樣的常量。 它讓我們只需要在一個(gè)地方修改列名,然后這個(gè)列名就可以自動(dòng)傳遞給整個(gè)code。

組織contract類(lèi)的一個(gè)好方法是在類(lèi)的根層級(jí)定義一些全局變量,然后為每一個(gè)table來(lái)創(chuàng)建內(nèi)部類(lèi)。

Note:通過(guò)實(shí)現(xiàn) BaseColumns 的接口,內(nèi)部類(lèi)可以繼承到一個(gè)名為_(kāi)ID的主鍵,這個(gè)對(duì)于Android里面的一些類(lèi)似cursor adaptor類(lèi)是很有必要的。這么做不是必須的,但這樣能夠使得我們的DB與Android的framework能夠很好的相容。

例如,下面的例子定義了表名與該表的列名:

public final class FeedReaderContract {
    // To prevent someone from accidentally instantiating the contract class,
    // give it an empty constructor.
    public FeedReaderContract() {}

    /* Inner class that defines the table contents */
    public static abstract class FeedEntry implements BaseColumns {
        public static final String TABLE_NAME = "entry";
        public static final String COLUMN_NAME_ENTRY_ID = "entryid";
        public static final String COLUMN_NAME_TITLE = "title";
        public static final String COLUMN_NAME_SUBTITLE = "subtitle";
        ...
    }
}

使用SQL Helper創(chuàng)建DB

定義好了的DB的結(jié)構(gòu)之后,就應(yīng)該實(shí)現(xiàn)那些創(chuàng)建與維護(hù)db和table的方法。下面是一些典型的創(chuàng)建與刪除table的語(yǔ)句。

private static final String TEXT_TYPE = " TEXT";
private static final String COMMA_SEP = ",";
private static final String SQL_CREATE_ENTRIES =
    "CREATE TABLE " + FeedReaderContract.FeedEntry.TABLE_NAME + " (" +
    FeedReaderContract.FeedEntry._ID + " INTEGER PRIMARY KEY," +
    FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP +
    FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP +
    ... // Any other options for the CREATE command
    " )";

private static final String SQL_DELETE_ENTRIES =
    "DROP TABLE IF EXISTS " + TABLE_NAME_ENTRIES;

類(lèi)似于保存文件到設(shè)備的internal storage ,Android會(huì)將db保存到程序的private的空間。我們的數(shù)據(jù)是受保護(hù)的,因?yàn)槟切﹨^(qū)域默認(rèn)是私有的,不可被其他程序所訪問(wèn)。

SQLiteOpenHelper類(lèi)中有一些很有用的APIs。當(dāng)使用這個(gè)類(lèi)來(lái)做一些與db有關(guān)的操作時(shí),系統(tǒng)會(huì)對(duì)那些有可能比較耗時(shí)的操作(例如創(chuàng)建與更新等)在真正需要的時(shí)候才去執(zhí)行,而不是在app剛啟動(dòng)的時(shí)候就去做那些動(dòng)作。我們所需要做的僅僅是執(zhí)行getWritableDatabase()或者getReadableDatabase().

Note:因?yàn)槟切┎僮骺赡苁呛芎臅r(shí)的,請(qǐng)確保在background thread(AsyncTask or IntentService)里面去執(zhí)行 getWritableDatabase() 或者 getReadableDatabase() 。

為了使用 SQLiteOpenHelper, 需要?jiǎng)?chuàng)建一個(gè)子類(lèi)并重寫(xiě)onCreate()onUpgrade()onOpen()等callback方法。也許還需要實(shí)現(xiàn)onDowngrade(), 但這并不是必需的。

例如,下面是一個(gè)實(shí)現(xiàn)了SQLiteOpenHelper 類(lèi)的例子:

public class FeedReaderDbHelper extends SQLiteOpenHelper {
    // If you change the database schema, you must increment the database version.
    public static final int DATABASE_VERSION = 1;
    public static final String DATABASE_NAME = "FeedReader.db";

    public FeedReaderDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(SQL_CREATE_ENTRIES);
    }
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        // This database is only a cache for online data, so its upgrade policy is
        // to simply to discard the data and start over
        db.execSQL(SQL_DELETE_ENTRIES);
        onCreate(db);
    }
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        onUpgrade(db, oldVersion, newVersion);
    }
}

為了訪問(wèn)我們的db,需要實(shí)例化 SQLiteOpenHelper的子類(lèi):

FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());

添加信息到DB

通過(guò)傳遞一個(gè) ContentValues 對(duì)象到insert()方法:

// Gets the data repository in write mode
SQLiteDatabase db = mDbHelper.getWritableDatabase();

// Create a new map of values, where column names are the keys
ContentValues values = new ContentValues();
values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID, id);
values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title);
values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_CONTENT, content);

// Insert the new row, returning the primary key value of the new row
long newRowId;
newRowId = db.insert(
         FeedReaderContract.FeedEntry.TABLE_NAME,
         FeedReaderContract.FeedEntry.COLUMN_NAME_NULLABLE,
         values);

insert()方法的第一個(gè)參數(shù)是table名,第二個(gè)參數(shù)會(huì)使得系統(tǒng)自動(dòng)對(duì)那些ContentValues 沒(méi)有提供數(shù)據(jù)的列填充數(shù)據(jù)為null,如果第二個(gè)參數(shù)傳遞的是null,那么系統(tǒng)則不會(huì)對(duì)那些沒(méi)有提供數(shù)據(jù)的列進(jìn)行填充。

從DB中讀取信息

為了從DB中讀取數(shù)據(jù),需要使用query()方法,傳遞需要查詢(xún)的條件。查詢(xún)后會(huì)返回一個(gè) Cursor 對(duì)象。

SQLiteDatabase db = mDbHelper.getReadableDatabase();

// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
    FeedReaderContract.FeedEntry._ID,
    FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE,
    FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED,
    ...
    };

// How you want the results sorted in the resulting Cursor
String sortOrder =
    FeedReaderContract.FeedEntry.COLUMN_NAME_UPDATED + " DESC";

Cursor c = db.query(
    FeedReaderContract.FeedEntry.TABLE_NAME,  // The table to query
    projection,                               // The columns to return
    selection,                                // The columns for the WHERE clause
    selectionArgs,                            // The values for the WHERE clause
    null,                                     // don't group the rows
    null,                                     // don't filter by row groups
    sortOrder                                 // The sort order
    );

要查詢(xún)?cè)赾ursor中的行,使用cursor的其中一個(gè)move方法,但必須在讀取值之前調(diào)用。一般來(lái)說(shuō)應(yīng)該先調(diào)用moveToFirst()函數(shù),將讀取位置置于結(jié)果集最開(kāi)始的位置。對(duì)每一行,我們可以使用cursor的其中一個(gè)get方法如getString()getLong()獲取列的值。對(duì)于每一個(gè)get方法必須傳遞想要獲取的列的索引位置(index position),索引位置可以通過(guò)調(diào)用getColumnIndex()getColumnIndexOrThrow()獲得。

下面演示如何從course對(duì)象中讀取數(shù)據(jù)信息:

cursor.moveToFirst();
long itemId = cursor.getLong(
    cursor.getColumnIndexOrThrow(FeedReaderContract.FeedEntry._ID)
);

刪除DB中的信息

和查詢(xún)信息一樣,刪除數(shù)據(jù)同樣需要提供一些刪除標(biāo)準(zhǔn)。DB的API提供了一個(gè)防止SQL注入的機(jī)制來(lái)創(chuàng)建查詢(xún)與刪除標(biāo)準(zhǔn)。

SQL Injection:(隨著B(niǎo)/S模式應(yīng)用開(kāi)發(fā)的發(fā)展,使用這種模式編寫(xiě)應(yīng)用程序的程序員也越來(lái)越多。但由于程序員的水平及經(jīng)驗(yàn)也參差不齊,相當(dāng)大一部分程序員在編寫(xiě)代碼時(shí)沒(méi)有對(duì)用戶(hù)輸入數(shù)據(jù)的合法性進(jìn)行判斷,使應(yīng)用程序存在安全隱患。用戶(hù)可以提交一段數(shù)據(jù)庫(kù)查詢(xún)代碼,根據(jù)程序返回的結(jié)果,獲得某些他想得知的數(shù)據(jù),這就是所謂的SQL Injection,即SQL注入)

該機(jī)制把查詢(xún)語(yǔ)句劃分為選項(xiàng)條件與選項(xiàng)參數(shù)兩部分。條件定義了查詢(xún)的列的特征,參數(shù)用于測(cè)試是否符合前面的條款。由于處理的結(jié)果不同于通常的SQL語(yǔ)句,這樣可以避免SQL注入問(wèn)題。

// Define 'where' part of query.
String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
// Specify arguments in placeholder order.
String[] selelectionArgs = { String.valueOf(rowId) };
// Issue SQL statement.
db.delete(table_name, mySelection, selectionArgs);

更新數(shù)據(jù)

當(dāng)需要修改DB中的某些數(shù)據(jù)時(shí),使用 update() 方法。

update結(jié)合了插入與刪除的語(yǔ)法。

SQLiteDatabase db = mDbHelper.getReadableDatabase();

// New value for one column
ContentValues values = new ContentValues();
values.put(FeedReaderContract.FeedEntry.COLUMN_NAME_TITLE, title);

// Which row to update, based on the ID
String selection = FeedReaderContract.FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
String[] selectionArgs = { String.valueOf(rowId) };

int count = db.update(
    FeedReaderDbHelper.FeedEntry.TABLE_NAME,
    values,
    selection,
    selectionArgs);


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)