Android/Arquitectura BD SQlite

Introducció a SQLite[modifica]

SQLite és un gestor de bases de dades de codi obert que permet gestionar bases de dades relacionals de forma simple amb un cost de memòria molt reduït, cosa que el converteix en un candidat ideal per estar integrat en altres sistemes i/o aplicacions.

En les bases de dades relacionals, les dades estan organitzades per taules. Una taula és un conjunt de valors organitzats utilitzant un model de columnes verticals i files horitzontals. Una columna és un conjunt de valors del mateix tipus (identificats pels seus noms), un per cada fila de la taula. Un camp es un valor que existeix en una intersecció entre una fila i una columna. Una clau primària identifica de forma única cadascuna de les files de la taula mentre que la clau forana és una referència entre dues taules.


SQLite està integrat en tots els dispositius Android i també en aplicacions i navegadors d'Internet (Chrome, Firefox). La utilizació d'una base de dades en Android no precisa d'una instal·lació o administració de la mateixa; només s'han de definir les declaracions en SQL per crear/actualitzar les dades de la base de dades.

Els accessos a una base de dades SQLite precisen de l'accés al sistema de fitxers. Per tant, es recomanable realitzar operacions a la base de dades de forma asíncrona.

El contingut d'aquest article mostra un exemple de creació d'una petita base de dades seguint l'standard que proposa Google. La principal diferència entre l'standard i una aplicació feta ràpidament és que l'standard defineix una estructura de classes determinada. L'standard proposa 3 classes:

  • Una classe Contracte (per exemple, DBContract), que s'encarrega de definir les constants de la base de dades.
  • Una classe Helper (que estén de DBHelper), que s'encarrega d'obrir/crear/actualitzar la base de dades.
  • Una classe Manager (per exemple, DBManager), que s'encarrega de gestionar la base de dades.

Aquesta estructura no és obligatòria per gestionar una base de dades (es poden tenir totes les classes implementades en una de sola); però és recomanable utilitzar-la a mode de facilitar l'estructuració, el manteniment i la llegibilitat d'aquesta.

SQLite en l'entorn Android[modifica]

A diferència d'altres àmbits de les bases de dades, en el desenvolupament d'aplicacions mòbils SQLite és un dels 5 gestors de bases de dades més utilitzats. Partim d'un gestor de base de dades que treballa amb tipus de dades més petites respecte altres gestors i per tant que ens permet reduir les dimensions de la nostra aplicació. Un altre punt a favor és que es pot utilitzar directament sense cap tipus de configuració, sense haver de configurar paràmetres d'un servidor. Aquesta combinació de característiques proporciona accessos ràpids a memòria i una fàcil usabilitat a nivell de codi.

Les dades que guarda SQLite són emmagatzemades dins d'un dispositiu mòbil com a text, per tant, no és necessari haver d'utilitzar un administrador de bases de dades. Això ens permet que la informació desada d'una aplicació pugui ser utilitzada en multiplataforma. També està suportat l'ús de SQLite en IOS, Blackberry i Windows Phone.

Database Contract[modifica]

Un dels principals principis de les bases de dades SQL són els esquemes (schema). L'esquema és una declaració formal sobre com una base de dades s'organitza, que queda reflectit en les sentències SQL utilitzades per crear la base de dades. És recomanable crear una classe (Contract) que específica explícitament l'esquema de la base de dades.

La classe Contract és un contenidor de constants que defineix noms per les taules, les columnes i les URIs (Uniform Resource Identifier). Amb l'ús de la classe Contract independitzem els noms de les taules i els atributs del a base de dades, de l'ús d'aquesta.

El contingut necessari que ha de tenir la classe contract és:[1]

  • DATABASE_VERSION : integer que defineix la versió de la base de dades. Aquest valor s'usa, com expliquem més endavant, per tal de detectar si la definició de la base de dades ha canviat i per tant s'ha de prendre alguna decisió al respecte.
  • DATABASE_NAME : String que defineix el nom de la base de dades.

Dins de la classe Contract`` cada taula de la base de dades s'ha de definir com una nova classe que implementi la classe BaseColumns[2].

Per cada taula s'ha de definir:

  • TABLE_NAME : String que defineix quin és el nom de la taula.
  • COLUMN_NAME : String que defineix quin és el nom d'una columna de la base de dades. S'han de definir tantes com columnes tingui la taula.
  • CREATE_TABLE : String que defineix la instrucció encarregada de realitzar la creació de la taula. El contingut d'aquesta constant està formulada en el llenguatge SQL.
  • DROP_TABLE : String que defineix la instrucció encarregada de destruir la taula. El contingut d'aquesta constant està formulada en el llenguatge SQL.

