ADA Framework (Android Data Abstraction Framework) es un ORM supervitaminado. Está creado por Txus Ballesteros de Mob&Me. Si queréis escuchar un resumen muy completo de sus características podéis escuchar este podcast en Javahispano.
Como cualquier ORM nos va a permitir despreocuparnos de todo lo relacionado con la base de datos. Ya no será necesario crearnos nuestras sqls de creación de tablas o todas las clases dao. Simplemente, deberemos crearnos nuestras clases entidades, donde mediante anotaciones, estableceremos sus propiedades y sus relaciones. Pero además de ser un ORM también nos va a permitir gestionar las validaciones, maneja la relación entre nuestros views y los atritutos de las entidades (databinder), permite llenar ListViews, etc.
También cabe destacar su potencia. Podéis encontrar en el repositorio de github una prueba de rendimiento comparándolo con ORMLite en el que sale como un claro ganador. También es importante su pequeño tamaño, no superando los 73 kb. Un tamaño muy ajustado para todo lo que ofrece.
En el tutorial simularemos la creación de una aplicación para gestionar la información básica de una academia. Vamos a gestionar los datos relacionados con los profesores, con los cursos que imparten y de los alumnos y los cursos a los que están inscritos.
Un profesor puede tener varios cursos pero un curso sólo puede estar impartido por un profesor, con lo que tenemos una relación 1:N. Así mismo, un alumno puede estar inscrito en varios cursos y cada curso puede tener varios alumnos (N:M).
Las clases más importante de ADA framework son:
Para definir una clase que queramos que persista es necesario que esta extienda de la clase Entity. En esta clase, mediante anotaciones, definiremos el nombre de la tabla @Table(name = "profesores") y el nombre y las características de cada campo @TableField(name = "numero_horas_clase", datatype = DATATYPE_INTEGER). Todas las anotaciones las encontramos en las clases annotations.Table y annotations.TableField.
A nivel de campo, le indicaremos el tipo (DATATYPE). Los tipos que nos permite usar SQLite son:
DATATYPE_BOOLEAN. *
DATATYPE_INTEGER.
DATATYPE_LONG.
DATATYPE_DOUBLE.
DATATYPE_REAL.
DATATYPE_TEXT.
DATATYPE_STRING.
DATATYPE_DATE.
DATATYPE_BLOB.
DATATYPE_ENTITY.
DATATYPE_ENTITY_REFERENCE: está deprecado en las últimas versiones ya que es sustituido por DATATYPE_ENTITY_LINK.
* En SQLite no existe el tipo de datos boolean así que se le trata como un integer con el valor 1-0.
En Android, para SQLite se recomienda crear una clave única que sea el identificador. No es necesario declararnos ningún campo ID para estos menesteres pues ADA lo crea automáticamente y lo añade a nuestros objetos Entitys.
Mediante los tipos DATATYPE_ENTITY, DATATYPE_ENTITY_REFERENCE y DATATYPE_ENTITY_LINK podremos establecer relaciones entre nuestras clases. ENTITY_REFERENCE se utiliza cuando quieres hacer referencia a una tabla maestra que dispone de su propio ObjectSet y podemos usarle tanto con valores simples como con listas. El tipo ENTITY nos permite insertar una clave foránea en la tabla y hoy en día no admite listas. Con ENTITY_LINK podemos establecer relaciones 1:N entre entidades y entidades maestras.
Nuestra clase de profesores, además de tener sus datos básicos, debe tener los cursos que imparte. Aquí tenemos su definición:
@Table(name = "profesores") public class Profesor extends Entity{ @TableField(name = "numero_horas_clase", datatype = DATATYPE_INTEGER) private int numero_horas_clase; @TableField(name = "nombre", datatype = DATATYPE_STRING, required = true) private String nombre; @TableField(name = "apellido", datatype = DATATYPE_STRING, required = true) private String apellido; @TableField(name = "dni", datatype = DATATYPE_STRING) private String dni; @TableField(name = "curso", datatype = DATATYPE_ENTITY) private List cursos; public Profesor(){ cursos = new ArrayList(); } public int getNumero_horas_clase() { return numero_horas_clase; } public void setNumero_horas_clase(int numero_horas_clase) { this.numero_horas_clase = numero_horas_clase; } public String getNombre() { return nombre; } public void setNombre(String nombre) { this.nombre = nombre; } public String getApellido() { return apellido; } public void setApellido(String apellido) { this.apellido = apellido; } public String getDni() { return dni; } public void setDni(String dni) { this.dni = dni; } public List getCursos() { return cursos; } public void setCursos(List cursos) { this.cursos = cursos; } }
Mediante el datatype DATATYPE_ENTITY le estamos indicando que debe guardar la relación de los cursos con los profesores. A nivel de base de datos, lo que está haciendo es añadir la clave del profesor en el curso. De esta forma, cuando pasemos la información de un profesor añadiremos los cursos que imparte. Si obtenemos la información de un profesor también obtendremos toda la información de los cursos.
Otras anotaciones importantes son:
Si deseamos calcular el número total de horas que trabajan todos los profesores, tendremos que declararnos un campo virtual, y después, en el objectSet, mediante en método search, en el parámetro de los campos que deseamos recuperar tenemos que pasar el campo sum(xxx) as xxx. Más adelante profundizaremos en la clase objectSet, por ahora sólo necesitamos saber, que es la responsable entre otras cosas de permitirnos realizar búsquedas.
@TableField(name = "sum_numero_horas_clase", datatype = DATATYPE_INTEGER, virtual = true) private int sum_numero_horas_clase; //search(pDistinct, pFields, pWherePattern, pWhereValues, pOrderBy, pGroupBy, pHaving, pOffset, pLimit); List profesores = contextoDatos.profesorDao.search(false, new String[]{"sum(numero_horas_clase) as sum_numero_horas_clase"}, null, null, null, null, null, null, null);
*Si nuestra clase está relacionada con otras, como es el caso de profesores, no funcionaría. Es decir, sólo podemos aplicar funciones de agregado a clases que no contengan otras.
Una vez que disponemos de nuestras entidades debemos instanciar los objetos ObjectSet en la clase ObjectContext.Esta clase es la encargada de realizar todo el trabajo acceso a la base de datos, ya sea para crear o actualizar el esquema de la base de datos o para obtener acceso a los registros de la misma.
En nuestra aplicación debemos crearnos una clase que extienda de ObjectContext.
public class ContextoAplicacionDatos extends ObjectContext { public ObjectSet profesorDao; public ObjectSet alumnoDao; public ObjectSet cursoDao; public ObjectSet alumnoEnUnCursoDao; public ContextoAplicacionDatos(Context pContext) throws AdaFrameworkException { super(pContext, "AdaFramework_test.db"); if (profesorDao==null) profesorDao = new ObjectSet(Profesor.class, this); if (alumnoDao==null) alumnoDao = new ObjectSet(Alumno.class, this); if (cursoDao==null) cursoDao = new ObjectSet(Curso.class, this); if (alumnoEnUnCursoDao==null) alumnoEnUnCursoDao = new ObjectSet(AlumnoEnUnCurso.class, this); } }
* Evita crear campos privados en esta clase que no sean los ObjectSet. Se produce un problema en el acceso a estos campos que impide poder abrir la base de datos.
En la clase ContextoAplicacionDatos realizamos dos tareas:
La clase ObjectContext tiene tres constructores:
Si no le pasamos el nombre de la base de datos y la versión el genera un nombre por defecto y toma el control de las versiones. Recordemos que Android utiliza el nombre de las versiones para saber si tiene que llamar al método onUpgrade (SQLiteDatabase db, int oldVersion, int newVersion) de la clase SQLiteOpenHelper para actualiar el esquema de la base de datos.
Alguno de los métodos que nos encontramos en esta clase son:
Los siguientes tres métodos ejecutarían sentencias de insercción, actualización y eliminación de datos. Naturalmente, no será necesario utilizar ninguno de estos métodos cuando estemos trabajando con los ObjectSets pues ellos ya realizan todas estas tareas aislándonos de las características de una base de datos relacional.
Esta clase es una lista que contendrá todas las entidades que insertamos o seleccionemos de la base de datos. Nos proporciona todos los métodos que necesitamos para realizar las tareas CRUD (Crear, Obtener, Actualizar y Borrar).
Si queremos añadir una entidad podremos usar el método add(). Este método no inserta la entidad en la base de datos, simplemente la añade a la lista. También podemos añadir una entidad en una posición determinada de nuestra ObjectSet con add(final int location,final T object) o añadir directamente una lista con addAll (Collection<?extends T > collection).
Para insertar los datos, tendremos que utilizar el método save(). También podemos salvar los datos sin necesidad de haberles añadido con el método save(T pEntity). En este caso, el registro no se conservará en el ObjectSet con lo que si ejecutamos el método get(int indice) nos devolverá una excepción. Para usos normales, es más óptimo añadir la entidad y después salvarla, pues de esta forma, no es necesario volver a recuperar de la base de datos la entidad.
El método save, no sólo nos permite realizar inserts, sino que es el encargado de actualizar o eliminar un registro. Para que ADA sepa qué acción debe realizar en cada momento, tenemos que indicarle en el método setStatus de la entidad si deseamos insertar el registro, actualizarlo o eliminarlo.
//Insertamos la entidad curso. Curso curso = new Curso(); curso.setNombre("GESTIÓN DE RECURSOS"); curso.setNumeroMaximoAlumnos(50); curso.setPrecioHora(2.25); curso.setStatus(Entity.STATUS_NEW); contextoDatos.cursoDao.add(curso); contextoDatos.cursoDao.save(); //Eliminamos el curso que hemos añadido. curso1 = contextoDatos.cursoDao.get(0); curso1.setStatus(Entity.STATUS_DELETED); contextoDatos.cursoDao.save(); //Actualizamos una entidad. curso = contextoDatos.cursoDao.get(0); curso.setNumeroMaximoAlumnos(35); curso.setStatus(Entity.STATUS_UPDATED); contextoDatos.cursoDao.save();
Para las búsquedas disponemos de los métodos fill():
También disponemos de los métodos search para realizar las búsquedas. Estos nos permiten realizar búsquedas más completas, estableciendo si la sql tiene que trabajar con el DISTINCT, los campos que queremos recuperar, el where, etc, y ejecutar las funciones de agregados.
La diferencia entre los métodos fill y search, es que mediante una búsqueda fill se devolverán todas las entidades relacionadas con el objeto propietario. Por ejemplo, si tenemos un pedido, que está formado por una lista de líneas de pedido, al hacer la búsqueda mediante el método fill, recibiremos tanto el objeto pedido como sus líneas. Si hacemos la búsqueda mediante el método search, sólo recibimos la entidad maestra. En el caso de los pedidos, tendríamos acceso al objeto pedido, a nivel de cabecera, pero no a sus líneas.