Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

Android e gli ORM

Come usare un database di tipo ROM in Android e utilizzare così la programmazione orientata agli oggetti anche per il db
Come usare un database di tipo ROM in Android e utilizzare così la programmazione orientata agli oggetti anche per il db
Link copiato negli appunti

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.

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:

  • OrmLite: appare come una libreria alquanto completa. Supporta diversi DBMS relazionali anche tramite JDBC. Nasce come strumento di supporto generale alla programmazione Java ma viene anche presentato come valido tool per lo sviluppo Android. Il suo utilizzo è poco invasivo per il codice in quanto fa ampio uso delle annotazioni;
  • greenDAO: nasce specificamente per gestire SQLite su Android e mira ad ottenere buoni livelli di efficienza nelle prestazioni e leggerezza. A differenza di OrmLite per essere integrato in un progetto Android sfrutta la generazione di codice. È comunque una buona soluzione tanto che nel prosieguo dell'articolo viene trattato in un esempio applicativo.

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:

  • 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.

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à e si occupa di tutte le operazioni di persistenza di questa

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 come già spiegato sulle pagine di questo sito.

Per iniziare il lavoro è necessario scaricare opportune librerie. Il progetto greenDAO in questo sfrutta  Maven, quindi per ottenere i jar necessari si devono seguire le istruzioni presentate alla pagina dei download del sito ufficiale. Esistono due librerie: greendao-generator per supportare la fase di generazione del codice e greendao per lo sviluppo in Android tramite i DAO automaticamente generati. È bene sottolineare comunque che nella fase di code generation serve un ulteriore jar denominato freemarker e disponibile su http://freemarker.org/.

Esempio applicativo

L'esempio presentato gestisce un database di persone e permette, tramite le voci nell'Options Menu di:

  • 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.

Inoltre una volta che l'activity mostra l'elenco delle persone registrate nel database si può invocare su una singola voce un menu contestuale (con la pressione continuata sull'elemento interessato) che permette di eliminare la voce in questione o modificarla tramite form. Gli effetti di ogni operazione saranno visibili direttamente sulla lista e si ripercuoteranno immediatamente sul database.

La prima fase da affrontare è la generazione automatica del codice. Le seguenti righe richiederanno per prima cosa la definizione del database tramite l'oggetto Schema che viene inizializzato con una stringa che rappresenta il package cui apparterranno le classi generate:

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 arricchito di una serie di proprietà. Il metodo addEntity riceve come parametro una stringa che corrisponde al nome della tabella. I metodi addStringProperty e addIntProperty si può dire che “aggiungono colonne alla tabella” specificandone il nome. Più particolare il metodo addIdProperty che fornisce all'oggetto un id interno univoco e progressivo. Queste proprietà prefigurate non saranno altro che membri della classe che tramite DAO manipolerà la tabella.

Terminata la configurazione si potrà avviare la generazione vera e propria con la riga:

new DaoGenerator().generateAll(schema, "../GreenDaoExample/src");

L'oggetto DaoGenerator si occuperà di svolgere le operazioni. Nella riga appena mostrata vengono passati due parametri: l'oggetto schema che contiene la configurazione richiesta e la cartella destinazione per i file .java generati. In questo caso, il path di produzione indicato nell'esempio coincide con la cartella src del progetto Android che condivide lo stesso workspace Eclipse del codice di generazione.

Il risultato produrrà quattro classi:

  • Persone, il singolo record, il cui nome rispecchia quello impostato per l'entità;
  • PersoneDAO, il dao per gestire la classe/tabella Persone;
  • DaoMaster
  • DaoSession.

Il progetto Android è costituito da un'unica Activity, MainActivity.

public class MainActivity extends ListActivity
{

	private ArrayAdapter<Persone> adapter;
	private DbManager db;
	private enum Filtro {MAGGIORENNI,MINORENNI,NESSUNO};
	private Filtro filtroAttuale=Filtro.NESSUNO;

	. . .
	. . .
}

Considerato che il suo scopo è quello di mostrare una ListView che mostri le persone inserite nel database, non sorprende che estenda una ListActivity. Conterrà due membri particolari: un ArrayAdapter per gestire il popolamento del ListView e un oggetto DbManager, definito da noi che si occuperà di contenere un riferimento a PersoneDao. Il DbManager quindi incapsulerà l'intero funzionamento del database rendendolo opaco all'Activity.

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 che riceve tra gli altri parametri il nome del database;
  • viene ottenuto un riferimento ad un database come normale oggetto SQLiteDatabase. In questo caso si tratta di un riferimento writeable;
  • tramite il riferimento al db viene configurato un nuovo oggetto DaoMaster;
  • dal DaoMaster viene recuperato un oggetto DaoSession che indica la nuova sessione di lavoro aperta;
  • 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): riceve un riferimento ad un oggetto Persone e richiede la distruzione del record corrispondente. È l'unica invocazione che serve all'interno del metodo cancellaPersona();
  • void deleteAll(): consente di svuotare l'intero contenuto della tabella, l'equivalente di un TRUNCATE TABLE ...;
  • void update(Persone p): applica la modifica dei dati relativi ad un record esistente. L'oggetto passato come argomento conterrà i dati modificati e tramite l'id univoco contenuto sarà riconducibile al record della tabella su cui salvare i dati;
  • List<Persone> loadAll(): restituisce una lista di oggetti Persone che rappresenta il contenuto della tabella. Il suo effetto è lo stesso che produrrebbe una classica query SELECT * FROM nometabella;
  • Persone loadByKey(int id): è un metodo, non utilizzato in questo esempio, che recupera in base all'id univoco i dati presenti in una riga della tabella;
  • void insert(Persone p): inserisce all'interno della tabella i dati contenuti nell'oggetto passato. Sostituisce l'usatissimo INSERT di SQL ed evita la tediosa elencazione dei campi interessati e dei relativi valori da inserire nel rispetto di apici e formati da non confondere. Questo metodo del DAO viene utilizzato all'interno di popola(int) che riempie la tabella di tante persone casuali quante ne indica il parametro intero passato. Le poche righe Java che precedono la sua invocazione non fanno altro che generare dati casuali usando numeri random per scegliere un nome ed un cognome dai rispettivi array ed analogamente un'età casuale.

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:

.queryBuilder().where(Properties.Eta.lt(18))

La classe Properties viene generata insieme al DAO e contiene costanti che indicano ogni singolo campo della tabella. Su ogni proprietà si possono applicare i metodi che prendono il posto degli operatori di confronto:

  • lt (lesser than) e le (lesser or equal) prendono il posto di < e <= ;
  • gt (greater than) e ge (greater or equal) prendono il posto di > e >= ;
  • eq (equal) corrisponde a =.

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:

.queryBuilder().where(Properties.Eta.gt(18), Properties.Nome.like("a%")).list();

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:

QueryBuilder<Persone> qb=dao.queryBuilder();
List<Persone> res=qb.where(Properties.Eta.gt(18),
	    qb.and(Properties.Nome.like("a%"),Properties.Nome.like("%a"))).list();

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.


Ti consigliamo anche