حفظ البيانات باستخدام SQLite

يعد حفظ البيانات في قاعدة بيانات أمرًا مثاليًا لتكرار البيانات المهيكلة، مثل معلومات الاتصال. تفترض هذه الصفحة أنك على دراية بقواعد بيانات SQL بشكل عام وتساعدك على البدء في قواعد بيانات SQLite على Android. واجهات برمجة التطبيقات التي تحتاجها لاستخدام قاعدة بيانات على أجهزة Android تتوفّر في حزمة android.database.sqlite.

تحذير: على الرغم من فعالية واجهات برمجة التطبيقات هذه، إلا أنها ذات مستوى منخفض إلى حد ما. وتتطلب قدرًا كبيرًا من الوقت والجهد لاستخدامها:

  • لا يتوفّر التحقّق في وقت التجميع لطلبات بحث SQL الأولية. استخدام البيانات في الرسم البياني، فستحتاج إلى تحديث استعلامات SQL (لغة الاستعلام البنيوية) المتأثرة يدويًا. هذا النمط يمكن أن تستغرق وقتًا طويلاً وتكون عرضة للخطأ.
  • تحتاج إلى استخدام الكثير من التعليمات البرمجية النموذجية للتحويل بين استعلامات SQL وعناصر البيانات.

لهذه الأسباب، ننصح بشدة باستخدام مكتبة استمرارية الغرفة كطبقة تجريدية للوصول إلى المعلومات في SQLite في تطبيقك لقواعد البيانات.

تحديد المخطط والعقد

أحد المبادئ الرئيسية لقواعد بيانات SQL هو المخطط: النهج الرسمي عن كيفية تنظيم قاعدة البيانات. ينعكس المخطط في لغة الاستعلامات البنيوية (SQL) العبارات التي تستخدمها لإنشاء قاعدة البيانات الخاصة بك. قد تجد أنه من المفيد إنشاء فئة مصاحبة، تُعرف باسم فئة عقد، والتي تحدّد صراحةً تخطيط المخطط بطريقة منهجية وتوثيقية.

فئة العقد هي حاوية للثوابت التي تحدّد أسماء لعناوين URL والجداول والأعمدة. تتيح لك فئة العقد استخدام نفس الثوابت عبر جميع الفئات الأخرى في نفس الحزمة. يتيح لك هذا الإجراء تغيير عمود الاسم في مكان واحد واجعله ينتشر عبر التعليمات البرمجية.

من الطرق الجيدة لتنظيم فئة العقد وضع تعريفات عامة إلى قاعدة البيانات بأكملها في المستوى الجذر للفئة. بعد ذلك، أنشئ فئة داخلية لكل جدول. تعرِض كل فئة داخلية أعمدة الجدول المقابل.

ملاحظة: من خلال تنفيذ 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 على مجموعة من واجهات برمجة التطبيقات لإدارة قاعدة البيانات لديك. عند استخدام هذه الفئة للحصول على مراجع لقاعدة بياناتك، ينفذ الإجراءات المحتملة عمليات طويلة الأمد لإنشاء قاعدة البيانات وتحديثها فقط عندما مطلوبة وليس أثناء بدء تشغيل التطبيق. ما عليك سوى الاتصال بالرقم getWritableDatabase() أو getReadableDatabase().

ملاحظة: بما أنّ هذه المعاملات قد تستغرق وقتًا طويلاً، تأكَّد من طلب getWritableDatabase() أو getReadableDatabase() في سلسلة محادثات في الخلفية. يُرجى الاطّلاع على مقالة Threading على 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() معرّف حديثًا، وإلا فسيتم عرض -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() إلى وضع "موضع القراءة" في صفحة الإدخال الأول في النتائج ويعرض ما إذا كان المؤشر قد تجاوز الإدخال الأخير بالفعل أم لا مجموعة النتائج. بالنسبة إلى كل صف، يمكنك قراءة قيمة عمود من خلال استدعاء إحدى methods Cursor للحصول على القيمة، مثل 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();
}

تصحيح أخطاء قاعدة البيانات

تتضمّن حزمة تطوير البرامج (SDK) لنظام التشغيل Android أداة واجهة sqlite3 تتيح لك التصفّح. وتشغيل أوامر SQL وتنفيذ دوال مفيدة أخرى على SQLite لقواعد البيانات. لمزيد من المعلومات، يُرجى الاطّلاع على طريقة طريقة إصدار أوامر واجهة الأوامر.