W3Cschool
恭喜您成為首批注冊(cè)用戶(hù)
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
編寫(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包中找到。
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";
...
}
}
定義好了的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());
通過(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中讀取數(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)
);
和查詢(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);
當(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);
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: