การบันทึกข้อมูลลงในฐานข้อมูลเหมาะสำหรับการทำซ้ำหรือข้อมูลที่มีโครงสร้าง เช่น ข้อมูลติดต่อ หน้านี้ถือว่าคุณคุ้นเคยกับฐานข้อมูล SQL โดยทั่วไปและจะช่วยคุณเริ่มต้นใช้งานฐานข้อมูล SQLite ใน Android API ที่คุณจะต้องใช้ฐานข้อมูลใน Android จะมีให้ในแพ็กเกจ android.database.sqlite
ข้อควรระวัง: แม้ว่า API เหล่านี้จะมีประสิทธิภาพ แต่ก็อยู่ในระดับที่ค่อนข้างต่ำ รวมถึงต้องใช้เวลาและความพยายามอย่างมากในการใช้งาน
- ไม่มีการยืนยันเวลาคอมไพล์สําหรับการค้นหา SQL ดิบ เมื่อกราฟข้อมูลมีการเปลี่ยนแปลง คุณต้องอัปเดตการค้นหา SQL ที่ได้รับผลกระทบด้วยตนเอง กระบวนการนี้อาจใช้เวลานานและเกิดข้อผิดพลาดได้ง่าย
- คุณต้องใช้โค้ดต้นแบบจำนวนมากเพื่อแปลงการค้นหา SQL และออบเจ็กต์ข้อมูล
ด้วยเหตุนี้ เราจึงแนะนําอย่างยิ่งให้ใช้ไลบรารีความต่อเนื่องของห้องเป็นเลเยอร์ Abstraction ในการเข้าถึงข้อมูลในฐานข้อมูล SQLite ของแอป
กำหนดสคีมาและสัญญา
หลักการหลักอย่างหนึ่งของฐานข้อมูล SQL คือสคีมา ซึ่งเป็นประกาศอย่างเป็นทางการเกี่ยวกับวิธีจัดระเบียบฐานข้อมูล สคีมาจะแสดงในคำสั่ง SQL ที่คุณใช้สร้างฐานข้อมูล คุณอาจพบว่าการสร้างคลาสที่แสดงร่วมกันที่เรียกว่าคลาส contract ซึ่งระบุเลย์เอาต์ของสคีมาอย่างชัดเจนในแบบที่เป็นระบบและมีการจัดทำเอกสารด้วยตนเอง
คลาสสัญญาคือคอนเทนเนอร์สำหรับค่าคงที่ที่กำหนดชื่อสำหรับ 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
เมื่อกําหนดลักษณะของฐานข้อมูลแล้ว คุณควรใช้เมธอดที่สร้างและดูแลรักษาฐานข้อมูลและตาราง ตัวอย่างคำสั่งทั่วไปที่สร้างและลบตารางมีดังนี้
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()
คือชื่อตาราง
อาร์กิวเมนต์ที่ 2 บอกเฟรมเวิร์กว่าต้องทำอะไรในกรณีที่ ContentValues
ว่างเปล่า (กล่าวคือ คุณไม่ได้put
ค่าใดๆ)
หากคุณระบุชื่อคอลัมน์ เฟรมเวิร์กจะแทรกแถวและตั้งค่าของคอลัมน์นั้นเป็น Null หากคุณระบุ null
อย่างเช่นในตัวอย่างโค้ดนี้ เฟรมเวิร์กจะไม่แทรกแถวเมื่อไม่มีค่า
เมธอด insert()
จะแสดงผลรหัสของแถวที่สร้างขึ้นใหม่ หรือจะแสดงผล -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 );
อาร์กิวเมนต์ที่ 3 และ 4 (selection
และ selectionArgs
) จะรวมเข้าด้วยกันเพื่อสร้างคำสั่ง WHERE เนื่องจากอาร์กิวเมนต์มีให้แยกต่างหากจากคำค้นหาการเลือก อาร์กิวเมนต์จึงถูก Escape ก่อนที่จะรวมเข้าด้วยกัน ซึ่งจะทำให้คำสั่งการเลือกของคุณไม่ได้รับผลกระทบจากการแทรก SQL ดูรายละเอียดเพิ่มเติมเกี่ยวกับอาร์กิวเมนต์ทั้งหมดได้ที่ข้อมูลอ้างอิงquery()
หากต้องการดูแถวในเคอร์เซอร์ ให้ใช้วิธีย้าย Cursor
วิธีใดวิธีหนึ่ง ซึ่งต้องเรียกใช้ทุกครั้งก่อนที่จะเริ่มอ่านค่า เนื่องจากเคอร์เซอร์เริ่มต้นที่ตำแหน่ง -1 การเรียกใช้ moveToNext()
จึงจะวาง "ตำแหน่งการอ่าน" บนรายการแรกในผลลัพธ์ และแสดงผลว่าเคอร์เซอร์เลยรายการสุดท้ายในชุดผลลัพธ์ไปแล้วหรือไม่ สําหรับแต่ละแถว คุณสามารถอ่านค่าของคอลัมน์ได้โดยเรียกใช้Cursor
เมธอด get อย่างใดอย่างหนึ่ง เช่น getString()
หรือ getLong()
สําหรับเมธอด get แต่ละรายการ คุณจะต้องส่งตําแหน่งอินเด็กซ์ของคอลัมน์ที่ต้องการ ซึ่งคุณรับได้โดยเรียกใช้ getColumnIndex()
หรือ getColumnIndexOrThrow()
เมื่อเรียกใช้ผลลัพธ์จนเสร็จแล้ว ให้เรียกใช้ close()
บนเคอร์เซอร์เพื่อปล่อยทรัพยากร
ตัวอย่างต่อไปนี้แสดงวิธีรับรหัสสินค้าทั้งหมดที่จัดเก็บไว้ในเคอร์เซอร์ และเพิ่มรหัสดังกล่าวลงในรายการ
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()
การอัปเดตตารางจะรวมไวยากรณ์ ContentValues
ของ insert()
เข้ากับไวยากรณ์ WHERE
ของ delete()
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 มีเครื่องมือ Shell ของ sqlite3
ที่ช่วยให้คุณเรียกดูเนื้อหาตาราง, เรียกใช้คำสั่ง SQL และใช้ฟังก์ชันอื่นๆ ที่เป็นประโยชน์ในฐานข้อมูล SQLite ดูข้อมูลเพิ่มเติมได้ที่วิธีออกคำสั่ง Shell