Articles

Gegevens opslaan met SQLite

Gegevens opslaan in een database is ideaal voor herhalende of gestructureerde gegevens, zoals contact informatie. Deze pagina gaat ervan uit dat u bekend bent met SQL databases in het algemeen en helpt u aan de slag met SQLite databases op Android. De API’s die u nodig hebt om een database op Android te gebruiken zijn beschikbaar in het android.database.sqlite pakket.

Voorzichtigheid: Hoewel deze API’s krachtig zijn, zijn ze vrij low-level en vereisen ze veel tijd en moeite om te gebruiken:

  • Er is geen compile-time verificatie van ruwe SQL queries. Als uw gegevensgrafiek verandert, moet u de betreffende SQL-query’s handmatig bijwerken. Dit proces kan tijdrovend en foutgevoelig zijn.
  • U moet veel boilerplate code gebruiken om te converteren tussen SQL-query’s en data-objecten.

Om deze redenen is het gebruik van de Room Persistence Library als abstractielaag voor toegang tot informatie in de SQLite-databases van uw app sterk aan te bevelen.

Stel een schema en contract op

Eén van de belangrijkste principes van SQL-databases is het schema: een formele verklaring van hoe de database is georganiseerd. Het schema wordt weerspiegeld in de SQL-statements die u gebruikt om uw database te maken. U kunt het nuttig vinden om een begeleidende klasse te maken, bekend als een contract klasse, die expliciet de lay-out van uw schema specificeert op een systematische en zelf-documenterende manier.

Een contract klasse is een container voor constanten die namen definiëren voor URI’s, tabellen, en kolommen. De contract klasse staat u toe om dezelfde constanten te gebruiken in alle andere klassen in hetzelfde pakket. Hierdoor kun je een kolomnaam op één plaats veranderen en deze door je hele code laten verspreiden.

Een goede manier om een contract class te organiseren is om definities die globaal zijn voor je hele database in het root level van de class te zetten. Maak dan een innerclass voor elke tabel. Elke innerclass somt de kolommen van de corresponderende tabel op.

Note: Door de BaseColumnsinterface te implementeren, kan je innerclass een primarykey-veld erven genaamd _ID dat sommige Android-klassen zoals CursorAdapter verwachten dat het heeft. Het is niet vereist, maar dit kan helpen uw databasewerk harmonieus met het Android framework.

Het volgende contract definieert bijvoorbeeld de tabelnaam en kolomnamen voor een enkele tabel die een RSS feed weergeeft:

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

Maak een database met behulp van een SQL-helper

Als u eenmaal hebt gedefinieerd hoe uw database eruitziet, moet u methoden implementeren die de database en tabellen maken en onderhouden. Hier zijn enkele typische verklaringen voor het maken en verwijderen van een 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;

Net als bestanden die u op de interne opslagruimte van het apparaat opslaat, slaat Android uw database op in de privémap van uw app. Uw gegevens zijn veilig, omdat dit gebied standaard niet toegankelijk is voor andere apps of de gebruiker.

De klasse SQLiteOpenHelper bevat een handige set API’s voor het beheren van uw database.Wanneer u deze klasse gebruikt om verwijzingen naar uw database te verkrijgen, voert het systeem de potentieel langdurige bewerkingen voor het maken en bijwerken van de database alleen uit wanneer dat nodig is en niet tijdens het opstarten van de app. Alles wat u hoeft te doen isgetWritableDatabase() ofgetReadableDatabase() op te roepen.

Note: Omdat ze lang kunnen duren, moet u ervoor zorgen dat u getWritableDatabase() of getReadableDatabase() in een achtergronddraad oproept.Zie Threading op Android voor meer informatie.

Om SQLiteOpenHelper te gebruiken, maakt u een subklasse die de onCreate() enonUpgrade() callback methoden overschrijft. U kunt ook de methodenonDowngrade() ofonOpen() willen implementeren, maar deze zijn niet verplicht.

Hier ziet u bijvoorbeeld een implementatie van SQLiteOpenHelper waarin enkele van de hierboven getoonde commando’s zijn gebruikt:

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

Om toegang te krijgen tot uw database, installeert u uw subklasse vanSQLiteOpenHelper:

Kotlin

val dbHelper = FeedReaderDbHelper(context)

Java

FeedReaderDbHelper dbHelper = new FeedReaderDbHelper(getContext());

Voer informatie in een database

