GreenDAO un ORM ligero y simple para Android
Para poder gestionar nuestros datos a nivel de base de datos, Android, pone a nuestra disposición la base de datos relacional SQLite así como una serie de clases mediante las cuáles podremos crear el esquema de la base de datos (es necesario crearlo mediante código), como los métodos para poder seleccionar registros, insertarles, eliminarles o actualizarles. Para más información sobre el acceso y la gestión de SQLite en Android podéis leer este artículo en javahispano.
Una vez conocidas estas clases, la gestión del SQLite es fácil, sin embargo, encierra una gran cantidad de código "repetitivo". Tenemos que crearnos nuestras clases con la definición de la tabla, crearlas o actualizarlas heredando la clase SQLiteOpenHelper, crearnos las clases pojo que nos permitirán mapear los datos de las tablas a nuestros objetos y crearnos nuestras clases DAO con todos los métodos para manejar los datos. Para simplificar estas tareas se han desarrollados los ORMS (mapeo objeto relacional).
GreenDAO es un ORM en el que se pretende conservar la simplicidad para ser uno de los ORMs con mayor rendimiento. Teniendo en cuenta, que los dispositivos para los que inicialmente vamos a programar nuestras aplicaciones no disponen de un hardware que nos de toda la potencia que nos puede proporcionar un portatil o un equipo de sobremesa, es una característica a tener muy en cuenta.
La definición que su desarrollador hace de GreenDAO es que es una solución ORM ligera y rápida que mapea objetos de bases de datos SQLite. Destaca como sus principales características:
* Máximo rendimiento. En el gráfico se puede ver la diferencia de rendimiento que hay comparándolo con el ORM ORMLite, en especial en la carga de registros.
* Apis fácil de usar.
* Alta optimización para Android.
* Mínimo consumo de memoria.
* El tamaño de la librería es muy pequeño (menos de 100 kb), ya que centra su foco en los aspectos más importantes y útiles.
Para lograr la optimización en el rendimiento, no vamos a trabajar con anotaciones que luego van a llevar por detrás el uso de la clase reflection. Vamos a tener que programar por un lado el modelo de la base de datos e indicarle en qué proyecto queremos que nos genere las clases dao (data access objects) y las entidades (java data objects). En resumen, GreenDAO, nos va a generar clases, que va a incluir en nuestro proyecto, con la creación del esquema de la base de datos y con todos los métodos necesarios para poder ejecutar cualquier acción CRUD.
Modelo de datos de ejemplos.
Para el ejemplo que vamos a seguir a lo largo del artículo, vamos a crearnos una estructura de base de datos, en la que vamos a tener una relación de 1:N y una relación de N:M. Vamos a crear una base de datos en la que almacenaremos los pedidos que realicen a una tienda. Tendremos tres tablas, la cabecera de los pedidos (Pedido), las líneas de los pedidos (Linea) y las condiciones de pago (CondicionPagoDeUnPedido). Un pedido podrá tener varias líneas y varias condiciones de pago. Las condiciones tienen su propia tabla master (CondicionPago).
Proyecto para generar nuestro modelo de datos.
Como hemos comentado, vamos a tener que crearnos un proyecto java normal, independiente de nuestro programa Android, en donde crearemos el esquema de la base de datos. Necesitaremos añadir las librerías freemarker.jar y greenDAO-generator.jar que van a ser las encargadas de crear las clases dao y las entidades.
Lo primero que tenemos que hacer es crearnos un objeto Schema. Este objeto va a ser el encargado de almacenar todas nuestras entidades con sus características. El constructor tiene dos parámetros, el número de versión y el paquete. Recordemos que cada cambio que realicemos en el modelo de la base de datos, tiene que verse reflejado, en el aumento del número de versión, para que Android, sepa que tiene que realizar un upgrade de la base de datos. El paquete que indicamos es donde va a crear las entidades y los daos.
Schema esquema = new Schema(4, "com.jtristan.greendao.dao");
Cada tabla equivale a un objeto Entity. El nombre de la tabla lo asignamos mediante el método addEntity de Schema. Después tendremos que indicar todos nuestros campos. Aunque no es obligatorio, si es una buena costumbre, crearnos un campo _id, que SQLite identifica como clave y es de tipo auto numérico. Para ello disponemos del método addIdProperty(). En la documentación, se indica que este método, todavía no es totalmente funcional, así que también podríamos crearnos nuestra clave primaria creando un campo de tipo long y que sea clave primaria: addLongProperty("_id").primaryKey(); Para cada uno de los tipos de datos que admite SQLite también tenemos sus métodos (addLongProperty(nombre_capo), addStringProperty(nombre_campo), etc). Podemos indicar si un campo va a ser clave (unique()) o no debe ser nulo (notNull()).
La propiedad se crea a través de un builder PropertyBuilder, a través del cuál podemos asignar otro nombre de columna(columName(String columname)), crear un índice (index()), indicar que la columna no admite valores nulos(notNull), que es clave primaria (primaryKey()) o que los valores tienen que ser únicos (unique())
Entity cabeceraPedido = esquema.addEntity("Pedido"); cabeceraPedido.addIdProperty(); cabeceraPedido.addLongProperty("numeroPedido").unique(); cabeceraPedido.addStringProperty("cliente").notNull(); cabeceraPedido.addLongProperty("direccion").notNull(); cabeceraPedido.addLongProperty("idCondicionPago").notNull(); cabeceraPedido.addBooleanProperty("finalizado"); cabeceraPedido.addDateProperty("fechaCreacion");
Para crear relaciones, tenemos que obtener el objeto Property del campo que va a verse implicado. Tendremos que crear siempre la relación en ambos sentidos.
En nuestro ejemplo, un pedido tendrá varias líneas (1:N) y una línea sólo puede pertenecer a un pedido (1:1).
Si queremos relacionar las líneas con la cabecera del pedido a través del idPedido tendremos que crearnos dicho campo en la entidad Linea y obtener el objeto Property:
Property idPedido = lineaPedido.addLongProperty("idPedido").notNull().getProperty();
Indicaremos que la tabla líneas tiene como clave foranea el idPedido de la entidad cabeceraPedido.
lineaPedido.addToOne(cabeceraPedido, idPedido);
La relación entre el pedido y las líneas es de una a varias, por ello, instanciaremos un objeto ToMany mediante el método addToMany():
ToMany lineasDeUnPedido = cabeceraPedido.addToMany(lineaPedido, idPedido); lineasDeUnPedido.setName("Lineas"); lineasDeUnPedido.orderAsc(idLineaPedido);
Asignamos el nombre a la relación, lo cuál creará en el dao el método getLineas(), que nos devolverá todas las líneas para un pedido. También podemos indicarle la ordenación de los resultados.
GreenDAO no soporta directamente las relaciones N:M. Este tipo de relaciones crean una tabla de enlace, que contiene la clave de la tabla primaria con las claves de la tabla foránea. Para poder establecer una relación de este tipo deberemos crearnos la tabla de enlace y a partir de ahí crear los enlaces.
Entity condicionesPagoDeUnPedido = esquema.addEntity("CondicionPagoDeUnPedido"); Property idPedidoEnCondiciones = condicionesPagoDeUnPedido.addLongProperty("idPedido").notNull().getProperty(); Property idCondicion = condicionesPagoDeUnPedido.addLongProperty("idCondicion").notNull().getProperty(); cabeceraPedido.addToMany(condicionesPagoDeUnPedido, idPedidoEnCondiciones); condicionPago.addToOne(condicionesPagoDeUnPedido, idCondicion);
GreenDAO nos permite tener secciones dentro de las clases dao que genera en las cuáles podremos escribir nuestros métodos. Aunque volvamos a lanzar la generación del modelo de datos, ya sea por añadir una tabla, o modificar alguna característica, estas secciones siempre se respetarán y no se sobreescribirán. Podemos activar estas secciones mediante el método enableKeepSectionsByDefault() de la clase Schema o dentro de canda entidad con el método setHasKeepSection().
GreenDAO en nuestro proyecto Android.
Además de las clases modelo y las clases daos para cada una de nuestras entidades se generan la clase DaoMaster y la DaoSession. La clase DaoMaster es la encargada de crear o actualizar el esquema de la base de datos. Hay que tener cuidado a la hora de actualizar la base de datos, pues este ORM no contempla ningún script para salvaguardar los datos. Simplemente elimina las tablas y las vuelve a crear. Si nuestra aplicación está ya en producción, deberíamos de desarrollar nosotros el método para almacenar los datos o dependiendo del tipo de modificación en la tabla eliminar el DROP TABLE y el CREATE TABLE por un ALTER TABLE. Esta clase también nos permite crearnos un objeto de tipo DaoSession el cual nos dará acceso a nuestros daos.
Para generar el esquema o obtener un objeto SQLiteDatabase debemos implementar la clase interna DevOpenHelper de DaoMaster.
private SQLiteDatabase db; private DaoMaster daoMaster; private DaoSession daoSession; DevOpenHelper helper = new DaoMaster.DevOpenHelper(this, "greendao", null); try{ db = helper.getWritableDatabase(); }catch (Exception e){ System.out.println(e.getMessage()); } daoMaster = new DaoMaster(db);
Como parámetros le pasamos el contexto, el nombre de la base de datos que queremos abrir y si es necesario un CursorFactory. Una vez obtenido el objeto DevOpenHelper ya podemos llamar al método getWritableDatabase() de la clase SQLiteOpenHelper el cuál nos va a dar acceso a la base de datos en modo de lectura/escritura. Finalmente con el objeto SQLiteDatabase ya podemos llamar a la clase DaoMaster y a partir de esta implementar la sesión mediante la clase DaoSession.
A partir de aquí ya podemos obtener referencias a nuestras entidades y sus clases daos.
PedidoDao pedidoDao = daoSession.getPedidoDao();
En las clases daos podemos encontrar los métodos:
- insert(Entity entidad): nos permitirá insertar el registro entidad.
- insertInTx(Iterable entidad): inserta todos los registros creando una transacción.
- update(Entity entidad): actualizaremos el registro por los campos clave.
- updateInTx(Iterable entidades): actualiza todas las entidades mediante una transacción.
- insertOReplace(Entity entidad): si la clave del registro no existe lo inserta y si ya existe actualiza sus campos.
- delete(Entity entidad): elimina el registro que coincida con la clave. Actualmente sólo soporta claves primarias simples.
- deleteAll(): elimina todos los registros de la tabla.
- deleteByKey(K key): elimina el registro que coincida con la clave. También sólo soporta claves primarias simples.
- queryBuilder(): nos permite generar consultas. Vamos a tratar las consultas a continuación.
Existen más métodos que se pueden consultar en la documentación del API.
A tener en cuenta cuando trabajamos con entidades con relaciones, por ejemplo, la cabecera de los pedidos y las líneas, es que si hemos obtenido las líneas e insertamos una nueva esta no va a verse reflejada aún cuando volvamos a llamar al método getLineas(). Esto es debido al caché. Vamos a ver un ejemplo, para entenderlo.
List lineas = pedido.getLineas(); Linea linea = new Linea(); linea.set_id(pedido.getId()); lineaDao.insert(linea); List lineas2 = pedido.getLineas(); Log.i(TAG, lineas.size() + " " + lineas2.size());
Si el tamaño de la primera lista es 1, al insertar el segundo registro esperaríamos que el tamaño de la segunda lista sea 2. Sin embargo, esta sigue siendo 1, ya que no se vuelve a ejecutar una llamada a la base de datos para obtener los registros. Solucionarlo es tan simple como añadir a nuestra Lista el registro que hemos insertado.
lineaDao.insert(linea); lineas.add(linea); Log.i(TAG, String.valueOf(lineas.size()));
Si por algún motivo necesitamos forzar a que si se vuelvan a cargar los registros, tendremos que llamar al método resetNombre_de_la_entidad_a_limpiar: pedido.resetLineas().
Consultas.
Una de las acciones más habituales cuando trabajamos con bases de datos son las consultas. GreenDAO tiene dos métodos para generar estas sqls que cubren cualquier necesidad de consulta que tengamos.
Con queryRaw(String where, String ... selectionArg) pasaremos directamente la SQL entera. No es la opción recomendada, pues vamos a tener que trabajar directamente con código SQL que o bien hemos probado en un analizador sintáctico o podemos cometer errores. Sin embargo, nos permite cubrir opciones que no va a cubrir el método queryBuilder() como joins entre tablas.
queryBuilder nos devuelve un objeto QueryBuilder que es el encargado de permitirnos crear las consultas sin necedidad de utilizar sql.
CloseableListIterator<CondicionPago> consulta = null; consulta = condicionPagoDao.queryBuilder() .where(Properties.Condicion.eq(condicion)).listIterator();
Mediante el método where indicamos el campo de consulta. Añadiremos tantos métodos where como campos formen parte de nuestra consulta. Si tuviéramos que incluir un OR utilizaríamos el método whereOr. La clase Property son meta datos que describen un atributo de nuestra entidad mapeado en una columna de la base de datos. Contiene todos los métodos de comparación para crear el objeto WhereCondition. Así tenemos:
- between(Object value1, Object value2): devuelve los resultados que se encuentran entre los dos valores.
- eq(Object value): equivale a =.
- ge(Object value): mayor o igual que.
- gt(Object value): mayor que.
- le(Object value): el resultado tiene que ser menor o igual que el valor.
- in(Collection values): compondría la sentencia IN de SQL.
- isNull(): sólo valores nulos.
- También tenemos los métodos contrarios, como isNotNull(), notEq(Object value), notIn(Collection notInValues), etc.
Finalmente tendremos que indicar la forma en que queremos que se devuelvan los valores. Por ahora, GreenDAO nos da cuatro opciones.
- list: en una lista normal.
- listLazy: los registros se cargan en memoria. Cuando se accede al primer registro son cacheados. Es necesario cerrar el cursor.
- listLazyUncached(): los registros se cargan en memoria y no se cachean jamás. También es necesario cerrarla.
- listIterator(): devuelve los resultados en un Iterator. Si se alcanza el último registro el cursor se cierra automáticamente, en caso contrario es necesario cerrarlo.
Si necesitamos ejecutar la consulta varia veces, podemos construir un objeto query reutilizable mediante el método build(). De esta forma optimizaremos el rendimiento.
Reader Comments (10)
Yo estoy valorando para cuando tenga algo de tiempo probar JEPLayer en Android, primero con un driver JDBC para SQLLite para Android y luego ver si vale la pena soportar SQLLite usando la API de Android aunque el resultado sería muy diferente.
JEPLayer es una capa muy ligera sobre JDBC estoy seguro que puede funcionar de la manera tradicional (es decir JDBC).
En ORMLite se ha implementado un mecanismo para evitar el uso intensivo de introspección: Se genera un fichero de texto con toda la información en tiempo de desarrollo:
http://ormlite.com/javadoc/ormlite-core/doc-files/ormlite_4.html#SEC41
No se la gráfica de rendimiento sigue siendo válida con este mecanismo (no he hecho ninguna prueba) pero el uso de anotaciones me parece un sistema muy útil que el modelo es fiel reflejo de lo persistido y evitar sorpresas.
En algún sitio he leido, que si bien, Android sólo da soporte a SQLite si está preparado para trabajar con cualquier otra base de datos, al final, todo es JDBC con lo que imagino que no habría problemas en poder usar JEPlayer.
El siguiente ORM que quiero mirar el ORMLite o tal vez ADA framework. Una vez que sepa como usarles se podría intentar hacer una comparativa. Habría que valorar también el nivel de datos que va a manejar las aplicaciones. Imagino que una select que devuelva cinco registros no habrá mucha diferencia se ejecute en el ORM que se ejecute. Otra cosa será en lecturas masivas. Yo también prefiero las anotaciones. No me gusta mucho, el tener que tener un proyecto independiente de la aplicación android, y también es cierto que poder ver claramente todo en la entidad facilita mucho el trabajo.
Seguro que lo conoces (aunque no lo he probado)
https://github.com/SQLDroid/SQLDroid
Yo creo que nada impide que exista una alternativa a SQLLite
Pues no lo conocía pero lo apunto a la lista de "echar un vistazo".
En mi trabajo lo que hicimos fue trabajar con los hbm.xml de hibernate como los archivos de configuracion y agregandole nuevas plantillas de freemaker para generar los DTOs y DAOs propios de android más una tarea ant que genere el jar listo para los proyectos que lo utilicen.
Con esto autogeneramos todos los codigos java de la capa de datos.
Un saludo.
Yo hice algo muy simple, sencillo y potente :)
Se llama gORMiti.
El usuario/programador debe crear SOLO clases bean, con ciertas anotaciones en las que se indican los campos y relaciones.
Se usa reflexion sólo la primera vez y se guardan todas las relaciones atributo-campo, tabla-clase, sql's generadas, etc etc.
ejemplo:
import db.orm.annotation.Column;
import db.orm.annotation.Table;
@Table(name="coches")
public class CocheBean {
@Column(name="matricula", primaryKey=true)
private String matricula;
@Column(name="propietario", foreignKey="gormiti.test.PersonaBean/dni")
private String propietario;
@Column(name="marca")
private String marca;
@Column(name="modelo")
private String modelo;
constructores...
metodos get...
metodos set...
}
forumisto
El enfoque es parecido al nuestro, al final es Domain Driven Design.
Saludos.
Hola, me gustaría saber que es lo que compara la gráfica en el Eje Y... 6000 que?
gracias!