page.title=在 SQL 資料庫中儲存資料 page.tags=資料儲存空間 helpoutsWidget=true trainingnavtop=true @jd:body
對於重複資料或結構化資料 (例如連絡資訊),將其儲存至資料庫是理想的選擇。 本課程假設您已大體熟悉 SQL 資料庫,並協助您開始在 Android 上使用 SQLite 資料庫。 {@link android.database.sqlite} 套件中提供在 Android 上使用資料庫所需的 API。
SQL 資料庫的其中一項主要準則是結構描述,即針對資料庫組織方式的正式宣告。 在您建立資料庫所用的 SQL 陳述式中,會反映結構描述。 您可能會發現,建立伴隨類別 (也稱為合約類別) 會非常有益,該類別會以系統化的自我記錄方式,明確指定結構描述的配置。
合約類別是常數 (可為 URI、表格與欄定義名稱) 的容器。 藉由合約類別,您可在同一套件內的所有其他類別中使用相同的常數。 您可藉此在一個位置變更欄名稱,然後將其傳播到全部程式碼中。
組織合約類別的良好方式,是將適用於整個資料庫的全域定義置於類別的根層級, 然後,針對列舉欄的每個表格建立內部類別。
注意:透過實作 {@link android.provider.BaseColumns} 介面,您的內部類別可繼承主索引鍵欄位 (稱為 {@code _ID}),某些 Android 類別 (諸如 cursor adaptor) 希望其具備該欄位。 此操作並非必需的操作,但是可協助您的資料庫與 Android 架構協調運作。
例如,以下程式碼片段會定義單一表格的表格名稱與欄名稱:
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"; ... } }
定義資料庫的外觀之後,您應實作多種方法以建立並維護資料庫與表格。 以下所示是可建立及刪除表格的某些典型陳述式:
private static final String TEXT_TYPE = " TEXT"; private static final String COMMA_SEP = ","; private static final String SQL_CREATE_ENTRIES = "CREATE TABLE " + FeedEntry.TABLE_NAME + " (" + FeedEntry._ID + " INTEGER PRIMARY KEY," + FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP + 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 " + FeedEntry.TABLE_NAME;
如同在裝置的內部儲存空間儲存檔案一樣,Android 會將您的資料庫儲存在與應用程式關聯的私用磁碟空間內。 依預設,其他應用程式無法存取此區域,因此您的資料安全無虞。
{@link android.database.sqlite.SQLiteOpenHelper} 類別中提供一組有用的 API。若使用此類別取得資料庫的參考,只有在需要執行且並非處於應用程式啟動期間時,系統才會執行資料庫的建立與更新操作 (執行時間可能很長)。 您只需呼叫 {@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase} 或 {@link android.database.sqlite.SQLiteOpenHelper#getReadableDatabase} 即可。
注意:由於這些操作的時間可能很長,因此請確保您在背景執行緒 (例如 {@link android.os.AsyncTask} 或 {@link android.app.IntentService}) 中呼叫 {@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase} 或 {@link android.database.sqlite.SQLiteOpenHelper#getReadableDatabase}。
若要使用 {@link android.database.sqlite.SQLiteOpenHelper},請建立可覆寫 {@link android.database.sqlite.SQLiteOpenHelper#onCreate onCreate()}、{@link android.database.sqlite.SQLiteOpenHelper#onUpgrade onUpgrade()} 與 {@link android.database.sqlite.SQLiteOpenHelper#onOpen onOpen()} 回呼方法的子類別。 您還可以實作 {@link android.database.sqlite.SQLiteOpenHelper#onDowngrade onDowngrade()},但這並非必需的操作。
例如,以下展示了 {@link android.database.sqlite.SQLiteOpenHelper} (使用上述某些命令) 的實作:
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); } }
若要存取您的資料庫,請啟動 {@link android.database.sqlite.SQLiteOpenHelper} 的子類別:
FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());
透過將 {@link android.content.ContentValues} 物件傳遞至 {@link android.database.sqlite.SQLiteDatabase#insert 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(FeedEntry.COLUMN_NAME_ENTRY_ID, id); values.put(FeedEntry.COLUMN_NAME_TITLE, title); values.put(FeedEntry.COLUMN_NAME_CONTENT, content); // Insert the new row, returning the primary key value of the new row long newRowId; newRowId = db.insert( FeedEntry.TABLE_NAME, FeedEntry.COLUMN_NAME_NULLABLE, values);
{@link android.database.sqlite.SQLiteDatabase#insert insert()} 的第一個引數即為表格名稱。 第二個引數將提供欄的名稱, 在 {@link android.content.ContentValues} 為空時,架構可在該欄中插入 NULL (若您將其設為 {@code "null"},則在無值時,架構不會插入列)。
若要從資料庫進行讀取,請使用 {@link android.database.sqlite.SQLiteDatabase#query query()} 方法,然後向其傳遞您的選取條件與所需的欄。該方法會合併 {@link android.database.sqlite.SQLiteDatabase#insert insert()} 與 {@link android.database.sqlite.SQLiteDatabase#update update()} 的元素,對您希望擷取的資料 (而非要插入的資料) 進行定義的欄清單除外。 將在 {@link android.database.Cursor} 物件中,為您傳回查詢結果。
SQLiteDatabase db = mDbHelper.getReadableDatabase(); // Define a projection that specifies which columns from the database // you will actually use after this query. String[] projection = { FeedEntry._ID, FeedEntry.COLUMN_NAME_TITLE, FeedEntry.COLUMN_NAME_UPDATED, ... }; // How you want the results sorted in the resulting Cursor String sortOrder = FeedEntry.COLUMN_NAME_UPDATED + " DESC"; Cursor c = db.query( 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 );
若要查看游標指示的列,請使用其中一種 {@link android.database.Cursor} move 方法,您必須始終先呼叫該方法,然後再開始讀取值。 一般而言,您應呼叫 {@link android.database.Cursor#moveToFirst} 來執行啟動,如此會將「讀取位置」置於結果中的第一個項目。 對於每列,您可以呼叫其中一種 {@link android.database.Cursor} get 方法 (例如 {@link android.database.Cursor#getString getString()} 或 {@link android.database.Cursor#getLong getLong()}),以讀取欄的值。 對於每種 get 方法,您必須傳遞所需欄的索引位置,可以呼叫 {@link android.database.Cursor#getColumnIndex getColumnIndex()} 或 {@link android.database.Cursor#getColumnIndexOrThrow getColumnIndexOrThrow()} 取得該位置。例如:
cursor.moveToFirst(); long itemId = cursor.getLong( cursor.getColumnIndexOrThrow(FeedEntry._ID) );
若要刪除表格中的列,您需要提供識別這些列的選取條件。 資料庫 API 可提供建立選取條件的機制 (能防止 SQL 插入)。 該機制會將選取規格分為選取子句與選取引數。 子句可定義要查看的欄,您也可以藉此合併欄測試。 引數是要測試的值,繫結在子句中。由於對結果的處理方式不同於規則 SQL 陳述式,因此結果不會遭受 SQL 插入。
// Define 'where' part of query. String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; // Specify arguments in placeholder order. String[] selectionArgs = { String.valueOf(rowId) }; // Issue SQL statement. db.delete(table_name, selection, selectionArgs);
若您需要修改資料庫值的子集,請使用 {@link android.database.sqlite.SQLiteDatabase#update update()} 方法。
更新表格會合併 {@link android.database.sqlite.SQLiteDatabase#insert insert()} 的內容值語法與 {@link android.database.sqlite.SQLiteDatabase#delete delete()} 的 {@code where} 語法。
SQLiteDatabase db = mDbHelper.getReadableDatabase(); // New value for one column ContentValues values = new ContentValues(); values.put(FeedEntry.COLUMN_NAME_TITLE, title); // Which row to update, based on the ID String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?"; String[] selectionArgs = { String.valueOf(rowId) }; int count = db.update( FeedReaderDbHelper.FeedEntry.TABLE_NAME, values, selection, selectionArgs);