שמירת נתונים במסד נתונים אידיאלית לחזרה או לנתונים מובְנים,
כמו פרטים ליצירת קשר. דף זה מניח שאתם
להכיר את מסדי נתונים של SQL באופן כללי, ועוזר לך להתחיל
מסדי נתונים של SQLite ב-Android. ממשקי ה-API הדרושים לשימוש במסד נתונים
ב-Android זמינים בחבילה של android.database.sqlite
.
זהירות: ממשקי ה-API האלה חזקים, אבל הם ברמה נמוכה למדי ונדרשים הרבה זמן ומאמצים כדי להשתמש בהם:
- אין אימות בזמן הידור של שאילתות SQL גולמיות. לפי הנתונים שלך צריך לעדכן באופן ידני את שאילתות ה-SQL המושפעות. הזה התהליך עשוי להימשך זמן רב ולהביא לשגיאות.
- צריך להשתמש בהרבה קוד סטנדרטי כדי להמיר שאילתות SQL ואובייקטים של נתונים.
לכן, מומלץ מאוד להשתמש במאפיין ספריית ההתמדה בחדר כשכבה הפשטה של גישה למידע ב-SQLite של האפליקציה מסדי נתונים.
הגדרת סכימה וחוזה
אחד העקרונות המרכזיים במסדי נתונים של SQL הוא הסכימה: הצהרה על האופן שבו מסד הנתונים מאורגן. הסכימה משתקפת ב-SQL, שבהם משתמשים כדי ליצור את מסד הנתונים. מומלץ להיעזר בתכונה ליצור מחלקה נלווית, שנקראת חוזה, שמציינת במפורש הפריסה של הסכימה באופן שיטתי ולתיעוד עצמי.
מחלקה של חוזה היא קונטיינר של קבועים שמגדירים שמות למזהי URI, הטבלאות והעמודות. מחלקה של החוזה מאפשרת להשתמש באותם קבועים בכל הכיתות האחרות באותה חבילה. כך אפשר לשנות עמודה במקום אחד, כדי שיופץ לאורך כל הקוד.
דרך טובה לארגן מחלקה של חוזה היא לציין הגדרות גלובלי בכל מסד הנתונים, ברמת השורש של המחלקה. ואז ליצור כיתה לכל טבלה. כל מחלקה פנימית סופרת את העמודות המתאימות בטבלה.
הערה: באמצעות הטמעת BaseColumns
המחלקה הפנימית שלכם יכולה לרשת
שדה מפתח שנקרא _ID
, שחלק ממחלקות Android כמו CursorAdapter
מצפים לקבל. לא חובה, אבל זה יכול לעזור לך לשפר את מסד הנתונים
פועלים בהרמוניה עם framework של 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 helper
אחרי שהגדרתם את המראה של מסד הנתונים, עליכם להטמיע שיטות ליצירה ולתחזוקה של מסד הנתונים והטבלאות. הנה כמה דוגמאות טיפוסיות הצהרות שיוצרות ומוחקות טבלה:
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()
הוא פשוט שם הטבלה.
הארגומנט השני מציין למסגרת מה לעשות במקרה
השדה ContentValues
ריק (כלומר, לא עשית זאת)
put
ערכים).
אם מציינים שם של עמודה, המסגרת מוסיפה שורה ומגדירה
שהערך של העמודה הזו הוא null. אם מציינים null
, כמו בדוגמה הבאה
דוגמת הקוד, ה-framework לא מוסיפה שורה כשאין ערכים.
ה-methods 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. מאחר שהארגומנטים מסופקים בנפרד מהבחירה
חיפוש, הם מסומנים בתווי בריחה (escape) לפני שילובם. כך הצהרות הבחירה שלכם חסנות ל-SQL
הזרקה. לפרטים נוספים על כל הארגומנטים, אפשר לעיין במאמר
מסמך עזר של query()
.
כדי להסתכל על שורה בסמן, אפשר להשתמש באחת מהתנועות של Cursor
methods, שצריך תמיד לקרוא להן לפני שמתחילים לקרוא את הערכים. מאחר שהסמן מתחיל ב-
מיקום 1-, קריאה אל moveToNext()
מציבה את 'מיקום הקריאה' ב
הכניסה הראשונה בתוצאות, ומחזירה אם הסמן כבר עבר את הערך האחרון
והתוצאה מוגדרת. בכל שורה אפשר לקרוא את הערך של עמודה מסוימת באמצעות
Cursor
אחזור שיטות, כמו getString()
או getLong()
. בכל אחת משיטות השליפה
עליך להעביר את מיקום האינדקס של העמודה הרצויה, ואפשר לקבל אותה באמצעות קריאה
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();
מחיקת מידע ממסד נתונים
כדי למחוק שורות מטבלה צריך לספק קריטריונים לבחירה
לזהות את השורות ל-method 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);
הערך המוחזר ל-method 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 כולל כלי מעטפת של sqlite3
שמאפשר לך לעיין
תוכן העניינים, הרצת פקודות SQL וביצוע פונקציות שימושיות נוספות ב-SQLite
מסדי נתונים. מידע נוסף זמין במאמר איך להנפיק פקודות מעטפת.