Zapisz dane przy użyciu SQLite

Zapisanie danych w bazie danych to idealne rozwiązanie do ich tworzenia powtórzonych lub uporządkowanych, np. danych kontaktowych. Zakładamy na tej stronie, że zna ogólnie bazy danych SQL i pomaga zacząć Bazy danych SQLite na Androidzie. Interfejsy API potrzebne do korzystania z bazy danych na Androida są dostępne w pakiecie android.database.sqlite.

Uwaga: te interfejsy API są zaawansowane, ale są dość niskie. i wymagają dużo czasu i wysiłku:

  • W przypadku nieprzetworzonych zapytań SQL nie można weryfikować nieprzetworzonych zapytań SQL w czasie kompilowania. Twoje dane zmian na wykresie, musisz ręcznie zaktualizować odpowiednie zapytania SQL. Ten może być czasochłonny i podatny na błędy.
  • Do konwersji między zapytaniami SQL musisz używać dużego stałego kodu i obiekty danych.

Dlatego zdecydowanie zalecamy korzystanie z Biblioteka trwałości sal jako warstwy abstrakcyjnej umożliwiającej dostęp do informacji w kodzie SQLite aplikacji baz danych.

Zdefiniuj schemat i umowę

Jedną z głównych zasad baz danych SQL jest schemat, czyli formalny czyli deklaracji struktury bazy danych. Schemat jest odzwierciedlony w SQL za pomocą instrukcji tworzenia bazy danych. Może Ci się przydać utwórz klasę towarzyszącą, tzw. contract, która wyraźnie określa układ Twojego schematu w sposób systematyczny i samodokumentujący się.

Klasa umowy to kontener dla stałych, które definiują nazwy identyfikatorów URI, i kolumny. Klasa kontraktowa pozwala na użycie tych samych stałych dla wszystkich innych klas w tym samym pakiecie. Umożliwia to zmianę kolumny w jednym miejscu i propagować ją w całym kodzie.

Dobrym sposobem na uporządkowanie klasy umowy jest zastosowanie definicji, które globalny do całej bazy danych na poziomie głównym klasy. Następnie stwórz w sobie dla każdej tabeli. Każda klasa wewnętrzna wylicza odpowiednie kolumny tabeli.

Uwaga: dzięki wdrożeniu BaseColumns klasa wewnętrzna może dziedziczyć polu klucza o nazwie _ID, którego oczekują niektóre klasy Androida, takie jak CursorAdapter. Nie jest to wymagane, ale może to pomóc w Twojej bazie danych współdziałają z platformą Android.

Na przykład ta umowa definiuje nazwę tabeli i nazwy kolumn dla pojedyncza tabela reprezentująca 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 za pomocą Asystenta SQL

Po zdefiniowaniu wyglądu bazy danych zaimplementuj metody które tworzą i utrzymują bazę danych oraz tabele. Oto kilka typowych instrukcje tworzenia i usuwania tabeli:

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 samo jak pliki zapisywane w pamięci wewnętrznej urządzenia , Android przechowuje bazę danych w prywatnym folderze aplikacji. Twoje dane są bezpieczeństwa, ponieważ domyślnie ten obszar nie jest które mogą być dostępne dla innych aplikacji lub dla użytkownika.

Klasa SQLiteOpenHelper zawiera przydatne interfejsów API do zarządzania bazą danych. Gdy używasz tej klasy do uzyskania odwołań do bazy danych, system przetwarza potencjalnie długotrwałe operacje tworzenia i aktualizowania bazy danych tylko wtedy, i nie podczas uruchamiania aplikacji. Wystarczy zadzwonić getWritableDatabase() lub getReadableDatabase()

Uwaga: mogą one być długoterminowe, pamiętaj o wywołaniu funkcji getWritableDatabase() lub getReadableDatabase() w wątku w tle. Więcej informacji znajdziesz w artykule Threading na urządzeniach z Androidem.

Aby użyć klasy SQLiteOpenHelper, utwórz podklasę, która zastępuje onCreate() i onUpgrade() metod wywołania zwrotnego. Możesz chcą też wdrożyć onDowngrade() lub onOpen() metod, ale nie są one wymagane.

Oto przykładowa implementacja SQLiteOpenHelper, która używa niektórych z powyższych poleceń:

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, utwórz instancję podklasy SQLiteOpenHelper:

Kotlin

val dbHelper = FeedReaderDbHelper(context)

Java

FeedReaderDbHelper dbHelper = new FeedReaderDbHelper(getContext());

