บันทึกข้อมูลโดยใช้ SQLite

การบันทึกข้อมูลลงในฐานข้อมูลเหมาะสำหรับการทำซ้ำหรือข้อมูลที่มีโครงสร้าง เช่น ข้อมูลติดต่อ หน้านี้ถือว่าคุณคุ้นเคยกับฐานข้อมูล 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