บันทึกข้อมูลโดยใช้ 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

KotlinJava
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"
   
}
}
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

เมื่อกําหนดลักษณะของฐานข้อมูลแล้ว คุณควรใช้เมธอดที่สร้างและดูแลรักษาฐานข้อมูลและตาราง ตัวอย่างคำสั่งทั่วไปที่สร้างและลบตารางมีดังนี้

KotlinJava
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}"
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 ที่ใช้คำสั่งบางส่วนที่แสดงด้านบน

KotlinJava
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"
   
}
}
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

KotlinJava
val dbHelper = FeedReaderDbHelper(context)
FeedReaderDbHelper dbHelper = new FeedReaderDbHelper(getContext());

ใส่ข้อมูลลงในฐานข้อมูล

แทรกข้อมูลลงในฐานข้อมูลโดยการส่งออบเจ็กต์ ContentValues ไปยังเมธอด insert() ดังนี้

KotlinJava
// 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)
// 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

KotlinJava
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
)
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() บนเคอร์เซอร์เพื่อปล่อยทรัพยากร ตัวอย่างต่อไปนี้แสดงวิธีรับรหัสสินค้าทั้งหมดที่จัดเก็บไว้ในเคอร์เซอร์ และเพิ่มรหัสดังกล่าวลงในรายการ

KotlinJava
val itemIds = mutableListOf<Long>()
with
(cursor) {
   
while (moveToNext()) {
       
val itemId = getLong(getColumnIndexOrThrow(BaseColumns._ID))
        itemIds
.add(itemId)
   
}
}
cursor
.close()
List itemIds = new ArrayList<>();
while(cursor.moveToNext()) {
 
long itemId = cursor.getLong(
      cursor
.getColumnIndexOrThrow(FeedEntry._ID));
  itemIds
.add(itemId);
}
cursor
.close();

ลบข้อมูลออกจากฐานข้อมูล

หากต้องการลบแถวออกจากตาราง คุณต้องระบุเกณฑ์การเลือกที่จะระบุแถวให้กับเมธอด delete() กลไกนี้ทำงานเหมือนกับอาร์กิวเมนต์การเลือกของเมธอด query() โดยจะแบ่งข้อกําหนดการเลือกออกเป็นคำสั่งการเลือกและอาร์กิวเมนต์การเลือก ข้อกำหนดเหล่านี้จะกำหนดคอลัมน์ที่จะดูข้อมูลและยังช่วยให้คุณรวมการทดสอบคอลัมน์ได้ด้วย อาร์กิวเมนต์คือค่าที่จะทดสอบซึ่งเชื่อมโยงกับประโยค เนื่องจากผลลัพธ์ไม่ได้รับการจัดการเหมือนกับคำสั่ง SQL ปกติ จึงมีภูมิคุ้มกันต่อการแทรก SQL

KotlinJava
// 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)
// 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()

KotlinJava
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
)
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() ของกิจกรรมการเรียกใช้จะเป็นการดีที่สุด

KotlinJava
override fun onDestroy() {
    dbHelper
.close()
   
super.onDestroy()
}
@Override
protected void onDestroy() {
    dbHelper
.close();
   
super.onDestroy();
}

แก้ไขข้อบกพร่องของฐานข้อมูล

Android SDK มีเครื่องมือ Shell ของ sqlite3 ที่ช่วยให้คุณเรียกดูเนื้อหาตาราง, เรียกใช้คำสั่ง SQL และใช้ฟังก์ชันอื่นๆ ที่เป็นประโยชน์ในฐานข้อมูล SQLite ดูข้อมูลเพิ่มเติมได้ที่วิธีออกคำสั่ง Shell