Umieszczanie informacji w bazie danych

Wstaw dane do bazy danych, przekazując ContentValues do metody 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);

Pierwszy argument funkcji insert() jest po prostu nazwą tabeli.

Drugi argument wskazuje systemowi, co należy zrobić w przypadku, gdy Pole ContentValues jest puste (np. nie zawierało put dowolną wartość). Jeśli określisz nazwę kolumny, platforma wstawi wiersz i ustawi wartość tej kolumny na null. Jeśli określisz null, jak w tym przykładowego kodu, platforma nie wstawia wiersza, jeśli nie ma wartości.

Metody insert() zwracają identyfikator metody nowo utworzonego wiersza. W przeciwnym razie zwraca wartość -1, jeśli podczas wstawiania danych wystąpił błąd. Może się tak zdarzyć .

Odczytywanie informacji z bazy danych

Aby odczytać dane z bazy danych, użyj metody query(), przekazując do niej kryteria wyboru i odpowiednie kolumny. Metoda łączy elementy funkcji insert() i update(), z wyjątkiem listy kolumn określa dane, które chcesz pobrać („odwzorowanie”), a nie dane do wstawienia. Wyniki zapytań zostanie zwróconych 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 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
    );

Trzeci i czwarty argument (selection i selectionArgs) to: zostanie połączona w celu utworzenia klauzuli WHERE. Ponieważ argumenty są podawane niezależnie od wyboru przed połączeniem za ich pomocą zmienia się znaczenie modyfikacji. Dzięki temu instrukcje wyboru są odporne na SQL wstrzyknięcie kodu. Więcej informacji o wszystkich argumentach znajdziesz tutaj: Odwołanie do: query().

Aby spojrzeć na wiersz w kursie, użyj jednego z Cursor przesunięć które należy zawsze wywoływać przed rozpoczęciem odczytu wartości. Ponieważ kursor zaczyna się od pozycja -1, wywołanie funkcji moveToNext() powoduje umieszczenie „pozycji czytania” w pierwszy wpis w wynikach i zwraca, czy kursor jest już po ostatniej pozycji w polu wyniki. Dla każdego wiersza możesz odczytać wartość kolumny, wywołując jedną z funkcji Cursor pobiera metody, takie jak getString() lub getLong(). Dla każdej metody get musisz przekazać pozycję indeksu wybranej kolumny, którą można uzyskać, wywołując getColumnIndex() lub getColumnIndexOrThrow() Po zakończeniu iteracja przy użyciu wyników, wywołaj funkcję close() na kursorze aby uwolnić zasoby. Na przykład poniżej pokazujemy, jak przenieść wszystkie identyfikatory produktów zapisane w kursie. i dodaj je do listy:

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

Usuwanie informacji z bazy danych

Aby usunąć wiersze z tabeli, musisz podać kryteria wyboru, które: identyfikować wiersze z metodą delete(). działa tak samo jak argumenty wyboru w funkcji Metoda query(). Dzieli on specyfikacji wyboru do klauzuli wyboru i argumentów wyboru. określa przegląd kolumn i umożliwia łączenie kolumn testów. Argumenty to wartości powiązane z klauzulą, które są podstawą do testowania. Wynik nie jest obsługiwany tak samo jak zwykła instrukcja SQL, dlatego jest odporny 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ść zwrócona dla metody delete(). wskazuje liczbę wierszy usuniętych z bazy danych.

Zaktualizowanie bazy danych

Aby zmodyfikować podzbiór wartości w bazie danych, użyj metody Metoda update().

Aktualizowanie tabeli łączy składnię ContentValues funkcji insert() ze składnią WHERE z 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);

Wartość zwrócona przez metodę update() to liczbę wierszy w bazie danych, których to dotyczy.

Trwałe połączenie z bazą danych

Od getWritableDatabase() i getReadableDatabase() są koszt połączenia po zamknięciu bazy danych, należy opuścić połączenie z bazą danych pozostają otwarte tak długo, jak to konieczne. Zwykle najlepiej jest zamknąć bazę danych, w onDestroy() aktywności związanej z połączeniami.

Kotlin

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

Java

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

Debugowanie bazy danych

Pakiet Android SDK zawiera narzędzie powłoki sqlite3, które umożliwia przeglądanie zawartość tabel, uruchamianie poleceń SQL i wykonywanie innych przydatnych funkcji w SQLite baz danych. Więcej informacji znajdziesz w artykule o wydawaniu poleceń powłoki.