Articles

Salvare i dati usando SQLite

Salvare i dati in un database è ideale per dati ripetuti o strutturati, come le informazioni di contatto. Questa pagina presuppone che tu abbia familiarità con i database SQL in generale e ti aiuta a iniziare con i database SQLite su Android. Le API di cui avrete bisogno per usare un database su Android sono disponibili nel pacchetto android.database.sqlite.

Attenzione: Anche se queste API sono potenti, sono abbastanza di basso livello e richiedono una grande quantità di tempo e sforzo per essere utilizzate:

  • Non c’è una verifica in tempo di compilazione delle query SQL grezze. Quando il vostro grafico dei dati cambia, dovete aggiornare manualmente le query SQL interessate. Questo processo può richiedere molto tempo ed essere soggetto ad errori.
  • È necessario utilizzare un sacco di codice boilerplate per convertire le query SQL e gli oggetti dati.

Per queste ragioni, raccomandiamo vivamente di utilizzare la Room Persistence Library come livello di astrazione per accedere alle informazioni nei database SQLite della vostra applicazione.

Definire uno schema e un contratto

Uno dei principi fondamentali dei database SQL è lo schema: una dichiarazione formale di come è organizzato il database. Lo schema si riflette negli statement SQL che usate per creare il vostro database. Potreste trovare utile creare una classe di accompagnamento, conosciuta come classe contratto, che specifichi esplicitamente il layout del vostro schema in modo sistematico e auto-documentato.

Una classe contratto è un contenitore di costanti che definiscono i nomi per URI, tabelle e colonne. La classe contratto permette di usare le stesse costanti in tutte le altre classi dello stesso pacchetto. Questo vi permette di cambiare il nome di una colonna in un posto e farlo propagare in tutto il vostro codice.

Un buon modo per organizzare una classe contratto è di mettere le definizioni che sono globali per tutto il vostro database nel livello principale della classe. Poi creare una classe interna per ogni tabella. Ogni classe interna enumera le colonne della tabella corrispondente.

Nota: implementando l’interfaccia BaseColumns, la vostra classe interna può ereditare un campo chiave primaria chiamato _ID che alcune classi Android come CursorAdapter si aspettano che abbia. Non è obbligatorio, ma questo può aiutare il vostro database a lavorare armoniosamente con il framework Android.

Per esempio, il seguente contratto definisce il nome della tabella e i nomi delle colonne per un’unica tabella che rappresenta un feed 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"; }}

Creare un database usando un helper SQL

Una volta definito l’aspetto del vostro database, dovreste implementare i metodi che creano e mantengono il database e le tabelle. Ecco alcuni tipici metodi che creano e cancellano una tabella:

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;

Proprio come i file che salvi nella memoria interna del dispositivo, Android salva il tuo database nella cartella privata della tua app. I vostri dati sono sicuri, perché per default quest’area non è accessibile ad altre app o all’utente.

La classe SQLiteOpenHelper contiene un utile insieme di API per la gestione del vostro database.Quando usate questa classe per ottenere riferimenti al vostro database, il sistema esegue le operazioni potenzialmente lunghe di creazione e aggiornamento del database solo quando necessario e non durante l’avvio dell’app. Tutto quello che dovete fare è chiamaregetWritableDatabase() ogetReadableDatabase().

Nota: Poiché possono essere di lunga durata, assicuratevi di chiamare getWritableDatabase() o getReadableDatabase() in un thread in background.Vedere Threading su Android per maggiori informazioni.

Per usare SQLiteOpenHelper, creare una sottoclasse che sovrascriva i metodi di callback onCreate() eonUpgrade(). Potresti anche voler implementare i metodionDowngrade() oonOpen(), ma non sono obbligatori.

Per esempio, ecco un’implementazione di SQLiteOpenHelper che utilizza alcuni dei comandi mostrati sopra:

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

Per accedere al tuo database, istanzia la tua sottoclasse diSQLiteOpenHelper:

Kotlin

val dbHelper = FeedReaderDbHelper(context)

Java

