SQLite kullanarak veri tasarrufu sağlayın

Verilerin veritabanına kaydedilmesi, iletişim bilgileri gibi tekrarlanan veya yapılandırılmış veriler için idealdir. Bu sayfada SQL veritabanlarına genel olarak aşina olduğunuz varsayılır ve Android'de SQLite veritabanlarını kullanmaya başlamanıza yardımcı olur. Android'de veritabanı kullanmak için ihtiyacınız olan API'ler android.database.sqlite paketinde mevcuttur.

Dikkat: Bu API'ler güçlü olsalar da oldukça düşük seviyelidir ve çok zaman ve çaba gerektirirler:

  • Ham SQL sorgularının derleme sırasında doğrulanması gerekmez. Veri grafiğiniz değiştikçe, etkilenen SQL sorgularını manuel olarak güncellemeniz gerekir. Bu işlem zaman alıcı ve hataya açık olabilir.
  • SQL sorguları ve veri nesneleri arasında dönüşüm yapmak için çok sayıda standart kod kullanmanız gerekir.

Bu nedenlerle, uygulamanızın SQLite veritabanlarındaki bilgilere erişmek için bir soyutlama katmanı olarak Oda Kalıcılığı Kitaplığı'nı kullanmanızı önemle tavsiye ederiz.

Şema ve sözleşme tanımlayın

SQL veritabanlarının ana ilkelerinden biri, veritabanının nasıl düzenlendiğinin resmi bir beyanı olan şemadır. Şema, veritabanınızı oluşturmak için kullandığınız SQL ifadelerine yansıtılır. Şemanızın düzenini sistematik ve kendi kendini belgeleyen bir şekilde açıkça belirten ve sözleşme sınıfı olarak bilinen bir tamamlayıcı sınıf oluşturmak işinize yarayabilir.

Sözleşme sınıfı; URI'ler, tablolar ve sütunların adlarını tanımlayan sabit değerler için bir kapsayıcıdır. Sözleşme sınıfı, aynı paketteki diğer tüm sınıflarda aynı sabitleri kullanmanıza olanak tanır. Bu da sütun adını tek bir yerde değiştirmenize ve kodunuza yayılmasını sağlamanıza olanak tanır.

Bir sözleşme sınıfını düzenlemenin iyi bir yolu, tüm veritabanınız için genel olan tanımları sınıfın kök düzeyine yerleştirmektir. Sonra her tablo için bir iç sınıf oluşturun. Her iç sınıf, karşılık gelen tablonun sütunlarını numaralandırır.

Not: BaseColumns arayüzünü uygulayarak iç sınıfınız, CursorAdapter gibi bazı Android sınıflarının sahip olmasını beklediği birincil anahtar alanını _ID adlı birincil anahtar alanını devralabilir. Zorunlu değildir ancak veritabanınızın Android çerçevesiyle uyumlu bir şekilde çalışmasına yardımcı olabilir.

Örneğin, aşağıdaki sözleşmede bir RSS özet akışını temsil eden tek bir tablo için tablo adı ve sütun adları tanımlanır:

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 yardımcısı kullanarak veritabanı oluşturma

Veritabanınızın nasıl göründüğünü tanımladıktan sonra veritabanını ve tabloları oluşturup bunları koruyan yöntemler uygulamanız gerekir. Tablo oluşturan ve silen bazı tipik ifadeler şunlardır:

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;

Cihazın dahili depolama alanına kaydettiğiniz dosyalar gibi, Android de veritabanınızı uygulamanızın özel klasöründe depolar. Varsayılan olarak diğer uygulamalar veya kullanıcı bu alana erişemediğinden verileriniz güvendedir.

SQLiteOpenHelper sınıfı, veritabanınızı yönetmeniz için faydalı bir API grubu içerir. Veritabanınıza referans almak için bu sınıfı kullandığınızda, sistem yalnızca ihtiyaç duyulduğunda ve uygulama başlatılırken değil, veritabanı oluşturma ve güncelleme gibi uzun süreli olabilecek işlemleri gerçekleştirir. Tek yapmanız gereken getWritableDatabase() veya getReadableDatabase() numaralı telefonu aramak.

Not: Uzun süre çalıştırılabilecekleri için arka plan iş parçacığında getWritableDatabase() veya getReadableDatabase() değerini çağırdığınızdan emin olun. Daha fazla bilgi için Android'de Threading sayfasına bakın.

SQLiteOpenHelper kullanmak için onCreate() ve onUpgrade() geri çağırma yöntemlerini geçersiz kılan bir alt sınıf oluşturun. onDowngrade() veya onOpen() yöntemlerini de uygulamak isteyebilirsiniz, ancak bu zorunlu değildir.

Örneğin, yukarıda gösterilen komutların bazılarını kullanan bir SQLiteOpenHelper uygulamasını burada görebilirsiniz:

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

Veritabanınıza erişmek için SQLiteOpenHelper alt sınıfınızı örneklendirin:

Kotlin

val dbHelper = FeedReaderDbHelper(context)

