داده ها را با استفاده از SQLite ذخیره کنید

ذخیره داده ها در پایگاه داده برای داده های تکراری یا ساختار یافته، مانند اطلاعات تماس، ایده آل است. این صفحه فرض می کند که شما به طور کلی با پایگاه های داده SQL آشنا هستید و به شما کمک می کند تا با پایگاه های داده SQLite در اندروید شروع کنید. APIهایی که برای استفاده از پایگاه داده در Android نیاز دارید در بسته android.database.sqlite موجود هستند.

احتیاط: اگرچه این APIها قدرتمند هستند، اما نسبتاً سطح پایینی دارند و برای استفاده به زمان و تلاش زیادی نیاز دارند:

  • هیچ تأیید زمان کامپایل پرس و جوهای SQL خام وجود ندارد. همانطور که نمودار داده شما تغییر می کند، باید پرس و جوهای SQL آسیب دیده را به صورت دستی به روز کنید. این فرآیند می تواند زمان بر و مستعد خطا باشد.
  • برای تبدیل بین پرس و جوهای SQL و اشیاء داده باید از کدهای boilerplate زیادی استفاده کنید.

به این دلایل، ما به شدت توصیه می‌کنیم که از Room Persistence Library به عنوان یک لایه انتزاعی برای دسترسی به اطلاعات در پایگاه‌های داده SQLite برنامه خود استفاده کنید.

طرح و قرارداد را تعریف کنید

یکی از اصول اصلی پایگاه های داده SQL طرح واره است: یک اعلام رسمی از نحوه سازماندهی پایگاه داده. این طرح در دستورات SQL که برای ایجاد پایگاه داده خود استفاده می کنید منعکس می شود. ممکن است برای شما مفید باشد که یک کلاس همراه، به نام کلاس قرارداد ، که به صراحت طرح طرح شما را به روشی سیستماتیک و مستندسازی مشخص می کند، مفید باشد.

کلاس قرارداد محفظه ای برای ثابت هاست که نام URI ها، جداول و ستون ها را تعریف می کند. کلاس قرارداد به شما امکان می دهد از ثابت های یکسانی در تمام کلاس های دیگر در همان بسته استفاده کنید. این به شما امکان می دهد نام ستون را در یک مکان تغییر دهید و آن را در سراسر کد خود منتشر کنید.

یک راه خوب برای سازماندهی کلاس قرارداد این است که تعاریفی را که برای کل پایگاه داده شما جهانی هستند در سطح ریشه کلاس قرار دهید. سپس برای هر جدول یک کلاس داخلی ایجاد کنید. هر کلاس داخلی ستون های جدول مربوطه را بر می شمارد.

توجه: با پیاده سازی رابط BaseColumns ، کلاس داخلی شما می تواند یک فیلد کلید اصلی به نام _ID را که برخی از کلاس های اندروید مانند CursorAdapter انتظار دارند، به ارث ببرند. لازم نیست، اما این می تواند به پایگاه داده شما کمک کند تا با فریم ورک اندروید هماهنگ باشد.

به عنوان مثال، قرارداد زیر نام جدول و نام ستون را برای یک جدول واحد که یک فید RSS را نشان می دهد، تعریف می کند:

کاتلین

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 helper یک پایگاه داده ایجاد کنید

هنگامی که نحوه ظاهر پایگاه داده خود را مشخص کردید، باید روش هایی را برای ایجاد و نگهداری پایگاه داده و جداول پیاده سازی کنید. در اینجا چند عبارت معمولی وجود دارد که یک جدول را ایجاد و حذف می کند:

کاتلین

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() را در یک رشته پس زمینه فراخوانی کنید. برای اطلاعات بیشتر به Threading در اندروید مراجعه کنید.

برای استفاده از SQLiteOpenHelper ، یک زیرکلاس ایجاد کنید که متدهای onCreate() و onUpgrade() را باطل می کند. همچنین ممکن است بخواهید متدهای onDowngrade() یا onOpen() را پیاده سازی کنید، اما نیازی به آنها نیست.

به عنوان مثال، در اینجا پیاده سازی SQLiteOpenHelper است که از برخی از دستورات نشان داده شده در بالا استفاده می کند:

