Introduzione
Le applicazioni Android sfruttano spesso le funzionalità di persistenza offerte dalla libreria SQLite. Arricchire la propria app di un database non risulta perciò proibitivo, ma porta comunque il programmatore a dover integrare problematiche tipiche del paradigma relazionale nella tecnologia ad oggetti di Java. Si può ovviare a tutto ciò rivolgendosi a prodotti O/RM già ampiamente diffusi nel mondo enterprise.
L’articolo, muovendo da queste considerazioni, presenta alcune delle alternative O/RM disponibili per Android soffermandosi in particolare su GreenDAO di cui viene offerto un esempio pratico. Al termine il lettore si troverà introdotto in un nuovo modo di integrare database nelle app non dividendo più i suoi sforzi tra mondo Java e relazionale ma affrontando una progettazione completamente ad oggetti.
Cosa sono gli O/RM
L'integrazione di un database relazionale nel codice ad oggetti è un qualcosa a cui i programmatori sono abituati in ogni ambiente. Ciò si ritrova anche nel mondo Android in cui il codice Java gestisce comodamente database Sqlite come spiegato in un precedente articolo di questo sito. Riflettendoci su, questa pratica consiste nell'incontro tra due tecnologie differenti nell'essenza, quasi due mondi separati: la programmazione orientata agli oggetti (OOP) ed i database relazionali.
Entrambi permettono di progettare una versione ingegnerizzata della realtà modellandone i concetti in entità e stabilendo relazioni tra esse. La OOP lo fa tramite gli oggetti basati su classi e dal modo in cui essi si collegano tra loro. Tali oggetti sono transienti, la loro memorizzazione è un fenomeno legato alla memoria volatile. Nell'ambiente relazionale le entità al contrario non vengono più definite tramite tabelle e i collegamenti tra oggetti lasciano il posto alle relazioni di varie tipologie (uno-a-molti, molti-a-molti, etc.). Tutta la modellazione prodotta è racchiusa in un database ed i comandi che lo manipolano - siano essi di inserimento, reperimento, modifca o cancellazione - sono rappresentati dalle query strutturate nel linguaggio SQL. Con i database relazionali le informazioni possono diventare persistenti trovando una memorizzazione non più volatile.
Il compromesso tra questi due mondi è offerto dalla categoria degli O/RM (Object/Relational Mapping). I termini che compongono questo acronimo ne illustrano da subito lo scopo: la mappatura tra mondo relazionale e mondo ad oggetti.
In pratica, ciò avviene predisponendo degli oggetti Java che rispecchiano le caratteristiche delle tabelle presenti nel database. L'O/RM svolge il ruolo di intermediario aggiornando le tabelle del database in conseguenza delle modifiche fatte agli oggetti. Le interrogazioni su database verranno veicolate anch'esse dagli O/RM e produrranno liste di oggetti rispecchianti i record selezionati. Tutto questo meccanismo funzionerà senza l'utilizzo di comandi SQL espliciti ma in maniera quasi del tutto automatica a patto che venga svolto un opportuno lavoro di configurazione da parte dello sviluppatore secondo le regole imposte dallo specifico ORM.
I vantaggi principali dell'impiego degli O/RM sono:
- una gestione completamente ad oggetti del programma;
- una totale indipendenza della parte di progettazione del db da quella del software;
- aumento della produttività grazie alla riduzione del codice da scrivere in particolare di quello ripetitivo legato all'interazione con l'infrastruttura del DBMS;
- maggiore portabilità del codice in quanto molte delle caratteristiche dello specifico DBMS sono astratte dallo O/RM.
- generazione del codice in un progetto separato. Da notare bene che si tratta di un normale programma Java, non Android;
- inserimento delle classi generate nei sorgenti del progetto Android;
- normale svolgimento del progetto Android gestendo l'interazione del database esclusivamente tramite le classi generate da greenDAO.
- popolare il database creando un numero di record con dati casuali;
- svuotare il database;
- filtrare l'elenco di persone per mostrare solo i maggiorenni o solo i minorenni.
Ovviamente l'uso degli O/RM diventa tanto più utile quanto più il database oggetto diventa articolato, pieno di tabelle e relazioni tra esse.
Le alternative in Android
Negli ultimi tempi si sono diffusi strumenti O/RM che dimostrano la fattibilità dell'applicazione di tale approccio anche nella programmazione Android. Non mirano a sostituire SQlite anzi offrono un modo agevole e ad oggetti per gestire questo database “leggero”.
Due prodotti che si sono già guadagnati una certa fama sono:
Come già detto ce ne sono molti altri, alcuni ancora progetti non troppo estesi, poco più di librerie per automatizzare l'uso di SQL. Tra i tanti si possono ancora segnalare SugarORM e ActiveAndroid.
Panoramica di greenDAO
Prima di passare ad un esempio pratico, è bene illustrare una panoramica del progetto greenDAO. Innanzitutto vale la pena investire qualche parola per spiegare il termine DAO che si incontrerà spesso nel seguito.
Un DAO (Data Access Object) è un particolare oggetto che incapsula in sé stesso le funzionalità di accesso ai database. Il suo scopo è quello di fare in modo che il resto del software non debba preoccuparsi di gestire connessioni a database, query e relativi risultati. Tutte le operazioni in proposito verranno richieste al DAO tramite i metodi pubblici esposti e questo risolverà al suo interno tutta la logica necessaria.
Il vantaggio evidente di questo approccio è che qualunque modifica possa essere in futuro apportata alla struttura del database non avrà il minimo impatto sul resto del programma ma comporterà solo l'aggiornamento interno del DAO.
Il lavoro con greenDAO si articola lungo le seguenti fasi:
Le classi prodotte dalla generazione si possono suddividere in quattro tipologie:
Campo | Descrizione |
---|---|
DaoMaster | È il punto d'accesso alla struttura creata da greenDAO. Contiene tutto il necessario alla gestione del database nel suo complesso: metodi statici, classi helper, etc. La generazione di codice produce una sola classe di questo tipo |
DaoSession | gestisce gli oggetti DAO e ne rende disponibili i riferimenti durante lo sviluppo. Anche di questa classe ne viene prodotta una sola |
Classi entità | sono le vere e proprie classi persistenti, da usare nel codice ad oggetti ma allo stesso tempo strutturate come le tabelle che rappresentano. Hanno la forma di normali classi Java (i cosiddetti POJO, Plain Old Java Object) con vari membri privati e metodi pubblici per accedervi |
Classi DAO | ne esiste una per ogni classe entità |
Possiamo dire, in pratica, che la generazione del codice prende il posto della consueta progettazione del database. In particolare, lavorando con un O/RM come greenDAO si perde un po' la percezione di lavorare veramente con un database. Ma dov'è il nostro database SQLite mentre vi interagiamo con greenDAO? Esattamente dove ci aspetteremmo di trovarlo: nella cartella /data/data/packagename/databases
Per iniziare il lavoro è necessario scaricare opportune librerie jar pagina dei download del sito ufficiale greendao-generator greendao code generation freemarker http://freemarker.org/
Esempio applicativo
L'esempio presentato gestisce un database di persone e permette, tramite le voci nell'Options Menu di:
Inoltre una volta che l'activity mostra l'elenco delle persone registrate nel database si può invocare su una singola voce un menu contestuale
La prima fase da affrontare è la generazione automatica del codice Schema
Schema schema=new Schema(1,"it.html.dao.persone"); Entity _persone=schema.addEntity("Persone"); _persone.addIdProperty(); _persone.addStringProperty("nome"); _persone.addStringProperty("cognome"); _persone.addIntProperty("eta");
Vediamo che viene istanziato un oggetto Entity
addEntity
addStringProperty
addIntProperty
addIdProperty
Terminata la configurazione si potrà avviare la generazione vera e propria con la riga:
new DaoGenerator().generateAll(schema, "../GreenDaoExample/src");
L'oggetto DaoGenerator
.java
src
Il risultato produrrà quattro classi:
- Persone
- PersoneDAO
- DaoMaster
- DaoSession
Il progetto Android è costituito da un'unica Activity, MainActivity
.
Considerato che il suo scopo è quello di mostrare una ListView
ListActivity
ArrayAdapter
ListView
DbManager
PersoneDao
Proprio per questo motivo analizziamo con attenzione la classe DbManager
public class DbManager { private PersoneDao dao; private Context context; public DbManager(Context ctx) { String dbname=ctx.getResources().getString(R.string.dbname); DaoMaster.DevOpenHelper helper = new DaoMaster.DevOpenHelper(ctx, dbname, null); SQLiteDatabase db = helper.getWritableDatabase(); DaoMaster daoMaster = new DaoMaster(db); DaoSession daoSession = daoMaster.newSession(); dao = daoSession.getPersoneDao(); context=ctx; } public void cancellaPersona(Persone p) { dao.delete(p); } public void modificaPersona(Persone p) { dao.update(p); } public List<Persone> listaMaggiorenni() { List<Persone> res=dao.queryBuilder().where(Properties.Eta.gt(18)).list(); return res; } public List<Persone> listaMinorenni() { List<Persone> res=dao.queryBuilder().where(Properties.Eta.le(18)).list(); return res; } public List<Persone> listaPersone() { return dao.loadAll(); } public void popola(int quanti) { String nomi[]=context.getResources().getStringArray(R.array.nomi); String cognomi[]=context.getResources().getStringArray(R.array.cognomi); Persone p=null; Random caso=new Random(); int nomePos=-1; int cognomePos=-1; for(int i=0;i<quanti;i++) { p=new Persone(); nomePos=caso.nextInt(nomi.length); cognomePos=caso.nextInt(cognomi.length); p.setNome(nomi[nomePos]); p.setCognome(cognomi[cognomePos]); p.setEta(12+caso.nextInt(50)); dao.insert(p); } } public void controllaMinimo() { if (dao.count()==0) popola(this.context.getResources().getInteger(R.integer.minimo)); } public void svuota() { dao.deleteAll(); } }
Il costruttore del DbManager va osservato per primo, le righe che lo compongono nell'ordine eseguono:
- dichiarazione di un helper
DaoMaster.DevOpenHelper
- viene ottenuto un riferimento ad un database come normale oggetto
SQLiteDatabase
- tramite il riferimento al db viene configurato un nuovo oggetto
DaoMaster
- dal
DaoMaster
DaoSession
- dalla sessione si possono richiedere riferimenti ai singoli DAO necessari, nell'esempio esiste solo
PersoneDao
Tutti i metodi del DbManager faranno uso del riferimento a PersoneDao
per richiedere delle operazioni sulla tabella. Il grande assente del codice è proprio SQL. Tutte le operazioni che verranno svolte, riflettenti ogni casistica CRUD, non richiederanno mai query ma faranno uso di oggetti di classe Persone
che verranno manipolati “ad oggetti”. Le modifiche eseguite verranno riflesse direttamente sul modello relazionale. Con questa modalità greenDAO mette in pratica ciò che un O/RM deve essere.
Visto che il codice viene generato appositamente tutti i riferimenti passati ai metodi di PersoneDao
sono perfettamente tipizzati all'occasione infatti tutti i parametri formali richiesti appartengono alla classe Persone
.
I seguenti metodi impiegati hanno un significato piuttosto intuitivo:
void delete(Persone p)
Persone
cancellaPersona()
void deleteAll()
TRUNCATE TABLE ...
void update(Persone p)
List<Persone> loadAll()
SELECT * FROM nometabella
;Persone loadByKey(int id)
void insert(Persone p)
INSERT
popola(int)
Merita una menzione particolare il QueryBuilder un oggetto che come si presume dal nome stesso funziona da aiuto automatico per la generazione di query. Infatti se si dovesse selezionare un set di record dalla tabella, diciamo una via di mezzo tra un loadAll
ed un loadByKey
, ci si troverebbe nella situazione di scegliere tra tornare al vecchio SQL o ricorrere ad una iterazione Java sulla lista totale fornita dal DAO. Per questo è stato inserito questo oggetto che offre metodi per applicare selezioni.
Ad esempio una generica clausola WHERE eta<18
diventa:
La classe Properties
lt
le
<
<=
gt
ge
>
>=
eq
=
Ognuno di questi metodi di confronto restituisce un oggetto WhereCondition
da usare come argomento del metodo where()
del QueryBuilder. I WhereCondition
passati a tale metodo possono essere più di uno ed in quel caso sono collegati tra loro in un unico AND
ossia vengono restituiti i record che soddisfano tutte le condizioni.
Ad esempio, la seguente invocazione:
estrae tutti gli oggetti che descrivono persone maggiorenni con il nome che inizia con la lettera 'a'.
Esistono inoltre i metodi and() e or() che descrivono gli omonimi operatori logici. Anch'essi restituiscono oggetti WhereCondition pertanto possono essere usati come argomenti del metodo where(). Il seguente esempio ne mostra in breve l'uso sottolineando al contempo che per invocarlo è necessario avere a disposizione un riferimento al QueryBuilder:
In questo caso vengono estratti dal database tutti i soggetti maggiorenni il cui nome inizi e finisca con la lettera 'a'.
Il resto dell'Activity, grazie all'incapsulamento di tutte le funzionalità di accesso ai dati in DbManager, non contiene alcuna traccia di greenDAO. Tutto ciò che vi si trova è una normale gestione di menu e di eventi.
Conclusioni
L'articolo ha illustrato le motivazioni che hanno portato alla nascita degli strumenti O/RM ed ha voluto sottolineare anche l'importanza architetturale che può rivestire in un progetto l'inserimento di un oggetto DAO. Dividere le funzionalità in base allo scopo permette di organizzare meglio i compiti in un team, i tempi di sviluppo e perfino la scelta delle risorse umane. Ciò è importante anche nei progetti Android.
Lo strumento analizzato, greenDAO, necessita ancora di essere esteso per arricchirsi di funzionalità, in fin dei conti è ancora abbastanza giovane. Tuttavia può già dimostrare la sua utilità e merita sicuramente di essere studiato, valutato e probabilmente apprezzato.