Exemple[modifica]

En el següent codi es mostra un exemple que té definides tres taules: la taula Table1 que defineix els usuaris; aquests usuaris disposen del seu name i email, cada un d'aquests atributs corresponent a una columna. La taula Table2 defineix les xarxes socials, amb els atributs name i counter que determinen, respectivament, el nom i quantes subscripcions (usuaris) disposa aquella xarxa social i l'última taula, Table12 indica la relació entre les dues taules anteriors on per cada parell de dades email i nom té una register_date que indica la data de registre d'aquest usuari a la determinada xarxa social.




Database Helper[modifica]

La classe Database Helper (DBHelper) es una classe que s'ha d'estendre de la classe abstract SQLiteOpenHelper. Aquesta classe s'encarrega de proporcionar l'accés a la base de dades i d'actualitzar-la si aquesta ja existís o crear-la de nou si no existís o el sistema ho requerís.

S'ha de fer l'override dels 2 mètodes de la classe la qual s'està estenent:

  • onCreate : mètode que ha de contenir les instruccions necessàries per crear la base de dades. Sobre l'objecte SQLiteDatabase que es rep per paràmetre usarem la funció execSQL per executar les sentències sQL necessàries. Cada crida a execSQL crearà una taula o un índex: per tant l'argument de cada crida a la funció execSQL és una de les constants de creació definides unes constants en la classe Contract.
  • onUpgrade : mètode que ha de contenir les instruccions necessàries per tal d'actualitzar la base de dades quan hi ha un canvi d'esquema. Aquest mètode s'executa quan el número de la versió de la base de dades (definit en la classe Contract) és superior respecte la versió de la base de dades instal·lada. En el cos del mètode se sol prendre la decisió respecte el canvi de versió. Per exemple, si se sap que per canviar d'una versió 'i' a una versió 'j' l'únic que s'ha fet es crear una taula nova, es pot executar només aquesta instrucció. O també, per exemple, es pot es pot fer el buidatge de la base de dades sencera i tornar-la a crear de nou. Les instruccions tornen a ser funcions execSQL de l'objecte tipus SQLiteDatabase que es rep per paràmentre, passant-li com a argument les accions definides en la classe Contract.