FeedReaderDbHelper dbHelper = new FeedReaderDbHelper(getContext());

Mettere informazioni in un database

Inserire dati nel database passando un ContentValues oggetto al metodo 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);

Il primo argomento per insert() è semplicemente il nome della tabella.

Il secondo argomento dice al framework cosa fare nel caso in cui ilContentValues sia vuoto (cioè, non aveteput alcun valore).Se si specifica il nome di una colonna, il framework inserisce una riga e imposta il valore di quella colonna a null. Se si specifica null, come in questo esempio di codice, il framework non inserisce una riga quando non ci sono valori.

I metodi insert() restituiscono l’ID per la riga appena creata, oppure restituiscono -1 se c’è stato un errore nell’inserimento dei dati. Questo può accadere se avete un conflitto con dati preesistenti nel database.

Leggere informazioni da un database

Per leggere da un database, usate il metodo query(), passandogli i vostri criteri di selezione e le colonne desiderate.Il metodo combina elementi di insert() e update(), eccetto che la lista delle colonne definisce i dati che si vogliono recuperare (la “proiezione”), piuttosto che i dati da inserire. I risultati della query vengono restituiti in un oggetto 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 );

I terzo e quarto argomento (selection e selectionArgs) sono combinati per creare una clausola WHERE. Poiché gli argomenti sono forniti separatamente dalla selectionquery, essi sono sottoposti a escape prima di essere combinati. Questo rende le dichiarazioni di selezione immuni da SQLinjection. Per maggiori dettagli su tutti gli argomenti, vedere ilquery() riferimento.

Per guardare una riga nel cursore, usate uno dei Cursor metodi di spostamento, che dovete sempre chiamare prima di iniziare a leggere i valori. Poiché il cursore inizia dalla posizione -1, chiamando moveToNext() si posiziona la “posizione di lettura” sulla prima voce dei risultati e si restituisce se il cursore ha già superato l’ultima voce del set di risultati. Per ogni riga, è possibile leggere il valore di una colonna chiamando uno dei metodi getCursor, come getString() o getLong(). Per ognuno dei metodi get, devi passare la posizione dell’indice della colonna che desideri, che puoi ottenere chiamandogetColumnIndex() ogetColumnIndexOrThrow(). Quando avete finito di scorrere i risultati, chiamate close() sul cursore per liberare le sue risorse.Per esempio, il seguente mostra come ottenere tutti gli ID degli elementi memorizzati in un cursore e aggiungerli ad una lista:

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

Eliminare informazioni da un database

Per eliminare righe da una tabella, è necessario fornire criteri di selezione che identificano le righe al metodo delete(). Il meccanismo funziona come gli argomenti di selezione del metodoquery(). Divide la specifica di selezione in una clausola di selezione e in argomenti di selezione. La clausola definisce le colonne da esaminare e permette anche di combinare test di colonna. Gli argomenti sono valori da testare che sono legati alla clausola, e poiché il risultato non è gestito come un normale statement SQL, è immune da SQL injection.

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

Il valore di ritorno per il metodo delete() indica il numero di righe che sono state eliminate dal database.

Aggiornare un database

Quando hai bisogno di modificare un sottoinsieme dei valori del tuo database, usa il metodoupdate().

Aggiornare la tabella combina la sintassi ContentValues diinsert() con quella WHERE di 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);

Il valore di ritorno del metodo update() è il numero di righe interessate nel database.

Connessione al database esistente

Siccome getWritableDatabase()e getReadableDatabase() sono costosi da chiamare quando il database è chiuso, si dovrebbe lasciare la connessione al database aperta per tutto il tempo in cui è necessario accedervi. Tipicamente, è ottimale chiudere il database nel onDestroy() dell’attività chiamante.

Kotlin

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

Java

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

Debug il tuo database

L’SDK Android include uno strumento di sqlite3 shell che ti permette di sfogliare i contenuti della tabella, eseguire comandi SQL ed eseguire altre utili funzioni sui database SQL. Per maggiori informazioni, vedere come emettere comandi di shell.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *