Articles

Zapisywanie danych za pomocą SQLite

Zapisywanie danych do bazy danych jest idealne dla powtarzających się lub ustrukturyzowanych danych, takich jak informacje kontaktowe. Ta strona zakłada, że jesteś zaznajomiony z bazami danych SQL w ogóle i pomoże Ci rozpocząć pracę z bazami danychSQLite w systemie Android. Interfejsy API, których będziesz potrzebował do korzystania z baz danych w systemie Android są dostępne w pakiecie android.database.sqlite.

Uwaga: Chociaż te interfejsy API są potężne, są one dość niskopoziomowe i wymagają dużo czasu i wysiłku, aby z nich korzystać:

  • Nie ma weryfikacji w czasie kompilacji surowych zapytań SQL. Gdy wykres danych ulega zmianie, musisz ręcznie zaktualizować zapytania SQL, których to dotyczy. Proces ten może być czasochłonny i podatny na błędy.
  • Musisz użyć wielu szablonów kodu do konwersji między zapytaniami SQL i obiektami danych.

Z tych powodów zdecydowanie zalecamy używanie Room Persistence Library jako warstwy abstrakcji dla dostępu do informacji w bazach danych SQLite Twojej aplikacji.

Zdefiniuj schemat i kontrakt

Jedną z głównych zasad baz danych SQL jest schemat: formalna deklaracja tego, jak baza danych jest zorganizowana. Schemat jest odzwierciedlony w instrukcjach SQL, których używasz do tworzenia bazy danych. Pomocne może okazać się stworzenie klasy towarzyszącej, zwanej klasą kontraktową, która jawnie określa układ schematu w sposób systematyczny i samodokumentujący.

Klasa kontraktowa jest kontenerem dla stałych, które definiują nazwy dla URI, tabel i kolumn. Klasa kontraktowa pozwala na używanie tych samych stałych we wszystkich innych klasach w tym samym pakiecie. Pozwala to na zmianę nazwy kolumny w jednym miejscu i rozpropagowanie jej w całym kodzie.

Dobrym sposobem na zorganizowanie klasy kontraktowej jest umieszczenie definicji, które są globalne dla całej bazy danych w głównym poziomie klasy. Następnie należy utworzyć klasę wewnętrzną dla każdej tabeli. Każda klasa wewnętrzna wylicza kolumny odpowiadającej jej tabeli.

Uwaga: Implementując interfejs BaseColumns, twoja klasa wewnętrzna może odziedziczyć pole primarykey o nazwie _ID, które niektóre klasy Androida, takie jak CursorAdapter, oczekują, że będzie posiadać. Nie jest to wymagane, ale może to pomóc twojemu databasework harmonijnie z frameworkiem Androida.

Na przykład, poniższy kontrakt definiuje nazwę tabeli i nazwy kolumn dla pojedynczej tabeli reprezentującej kanał 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"; }}

Tworzenie bazy danych przy użyciu helpera SQL

Po zdefiniowaniu, jak wygląda twoja baza danych, powinieneś zaimplementować metody, które tworzą i utrzymują bazę danych i tabele. Oto kilka typowych instrukcji, które tworzą i usuwają tabelę:

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;

Tak jak pliki, które zapisujesz na wewnętrznej pamięci urządzenia, Android przechowuje twoją bazę danych w prywatnym folderze twojej aplikacji. Twoje dane są bezpieczne, ponieważ domyślnie ten obszar nie jest dostępny dla innych aplikacji ani dla użytkownika.

Klasa SQLiteOpenHelper zawiera przydatny zestaw API do zarządzania bazą danych.Kiedy używasz tej klasy, aby uzyskać referencje do bazy danych, system wykonuje potencjalnie długotrwałe operacje tworzenia i aktualizowania bazy danych tylko wtedy, gdy są potrzebne, a nie podczas uruchamiania aplikacji. Wszystko, co musisz zrobić, to wywołaćgetWritableDatabase() lubgetReadableDatabase().

Uwaga: Ponieważ mogą one być długotrwałe, upewnij się, że wywołujesz getWritableDatabase() lub getReadableDatabase() w wątku tła.Zobacz Threading on Android, aby uzyskać więcej informacji.

Aby użyć SQLiteOpenHelper, utwórz podklasę, która nadpisuje metody onCreate() ionUpgrade() callback. Możesz również zaimplementować metodyonDowngrade() lubonOpen(), ale nie są one wymagane.

Na przykład, oto implementacja SQLiteOpenHelper, która wykorzystuje niektóre z poleceń pokazanych powyżej:

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

Aby uzyskać dostęp do bazy danych, należy zainicjować swoją podklasę SQLiteOpenHelper:

Kotlin

val dbHelper = FeedReaderDbHelper(context)

Java

FeedReaderDbHelper dbHelper = new FeedReaderDbHelper(getContext());

Wprowadzanie informacji do. bazy danych

Wprowadzanie danych do bazy danych poprzez przekazanie obiektu ContentValues do metody insert():

Kotlin

// Gets the data repository in write modeval db = dbHelper.writableDatabase// Create a new map of values, where column names are the keysval 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 rowval newRowId = db?.insert(FeedEntry.TABLE_NAME, null, values)

Java

// Gets the data repository in write modeSQLiteDatabase db = dbHelper.getWritableDatabase();// Create a new map of values, where column names are the keysContentValues 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 rowlong newRowId = db.insert(FeedEntry.TABLE_NAME, null, values);