کاتلین

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 خود را نمونه سازی کنید:

کاتلین

val dbHelper = FeedReaderDbHelper(context)

جاوا

FeedReaderDbHelper dbHelper = new FeedReaderDbHelper(getContext());

اطلاعات را در یک پایگاه داده قرار دهید

با ارسال یک شی ContentValues ​​به متد insert() داده ها را در پایگاه داده وارد کنید:

کاتلین

// 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() به سادگی نام جدول است.

آرگومان دوم به فریمورک می گوید که در صورت خالی بودن ContentValues ​​چه کاری انجام دهد (یعنی شما هیچ مقداری put نداده اید). اگر نام یک ستون را مشخص کنید، فریمورک یک ردیف را وارد می کند و مقدار آن ستون را null می کند. اگر null مشخص کنید، مانند این نمونه کد، فریم ورک وقتی هیچ مقداری وجود ندارد، ردیفی را وارد نمی‌کند.

متدهای insert() شناسه ردیف تازه ایجاد شده را برمی‌گرداند، یا اگر در درج داده‌ها خطایی وجود داشته باشد، -1 را برمی‌گرداند. اگر با داده های از قبل موجود در پایگاه داده تضاد داشته باشید، ممکن است این اتفاق بیفتد.

خواندن اطلاعات از پایگاه داده

برای خواندن از پایگاه داده، از متد query() استفاده کنید و معیارهای انتخاب و ستون های مورد نظر خود را به آن ارسال کنید. این متد عناصر insert() و update() را با هم ترکیب می کند، به جز اینکه لیست ستونی، داده هایی را که می خواهید واکشی کنید ("پروژه")، به جای داده هایی که قرار است درج کنید، تعریف می کند. نتایج پرس و جو در یک شی Cursor به شما برگردانده می شود.

کاتلین

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
    );

آرگومان های سوم و چهارم ( selection و selectionArgs ) برای ایجاد یک عبارت WHERE ترکیب می شوند. از آنجایی که آرگومان ها جدا از کوئری انتخاب ارائه می شوند، قبل از ترکیب شدن از آنها فرار می کنند. این باعث می شود که بیانیه های انتخاب شما در برابر تزریق SQL مصون باشند. برای جزئیات بیشتر در مورد همه آرگومان ها، به مرجع query() مراجعه کنید.

برای مشاهده یک ردیف در مکان نما، از یکی از روش های حرکت Cursor استفاده کنید، که همیشه باید قبل از شروع خواندن مقادیر، آن را فراخوانی کنید. از آنجایی که مکان نما از موقعیت -1 شروع می شود، فراخوانی moveToNext() "موقعیت خواندن" را در اولین ورودی نتایج قرار می دهد و برمی گرداند که آیا مکان نما از آخرین ورودی مجموعه نتایج گذشته است یا نه. برای هر سطر، می‌توانید مقدار یک ستون را با فراخوانی یکی از متدهای Cursor get، مانند getString() یا getLong() بخوانید. برای هر یک از متدهای get، باید موقعیت شاخص ستون مورد نظر خود را پاس کنید، که می توانید با فراخوانی getColumnIndex() یا getColumnIndexOrThrow() دریافت کنید. پس از پایان یافتن تکرار در نتایج، برای آزاد کردن منابع آن، close() روی مکان نما فراخوانی کنید. برای مثال، موارد زیر نشان می‌دهد که چگونه می‌توان همه شناسه‌های آیتم‌های ذخیره‌شده در مکان‌نما را دریافت کرد و آنها را به فهرست اضافه کرد:

کاتلین

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 مصون است.

کاتلین

// 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() ترکیب می کند.

کاتلین

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() Activity فراخوانی بهینه است.

کاتلین

override fun onDestroy() {
    dbHelper.close()
    super.onDestroy()
}

جاوا

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

دیتابیس خود را دیباگ کنید

Android SDK شامل یک ابزار پوسته sqlite3 است که به شما امکان می دهد محتویات جدول را مرور کنید، دستورات SQL را اجرا کنید و سایر عملکردهای مفید را در پایگاه های داده SQLite انجام دهید. برای اطلاعات بیشتر، نحوه صدور دستورات پوسته را ببینید.