Voeg gegevens in de database in door een ContentValuesobject door te geven aan de insert() methode:

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

Het eerste argument voor insert()is simpelweg de tabelnaam.

Het tweede argument vertelt het framework wat het moet doen in het geval dat deContentValues leeg is (d.w.z. dat u geenput waarden hebt opgegeven).Als u de naam van een kolom opgeeft, voegt het framework een rij in en stelt de waarde van die kolom in op null. Als u null opgeeft, zoals in dit code voorbeeld, voegt het framework geen rij in als er geen waarden zijn.

De insert() methods retourneert de ID voor de nieuw aangemaakte rij, of het retourneert -1 als er een fout is opgetreden bij het invoegen van de gegevens. Dit kan gebeuren als er een conflict is met reeds bestaande gegevens in de database.

Leest informatie uit een database

Om uit een database te lezen, gebruikt u de query() methode, waarbij u uw selectie criteria en gewenste kolommen doorgeeft.De methode combineert elementen van insert() en update(), behalve dat de kolommenlijst de gegevens definieert die u wilt ophalen (de “projectie”), in plaats van de in te voegen gegevens. De resultaten van de query worden naar u teruggestuurd in een Cursor object.

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

De derde en vierde argument (selection en selectionArgs) worden gecombineerd om een WHERE-clausule te maken. Omdat de argumenten apart van de selectiequery worden opgegeven, worden ze ge-escaped voordat ze worden gecombineerd. Dit maakt uw selectie statements immuun voor SQL-injectie. Voor meer details over alle argumenten, zie dequery() referentie.

Om een rij in de cursor te bekijken, gebruik je een van de Cursor movemethodes, die je altijd moet aanroepen voordat je begint met het lezen van waarden. Omdat de cursor begint op positie -1, plaatst het aanroepen van moveToNext() de “lees positie” op het eerste item in de resultaten en geeft terug of de cursor al voorbij het laatste item in de resultatenverzameling is of niet. Voor elke rij kunt u de waarde van een kolom lezen door een van deCursor get methodes aan te roepen, zoals getString() of getLong(). Voor elk van de get methodes, moet u de index positie doorgeven van de kolom die u wenst, die u kunt krijgen doorgetColumnIndex() ofgetColumnIndexOrThrow() op te roepen. Als u klaar bent met hetitereren van de resultaten, roep dan close() op de cursor aan om zijn bronnen vrij te geven.Het volgende voorbeeld laat zien hoe je alle item ID’s die in een cursor zijn opgeslagen kunt ophalen en aan een lijst kunt toevoegen:

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

Verwijder informatie uit een database

Om rijen uit een tabel te verwijderen, moet je selectiecriteria die de rijen identificeren aan de delete() methode geven. Het mechanisme werkt hetzelfde als de selectie-argumenten voor dequery() methode. Het verdeelt de selectiespecificatie in een selectieclausule en selectieargumenten. De clausule definieert de kolommen waarnaar moet worden gekeken, en laat u ook toe kolomtests te combineren. De argumenten zijn waarden waarmee moet worden getest en die aan de clausule zijn gebonden. Omdat het resultaat niet op dezelfde manier wordt behandeld als een gewoon SQL-instructie, is de methode ongevoelig voor SQL-injectie.

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

De returnwaarde voor de delete() methode geeft het aantal rijen aan dat uit de database is verwijderd.

Een database bijwerken

Wanneer u een subset van uw database waarden moet wijzigen, gebruik dan deupdate() methode.

Het bijwerken van de tabel combineert de ContentValues syntaxis vaninsert() met de WHERE syntaxis van 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);

De returnwaarde van de update() methode is het aantal rijen in de database dat is beïnvloed.

Bestaande databaseverbinding

Omdat getWritableDatabase()en getReadableDatabase() duur zijn om op te roepen wanneer de database gesloten is, moet u uw databaseverbinding open laten zolang u er toegang toe nodig hebt. Doorgaans is het optimaal om de database te sluiten in de onDestroy() van de aanroepende activiteit.

Kotlin

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

Java

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

Debug uw database

De Android SDK bevat een sqlite3 shelltool waarmee u de inhoud van tabellen kunt doorbladeren, SQL commando’s uit te voeren, en andere nuttige functies op SQLitedatabases uit te voeren. Zie voor meer informatie hoe u shell-commando’s kunt uitvoeren.

Geef een reactie

Het e-mailadres wordt niet gepubliceerd. Vereiste velden zijn gemarkeerd met *