També s'ha d'escriure el constructor de la classe, que s'encarregarà de enviar a la seva classe super (SQLiteOpenHelper en aquest cas) el context d'execució, el nom de la base de dades (definit al Contract), el cursor (si es vol definir, sinó s'indica com a null) i la versió de la base de dades. El Context és una classe abstracta, la implementació de la qual està proporcionada per el sistema Android; el context permet els accessos als recursos específics de l'aplicació, com les bases de dades pròpies.


Exemple[modifica]

A continuació es continua mostrant l'exemple anterior, en aquest cas la classe Helper.




Database Manager[modifica]

Operacions sobre la base de dades[modifica]

La classe Manager no es cap recomanació de l'standard de Google. Però s'introdueix per simetria amb les recomanacions de Google: és una classe que agrupa totes les interaccions que es duen amb la base de dades. Així les diferents interaccions amb la base de dades queden totes concentrades en una sola classe, i no pas disperses pel codi.

Principalment existeixen quatre tipus d'interaccions amb una base de dades:

  • insert : acció de introduir noves files a una taula de la base de dades.
  • update : acció d'actualitzar una fila de la base de dades introduïda anteriorment.
  • delete : acció d'eliminar d'una taula d'una base de dades una fila de la que ja no es vulgui conservar la seva informació.
  • query : acció d'obtenir algun tipus d'informació de la base de dades.

Exemple[modifica]

En tot moment la classe Manager ha de tenir visibiltiat sobre la classe helper, que és qui ens proporciona l'accés a la base de dades. Per això en l'exemple fem que el constructor de Manager sigui l'encarregat de crear l'objecte de la classe Helper i de mantenir-lo com un membre de dades de la classe. A continuació es mostra el codi d'aquest constructor.




Permisos d'accés[modifica]

Cal destacar que totes les interaccions necessiten accedir a la base de dades, i per això prèviament se n'ha de demanar el permís d'accés. Aquest permís pot ser:

  • Permís de lectura : S'atorga si la classe helper de la base de dades no ha atorgat el permís d'escriptura a ningú. Dos permisos de lectura són permesos simultàniament.
public SQLiteDatabase SQLiteOpenHelper::getReadableDatabase();
  • Permís d'escriptura: Aquest permís s'atorga si la classe Helper de la base de dades no ha atorgat cap permís ja sigui d'escriptura o de lectura.
public SQLiteDatabase SQLiteOpenHelper::getWritableDatabase();

Per indicar que el permís d'accés a la base de dades ja no és necessari, i per tant es pot suprimir, s'ha d'executar la següent comanda:

public synchronized void SQLiteOpenHelper::close();

Exemple[modifica]

Tot seguit es mostren un o més exemples de cada interacció amb la base de dades, xcorresponents a l'exemple ja definit a l'inici de l'article.

Interacció Insert[modifica]

La interacció d'inserir files en la base de dades requereix el permís d'accés del tipus escriptura a la base de dades.

La manera més simple d'inserir una nova fila és mitjançant un objecte ContentValues.

La funció necessària per a fer l'insert es la següent:

public long SQLiteDatabase::insert(String table, String nullColumnHack, ContentValues values);


Els seus paràmetres indiquen:

  • table : String que indica la taula on s'inserirà la fila.
  • nullColumnHack :
  • values : contentValues que conté el contingut de la fila.

Aquesta funció retorna -1 si s'ha produït algun error durant la inserció. Un exemple d'error en la inserció és l'intent d'afegir una fila que ja està a la taula. Si no hi ha cap error, retorna l'índex de la fila que s'acaba de crear.

Un exemple d'inserir és el codi següent en el qual s'insereix una nova fila a la taula 1 amb el parell name i email:




Interacció Update[modifica]

La interacció de sobrescriure files en la base de dades requereix el permís d'accés del tipus escriptura a la base de dades.

Igual que en la inserció d'una nova fila, la manera més simple de modificar una fila existent és mitjançant un objecte ContentValues.

La funció necessària per sobrescriure una fila és la següent:

public int SQLiteDatabase::update(String table, ContentValues values, String whereClause, String[] whereArgs);


Els seus paràmetres indiquen:

  • table : String que indica la taula on es sobreescriurà la fila.
  • values : contentValues que conté el contingut a modificar.
  • whereClause : String que indica la columna que es compararà amb el contingut del següent argument sempre que la columna es concateni amb l'String "=?". Si aquest argument és null significa que efectuarà la sobreescriure en totes les columnes.
  • whereArgs : string[] que indica tots els valors els quals pot prendre la columna indicada en el argument anterior.

Aquesta funció retorna el número de files afectades.

Un exemple de sobreescriure és el codi següent en què es sobreescriu la columna que indica el comptador d'usuaris de la xarxa social de la taula 2:




Interacció Delete[modifica]

La interacció d'eliminar files de la base de dades requereix el permís d'accés del tipus escriptura a la base de dades.

La funció necessària per eliminar una fila és la següent:

public int SQLiteDatabase::delete(String table, String whereClause, String[] whereArgs);

Els seus paràmetres indiquen:

  • table : String que indica la taula d'on s'esborrarà la fila.
  • whereClause : String que indica la columna que es compararà amb el contingut del següent argument sempre que la columna es concateni amb l'string "=?". Si aquest argument és null significa que s'eliminaran totes les columnes.
  • whereArgs : string[] que indica tots els valors que pot prendre la columna indicada en l'argument anterior.

Aquesta funció retorna el número de files afectades si s'indica ´whereClause, altrament retorna 0. Si es volen eliminar totes les files i saber quantes s'han eliminat s'ha d'indicar "1" en el paràmetre whereClause.

Un exemple d'eliminar és el codi següent en el qual s'elimina la fila de la taula 2 de la xarxa social que s'indica per paràmetre:




Interacció Query[modifica]

La interacció d'obtenir informació d'una taula de la base de dades requereix el permís d'accés del tipus lectura a la base de dades.

Les funcions que ens permeten fer les consultes sempre ens retornen un objecte. Cursor


La funció necessària per realitzar aquesta consulta té principalment quatre variants:




Els seus paràmetres indiquen:

  • distinct : booleà que indica si es volen suprimir les files repetides.
  • table : String que indica la taula on se sobrescriurà la fila.
  • columns : String[] que indica el llistat de columnes que es mostraran. Si aquest argument és null s'ignifica que es mostraran totes les columnes.
  • selection : String que indica la columna que es compararà amb el contingut del següent argument sempre que la columna es concateni amb l'string "=?". Si aquest argument és null significa es retornaran totes les columnes.
  • selectionArgs : String[] el qual indica tots els valors que pot prendre la columna indicada en l'argument anterior.
  • groupBy : String que indica com s'aplica el filtre de group by de l'SQL. Si aquest argument és null llavors no s'aplicarà cap filtre.
  • having : String que indica quins grups creats pel filtre group byes visualitzen. Si l'argument és null llavors es visualitzen tots els grups. Si l'argument groupBy és null és obligatori que aquest també ho sigui.
  • orderBy : String que indica amb quin ordre visualitzen les files. Si aquest argument és null llavors és mostraran amb l'ordre per defecte.
  • limit : String que indica quin es el límit de files mostrades. Si aquest argument és null es mostraran totes les files possibles.
  • cancellationSignal : cancellationSignal que permet indicar qui enviarà un signal de cancelació si es vol interrompre la query. L'enviament del signal provoca l'exepció OperationCanceledException.

Aquesta funció retorna el número de files afectades si s'indica whereClause, altrament retorna 0.

Un exemple de consulta és el codi següent en el qual pretén saber a quines xarxes socials està subscrit l'usuari indicat per paràmetre:




Hi ha un altre tipus de consulta, no gaire recomanable, que permet realitzar qualsevol query en format SQL. La funció és la següent:

public Cursor SQLiteDatabase::rawQuery(String sql, String[] selectionArgs)

Els seus paràmetres indiquen:

  • sql : String que indica la consulta SQL; aquesta no ha d'acabar amb ;.
  • selectionArgs : String[] que indica tots els valors els quals compara la clàusula selection si aquesta acaba amb l'String "=?".

Classe ContentProvider[modifica]

Una base de dades en sistemes Android és privada per l'aplicació que la crea; no existeix un espai comú d'emmagatzemament en el que diferents aplicacions la puguin compartir. Per tal que diferents aplicacions puguin utilitzar una mateixa base de dades, el sistema Android proveeix els ContentProvider.

El ContentProvider és un dels components primaris de les aplicacions d'Android; proporcionen contingut a les aplicacions. S'encarreguen d'encapsular les dades i de proporcionar-les a les aplicacions a través d'una sola interficie ContentResolver. El ContentProvider és necessari quan s'hagin de compartir dades entre múltiples aplicacions. Si no s'han de compartir les dades entre moltes aplicacions es pot utilitzar una base de dades a través de la classe SQLiteDatabase.

Quan una petició es fa a través d'un ContentResolver el sistema inspecciona l'autoritat de la URI donada i es passa la petició al ContentProvider registrat en l'autoritat.

Classe ContentResolver[modifica]

La classe ContentResolver s'encarrega d'obtenir/extraure les dades proporcionades per el ContentProvider. Aquestes dades es comuniquen amb el ContentProvider, determinades per la URI donada. Així que, quan volem obtenir les dades del ContentResolver, el sistema avalua la URI donada i envia la petició al ContentProvider.

Per obtenir l'objecte de tipus ContentResolver s'utilitza el mètode getContentResolver().

Classe ContentValues[modifica]

La classe ContentValues es necessita per tal d'introduir una fila a una taula de la base de dades. A continuació es mostra un exemple del procediment a seguir:

ContentValues values = new ContentValues();
values.put(key, value);

Aquest objecte permet crear una fila indicant el parell clau i valor de totes les columnes de la taula. Per tal de definir el paràmetre key s'usa la constant necessària de la classe Contract.

Classe Cursor[modifica]

La classe Cursor es necessita per tal d'obtenir el resultat d'una query. Normalment se sol iterar el cursor i se n'obtenen els valors que a retornat la query.
Un exemple d'aquest recorregut:




Notes[modifica]

  1. El que importa és definir les constants amb la semàntica indicada. El nom emprat és decisió del programador.
  2. La classe BaseColumns s'encarrega d'afegir a la nostra definició de base de dades el tag _ID com a nom de l'atribut identificador de cada fila; i _COUNT, que indica quantes files té la taula. Aquests tags els agefeix BaseColumns per tal que les classes DBHelper i DBManager puguin crear i fer un ús correcte de les taules de la base de dades creada.

Referències[modifica]