Java

FeedReaderDbHelper dbHelper = new FeedReaderDbHelper(getContext());

Bilgileri bir veritabanına yerleştirin

insert() yöntemine bir ContentValues nesnesi ileterek veritabanına veri ekleyin:

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() için ilk bağımsız değişken basit tablo adıdır.

İkinci bağımsız değişken, çerçeveye ContentValues boşsa (yani hiç değer put vermediyseniz) ne yapılacağını bildirir. Bir sütunun adını belirtirseniz çerçeve bir satır ekler ve bu sütunun değerini null olarak ayarlar. Bu kod örneğinde olduğu gibi null belirtirseniz çerçeve, değer olmadığında satır eklemez.

insert() yöntemleri, yeni oluşturulan satırın kimliğini döndürür. Veriler eklenirken bir hata oluştuysa -1 değerini döndürür. Bu durum, veritabanındaki önceden var olan verilerle bir çakışmanız varsa yaşanabilir.

Veritabanındaki bilgileri okuma

Bir veritabanından okumak için query() yöntemini kullanarak seçim ölçütlerinizi ve istediğiniz sütunları iletin. Bu yöntem, eklenecek veriler yerine getirmek istediğiniz verileri ("projeksiyon") tanımlayan sütun listesi dışında insert() ve update() öğelerini birleştirir. Sorgunun sonuçları size bir Cursor nesnesinde döndürülür.

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

Üçüncü ve dördüncü bağımsız değişkenler (selection ve selectionArgs) bir WHERE ifadesi oluşturmak üzere birleştirilir. Bağımsız değişkenler seçim sorgusundan ayrı olarak sağlandığı için, birleştirilmeden önce atlanırlar. Böylece, seçim ifadeleriniz SQL yerleştirme işleminden etkilenmez. Tüm bağımsız değişkenler hakkında daha fazla bilgi için query() referansına bakın.

İmleçteki bir satıra bakmak için Cursor taşıma yöntemlerinden birini kullanın. Bu yöntem, değerleri okumaya başlamadan önce her zaman çağırmanız gerekir. İmleç -1 konumundan başladığından moveToNext() çağrısı, "okuma konumu"nu sonuçlardaki ilk girişe yerleştirir ve imlecin sonuç grubundaki son girişi geçip geçmediğini döndürür. Her satır için getString() veya getLong() gibi Cursor alma yöntemlerinden birini çağırarak bir sütunun değerini okuyabilirsiniz. "Get" yöntemlerinin her biri için istediğiniz sütunun dizin konumunu iletmeniz gerekir. Bunu, getColumnIndex() veya getColumnIndexOrThrow() yöntemini çağırarak alabilirsiniz. Sonuçları yinelemeyi bitirdiğinizde kaynaklarını serbest bırakmak için imleçte close() çağrısı yapın. Örneğin, aşağıda imleçte depolanan tüm öğe kimliklerinin nasıl alınacağı ve bir listeye nasıl ekleneceği gösterilmektedir:

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

Veritabanından bilgi silme

Tablodan satır silmek için delete() yönteminde satırları tanımlayan seçim ölçütleri sağlamanız gerekir. Sistem, query() yönteminin seçim bağımsız değişkenleriyle aynı şekilde çalışır. Seçim spesifikasyonunu bir seçim ifadesine ve seçim bağımsız değişkenlerine böler. Bu madde, bakılacak sütunları tanımlar ve ayrıca sütun testlerini birleştirmenize olanak tanır. Bağımsız değişkenler, yan tümceye bağlı olarak test edilecek değerlerdir. Sonuç, normal bir SQL ifadesiyle aynı şekilde işlenmediği için SQL yerleştirme işleminden etkilenmez.

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() yönteminin döndürülen değeri, veritabanından silinen satır sayısını belirtir.

Veritabanı güncelleme

Veritabanı değerlerinizin bir alt kümesini değiştirmeniz gerektiğinde update() yöntemini kullanın.

Tabloyu güncellemek, insert() için ContentValues söz dizimini delete() için WHERE söz dizimiyle birleştirir.

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() yönteminin döndürülen değeri, veritabanında etkilenen satır sayısıdır.

Veritabanı bağlantısını sürdürme

Veritabanı kapatıldığında getWritableDatabase() ve getReadableDatabase() yönteminin çağrılması pahalı olduğundan, veritabanı bağlantınızı erişime ihtiyacınız olduğu sürece açık bırakmalısınız. Genellikle en iyi yöntem, çağrı Etkinliğinin onDestroy() bölümünde veritabanını kapatmaktır.

Kotlin

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

Java

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

Veritabanınızda hata ayıklama

Android SDK, tablo içeriklerine göz atmanızı, SQL komutlarını çalıştırmanızı ve SQLite veri tabanlarında diğer yararlı işlevleri gerçekleştirmenizi sağlayan bir sqlite3 kabuk aracı içerir. Daha fazla bilgi için kabuk komutlarının nasıl gönderileceğine bakın.