Pierwszym argumentem dla insert() jest po prostu nazwa tabeli.

Drugi argument mówi frameworkowi, co zrobić w przypadku, gdy ContentValues jest pusta (tzn. nieput żadnych wartości).Jeśli podasz nazwę kolumny, framework wstawi wiersz i ustawi wartość tej kolumny na null. Jeśli podasz null, tak jak w tej próbce kodu, framework nie wstawia wiersza, gdy nie ma żadnych wartości.

Metody insert() zwracają ID dla nowo utworzonego wiersza, lub zwrócą -1, jeśli wystąpił błąd podczas wstawiania danych. Może się to zdarzyć, jeśli masz konflikt z wcześniej istniejącymi danymi w bazie danych.

Wczytaj informacje z bazy danych

Aby odczytać informacje z bazy danych, użyj metody query() , przekazując jej kryteria wyboru i żądane kolumny.Metoda ta łączy w sobie elementy metod insert() i update(), z wyjątkiem tego, że lista kolumn określa dane, które mają zostać pobrane („projekcja”), a nie dane do wstawienia. Wyniki zapytania są zwracane do użytkownika w obiekcie 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 Cursorval 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 CursorString 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 );

Trzeci i czwarty argument Trzeci i czwarty argument (selection i selectionArgs) są łączone w celu utworzenia klauzuli WHERE. Ponieważ argumenty są dostarczane oddzielnie od zapytania selekcyjnego, są one ucieczką przed połączeniem. To czyni twoje zapytania wyboru odpornymi na SQLinjection. Aby uzyskać więcej szczegółów na temat wszystkich argumentów, zobaczquery() referencje.

Aby spojrzeć na wiersz w kursorze, użyj jednej z metod Cursor movet, które musisz zawsze wywołać przed rozpoczęciem odczytu wartości. Ponieważ kursor zaczyna się na pozycji -1, wywołanie moveToNext() powoduje umieszczenie „pozycji odczytu” na pierwszej pozycji w wynikach i zwraca, czy kursor znajduje się już za ostatnią pozycją w zestawie wyników, czy nie. Dla każdego wiersza można odczytać wartość kolumny, wywołując jedną z metodCursor get, takich jak getString() lub getLong(). Dla każdej z metod get, musisz przekazać pozycję indeksu kolumny, którą chcesz uzyskać, którą możesz uzyskać przez wywołaniegetColumnIndex() lubgetColumnIndexOrThrow(). Po zakończeniu przeglądania wyników, wywołaj close() na kursorze, aby zwolnić jego zasoby.Na przykład poniżej pokazano, jak pobrać wszystkie identyfikatory elementów przechowywane w kursorze i dodać je do listy:

Kotlin

val itemIds = mutableListOf<Long>()with(cursor) { while (moveToNext()) { val itemId = getLong(getColumnIndexOrThrow(BaseColumns._ID)) itemIds.add(itemId) }}

Java

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

Usuwanie informacji z bazy danych

Aby usunąć wiersze z tabeli, należy podać kryteria wyboru, które identyfikują wiersze do metody delete(). Themechanizm działa tak samo jak argumenty wyboru do metodyquery(). Dzieli tę specyfikację wyboru na klauzulę wyboru i argumenty wyboru. Klauzula definiuje kolumny, na które należy zwrócić uwagę, a także pozwala na łączenie testów kolumnowych. Ponieważ wynik nie jest traktowany tak samo jak zwykłe zapytanie SQL, metoda ta jest odporna na wstrzyknięcie kodu 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);

Wartość zwracana przez

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

iv wartość zwracana przez metodędelete()określa liczbę wierszy, które zostały usunięte z bazy danych.

Uaktualnianie bazy danych

Gdy potrzebujesz zmodyfikować podzbiór wartości w bazie danych, użyj metodyupdate().

Uaktualnienie tabeli łączy w sobie ContentValues składnię metodyinsert() z WHERE składnią metody delete().

Kotlin

val db = dbHelper.writableDatabase// New value for one columnval title = "MyNewTitle"val values = ContentValues().apply { put(FeedEntry.COLUMN_NAME_TITLE, title)}// Which row to update, based on the titleval 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 columnString title = "MyNewTitle";ContentValues values = new ContentValues();values.put(FeedEntry.COLUMN_NAME_TITLE, title);// Which row to update, based on the titleString selection = FeedEntry.COLUMN_NAME_TITLE + " LIKE ?";String selectionArgs = { "MyOldTitle" };int count = db.update( FeedReaderDbHelper.FeedEntry.TABLE_NAME, values, selection, selectionArgs);

Wartością zwracaną przez metodę update() jest liczba wierszy w bazie danych, których dotyczy problem.

Trwające połączenie z bazą danych

Ponieważ getWritableDatabase() i getReadableDatabase() są drogie w wywołaniu, gdy baza danych jest zamknięta, powinieneś pozostawić połączenie z bazą danych otwarte tak długo, jak długo potrzebujesz do niej dostępu. Zazwyczaj optymalnym rozwiązaniem jest zamknięcie bazy danych w onDestroy() wywołującej aktywności.

Kotlin

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

Java

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

Debugowanie swoją bazę danych

The Android SDK zawiera sqlite3 narzędzie powłoki, które pozwala na przeglądanie zawartości tabeli, uruchamianie poleceń SQL i wykonywanie innych przydatnych funkcji na bazach danych SQLited. Aby uzyskać więcej informacji, zobacz jak wydawać polecenia powłoki.

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *