對於重複資料或結構化資料 (如聯絡資訊),將資料儲存至資料庫是理想的選擇。本頁面假設您熟悉 SQL 資料庫的一般知識,並協助您開始使用 Android 上的 SQLite 資料庫。android.database.sqlite
套件提供在 Android 上使用資料庫所需的 API。
注意:這些 API 雖然非常強大,卻非常低階,且需要大量時間和精力:
- 原始 SQL 查詢無法進行編譯時間驗證。資料圖發生變更時,必須手動更新受影響的 SQL 查詢。這個程序可能很耗時且容易出錯。
- 您必須使用大量樣板程式碼,才能在 SQL 查詢與資料物件之間進行轉換。
因此,我們強烈建議使用 Room 持續性資料庫做為抽象層,用於存取應用程式 SQLite 資料庫中的資訊。
定義結構定義和合約
SQL 資料庫的主要原理之一就是結構定義:資料庫組織方式的正式宣告。結構定義反映在您用於建立資料庫的 SQL 陳述式中。建議您建立稱為合約的隨附類別,這樣就能以系統性及自行記錄的方式,明確指定結構定義的版面配置。
合約類別是一種容器,用於容納定義 URI、資料表和資料欄名稱的常數。藉助合約類別,您可以對同一套件中的所有其他類別使用相同的常數。這可讓您在一處變更資料欄名稱,並在程式碼中全面散佈。
組織合約類別的絕佳方式是,將全域層級的定義,置於整個資料庫類別的根目錄層級。然後為每個資料表建立內部類別。每個內部類別會列舉對應的資料表。
注意:實作 BaseColumns
介面,您的內部類別可能會繼承稱為 _ID
的主鍵欄位,某些 Android 類別 (如 CursorAdapter
) 可能具有該欄位。雖然這並非必要,但有助於讓資料庫與 Android 架構協調運作。
舉例來說,下列合約定義了代表 RSS 動態消息單一資料表的資料欄名稱和資料欄名稱︰
Kotlin
object FeedReaderContract { // Table contents are grouped together in an anonymous object. object FeedEntry : BaseColumns { const val TABLE_NAME = "entry" const val COLUMN_NAME_TITLE = "title" const val COLUMN_NAME_SUBTITLE = "subtitle" } }
Java
public final class FeedReaderContract { // To prevent someone from accidentally instantiating the contract class, // make the constructor private. private FeedReaderContract() {} /* Inner class that defines the table contents */ public static class FeedEntry implements BaseColumns { public static final String TABLE_NAME = "entry"; public static final String COLUMN_NAME_TITLE = "title"; public static final String COLUMN_NAME_SUBTITLE = "subtitle"; } }
使用 SQL helper 建立資料庫
定義資料庫的外觀之後,請實作建立及維護資料庫和資料表的方法。以下列出了一些建立及刪除資料表的常見陳述式:
Kotlin
private const val SQL_CREATE_ENTRIES = "CREATE TABLE ${FeedEntry.TABLE_NAME} (" + "${BaseColumns._ID} INTEGER PRIMARY KEY," + "${FeedEntry.COLUMN_NAME_TITLE} TEXT," + "${FeedEntry.COLUMN_NAME_SUBTITLE} TEXT)" private const val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS ${FeedEntry.TABLE_NAME}"
Java
private static final String SQL_CREATE_ENTRIES = "CREATE TABLE " + FeedEntry.TABLE_NAME + " (" + FeedEntry._ID + " INTEGER PRIMARY KEY," + FeedEntry.COLUMN_NAME_TITLE + " TEXT," + FeedEntry.COLUMN_NAME_SUBTITLE + " TEXT)"; private static final String SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;
就像儲存在裝置內部儲存空間中的檔案一樣,Android 會將資料庫儲存在應用程式的私人資料夾中。因為根據預設,其他應用程式或使用者無法存取這個區域,因此您的資料很安全。
SQLiteOpenHelper
類別包含一組實用 API,可用於管理您的資料庫。使用此類別取得資料庫的參考資料時,僅在必要時,而不是在應用程式啟動期間,才會執行可能需要長時間執行的資料庫建立和更新作業。您只需要呼叫 getWritableDatabase()
或 getReadableDatabase()
即可。
注意:由於可能要長期執行,請務必在背景執行緒中呼叫 getWritableDatabase()
或 getReadableDatabase()
。詳情請參閱 Android 的執行緒作業。
如要使用 SQLiteOpenHelper
,請建立覆寫 onCreate()
和 onUpgrade()
回呼方法的子類別。另外,您可能還想使用 onDowngrade()
或 onOpen()
方法,但並非必要。
例如,下面列出了使用上述某些指令的 SQLiteOpenHelper
實作:
Kotlin
class FeedReaderDbHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { override fun onCreate(db: SQLiteDatabase) { db.execSQL(SQL_CREATE_ENTRIES) } override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { // 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) } override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { onUpgrade(db, oldVersion, newVersion) } companion object { // If you change the database schema, you must increment the database version. const val DATABASE_VERSION = 1 const val DATABASE_NAME = "FeedReader.db" } }
Java
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); } }
如要存取資料庫,請將 SQLiteOpenHelper
的子類別執行個體化:
Kotlin
val dbHelper = FeedReaderDbHelper(context)
Java
FeedReaderDbHelper dbHelper = new FeedReaderDbHelper(getContext());
將資訊放入資料庫中
將 ContentValues
物件傳遞至 insert()
方法,藉此在資料庫中插入資料:
Kotlin
// Gets the data repository in write mode val db = dbHelper.writableDatabase // Create a new map of values, where column names are the keys val values = ContentValues().apply { put(FeedEntry.COLUMN_NAME_TITLE, title) put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle) } // Insert the new row, returning the primary key value of the new row val newRowId = db?.insert(FeedEntry.TABLE_NAME, null, values)
Java
// Gets the data repository in write mode SQLiteDatabase db = dbHelper.getWritableDatabase(); // Create a new map of values, where column names are the keys ContentValues values = new ContentValues(); values.put(FeedEntry.COLUMN_NAME_TITLE, title); values.put(FeedEntry.COLUMN_NAME_SUBTITLE, subtitle); // Insert the new row, returning the primary key value of the new row long newRowId = db.insert(FeedEntry.TABLE_NAME, null, values);
insert()
的第一個引數只是資料表名稱。
第二個引數則向架構說明,在 ContentValues
為空白時 (即未輸入任何 put
值) 要怎麼做。如果指定資料欄名稱,則架構會插入資料列,並將該資料欄的值設為空值。如果指定 null
(就像此程式碼範例),架構不會在沒有值時插入資料列。
insert()
方法會傳回新建立的資料列 ID,如果插入資料時發生錯誤,則會傳回 -1。資料庫中的現有資料若發生衝突,可能發生此情況。
讀取資料庫中的資訊
如要讀取資料庫,請使用 query()
方法,將選取條件和所需資料欄傳遞給資料庫。該方法會結合 insert()
和 update()
的元素,但資料欄清單會定義您要擷取的資料 (「預測值」),而不是要插入的資料。查詢結果會以 Cursor
物件傳回給您。
Kotlin
val db = dbHelper.readableDatabase // Define a projection that specifies which columns from the database // you will actually use after this query. val projection = arrayOf(BaseColumns._ID, FeedEntry.COLUMN_NAME_TITLE, FeedEntry.COLUMN_NAME_SUBTITLE) // Filter results WHERE "title" = 'My Title' val selection = "${FeedEntry.COLUMN_NAME_TITLE} = ?" val selectionArgs = arrayOf("My Title") // How you want the results sorted in the resulting Cursor val sortOrder = "${FeedEntry.COLUMN_NAME_SUBTITLE} DESC" val cursor = db.query( FeedEntry.TABLE_NAME, // The table to query projection, // The array of columns to return (pass null to get all) 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 )
Java
SQLiteDatabase db = dbHelper.getReadableDatabase(); // Define a projection that specifies which columns from the database // you will actually use after this query. String[] projection = { BaseColumns._ID, FeedEntry.COLUMN_NAME_TITLE, FeedEntry.COLUMN_NAME_SUBTITLE }; // Filter results WHERE "title" = 'My Title' String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?"; String[] selectionArgs = { "My Title" }; // How you want the results sorted in the resulting Cursor String sortOrder = FeedEntry.COLUMN_NAME_SUBTITLE + " DESC"; Cursor cursor = db.query( FeedEntry.TABLE_NAME, // The table to query projection, // The array of columns to return (pass null to get all) 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 );
第三個引數和第四個引數 (selection
和 selectionArgs
) 會合併以建立 WHERE 子句。由於引數與選項查詢分開提供,所以會在合併之前逸出。這會讓選擇陳述式不受 SQL 插入的影響。如要進一步瞭解所有引數,請參閱 query()
參考資料。
如要查看遊標中的資料列,請使用其中一種 Cursor
移動方法,您必須在讀取值前一律呼叫此方法。由於遊標從位置 -1 開始,呼叫 moveToNext()
會將「讀取位置」放置在結果的第一個項目,並傳回游標是否已超過結果集中的最後一個項目。針對每個資料列,您可以呼叫其中一種 Cursor
get 方法 (如 getString()
或 getLong()
) 來讀取資料欄的值。針對每種 get 方法,您必須傳遞所需資料欄的索引位置。如要取得該資料欄,請呼叫 getColumnIndex()
或 getColumnIndexOrThrow()
。反覆查看結果之後,呼叫遊標上的 close()
即可釋出資源。例如,以下範例說明了如何取得遊標中的所有項目 ID,並將其加入清單中:
Kotlin
val itemIds = mutableListOf<Long>() with(cursor) { while (moveToNext()) { val itemId = getLong(getColumnIndexOrThrow(BaseColumns._ID)) itemIds.add(itemId) } } cursor.close()
Java
List itemIds = new ArrayList<>(); while(cursor.moveToNext()) { long itemId = cursor.getLong( cursor.getColumnIndexOrThrow(FeedEntry._ID)); itemIds.add(itemId); } cursor.close();
刪除資料庫中的資訊
如要從資料表中刪除資料列,必須向 delete()
方法提供可識別資料列的識別條件。該機制的運作方式與在 query()
方法中選擇引數相同。這會將選擇規格分成選擇子句和選擇引數。子句定義要查看的資料欄,還可讓您合併資料欄測試。引數是繫結至該子句要進行測試的值。由於結果的處理方式與一般 SQL 陳述式不同,因此不受 SQL 植入影像。
Kotlin
// Define 'where' part of query. val selection = "${FeedEntry.COLUMN_NAME_TITLE} LIKE ?" // Specify arguments in placeholder order. val selectionArgs = arrayOf("MyTitle") // Issue SQL statement. val deletedRows = db.delete(FeedEntry.TABLE_NAME, selection, selectionArgs)
Java
// Define 'where' part of query. String selection = FeedEntry.COLUMN_NAME_TITLE + " LIKE ?"; // Specify arguments in placeholder order. String[] selectionArgs = { "MyTitle" }; // Issue SQL statement. int deletedRows = db.delete(FeedEntry.TABLE_NAME, selection, selectionArgs);
delete()
方法的傳回值指示從資料庫刪除的資料列數量。
更新資料庫
如要修改資料庫值子集,請使用 update()
方法。
更新資料表時,系統會將 insert()
的 ContentValues
語法與 delete()
的 WHERE
語法相結合。
Kotlin
val db = dbHelper.writableDatabase // New value for one column val title = "MyNewTitle" val values = ContentValues().apply { put(FeedEntry.COLUMN_NAME_TITLE, title) } // Which row to update, based on the title val selection = "${FeedEntry.COLUMN_NAME_TITLE} LIKE ?" val selectionArgs = arrayOf("MyOldTitle") val count = db.update( FeedEntry.TABLE_NAME, values, selection, selectionArgs)
Java
SQLiteDatabase db = dbHelper.getWritableDatabase(); // New value for one column String title = "MyNewTitle"; ContentValues values = new ContentValues(); values.put(FeedEntry.COLUMN_NAME_TITLE, title); // Which row to update, based on the title String selection = FeedEntry.COLUMN_NAME_TITLE + " LIKE ?"; String[] selectionArgs = { "MyOldTitle" }; int count = db.update( FeedReaderDbHelper.FeedEntry.TABLE_NAME, values, selection, selectionArgs);
update()
方法的傳回值是指資料庫中受影響的資料列數量。
永久資料庫連接
由於 getWritableDatabase()
和 getReadableDatabase()
在資料庫關閉時呼叫成本昂貴,因此,建議您僅在需要存取資料庫時保持連線開啟。通常,最好在呼叫活動的 onDestroy()
中關閉資料庫。
Kotlin
override fun onDestroy() { dbHelper.close() super.onDestroy() }
Java
@Override protected void onDestroy() { dbHelper.close(); super.onDestroy(); }
對資料庫進行偵錯
Android SDK 包含 sqlite3
殼層工具,可讓您瀏覽資料表內容、執行 SQL 指令,以及在 SQLite 資料庫中執行其他實用函式。詳情請參閱如何發出殼層指令。