Introducción
En este artículo estaremos viendo de manera simple el trabajo con la persistencia de datos en CRIAX-SDK. El mismo estará dividido en 2 partes, la primera cubrirá un caso sencillo de cursos y estudiantes, realizado en una aplicación para móviles. La segunda parte será una aplicación de escritorio muy sencilla para poder gestionar permisos de usuarios. Ambas aplicaciones no son reales y el código y las prácticas desarrolladas en las mismas son con fines educativos.
La aplicación móvil
La aplicación móvil es muy sencilla un listado de estudiante donde si seleccionamos uno podremos ver los datos del curso donde se encuentra y el listado de sus compañeros de curso.
Crear la base de datos
En este caso no utilizaremos el vendor sqlitemanager para crear la base de datos, sino el programa SQLiteStudio para poder crear las llaves foráneas de manera visual (una simple comodidad).
Creamos una base de datos llamada escuela, que constará de 2 tablas, curso (con el nombre del curso y el id) y estudiante (con el nombre del estudiante, id y el id del curso al que pertenece) una vez terminada el código de la base de datos queda:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | -- BD: escuela-- Table: cursoCREATE TABLE curso ( id_curso INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE, nombre VARCHAR NOT NULL);-- Datos: cursoINSERT INTO [curso] ([id_curso], [nombre]) VALUES (1, 'curso1');INSERT INTO [curso] ([id_curso], [nombre]) VALUES (2, 'curso2');INSERT INTO [curso] ([id_curso], [nombre]) VALUES (3, 'curso3');INSERT INTO [curso] ([id_curso], [nombre]) VALUES (4, 'curso4');-- Table: estudianteCREATE TABLE estudiante ( id_estudiante INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE, nombre VARCHAR NOT NULL, curso_id INTEGER NOT NULL REFERENCES curso ( id_curso ));-- Datos: estudianteINSERT INTO [estudiante] ([id_estudiante], [nombre], [curso_id]) VALUES (1, 'estudiante1', 1);INSERT INTO [estudiante] ([id_estudiante], [nombre], [curso_id]) VALUES (2, 'estudiante2', 1);INSERT INTO [estudiante] ([id_estudiante], [nombre], [curso_id]) VALUES (3, 'estudiante3', 1);INSERT INTO [estudiante] ([id_estudiante], [nombre], [curso_id]) VALUES (4, 'estudiante4', 2);INSERT INTO [estudiante] ([id_estudiante], [nombre], [curso_id]) VALUES (5, 'estudiante5', 3);INSERT INTO [estudiante] ([id_estudiante], [nombre], [curso_id]) VALUES (6, 'estudiante6', 3); |
Crear la aplicación
Lo primero que haremos será crear el skeleton de la aplicación llamada escuela. Para ello nos paramos en el directorio raíz de la plataforma y tecleamos por consola:
1 2 | $ cd Criax$ create-application (.bat o .sh) -n escuela -o .../CriaxProyect/ -t firefoxos |
Una vez creada la aplicación el siguiente paso es generarla para pre-compilar las clases y dejarlas listas para futuro desarrollo de otras aplicaciones, para ello nos vamos hasta el directorio raíz de la aplicación creada y tecleamos en la consola:
1 2 | $ cd escuela$ toolchain/generate(.bat o .sh) |
Quizás demore un poquito pues esto pre-compila todas las clases de la plataforma. Una vez que termine la generación se nos mostrará una ventana indicándonos que la aplicación esta lista para comenzar a ser desarrollada.
Mapear la bd
La primera acción será mapear la base de datos creada, esto nos facilitará el trabajo, para ello abrimos el vendor CormxDesktop (nuevo en la plataforma), de esta manera obtendremos el esquema y las clases necesarias para la gestión de la información (a las mismas hay que hacerle pequeños ajustes). Primero crearemos un workspace para los proyectos de cormx.
Y luego crearemos el proyecto escuela:
Para escanear la base de datos vamos a Base de Datos -> Escanear, esta acción sacará la meta información de la base de datos (tablas, columnas y características de las mismas). Si se desea inmediatamente después de terminado el escaneo de la base de datos, podemos generar el esquema que brindará información a al aplicación sobre la base de datos. Para generar el esquema vamos a Esquema -> Generar.
Si vamos al directorio donde se creó el proyecto podemos abrir el archivo schema.json que se encuentra dentro del directorio escuela/schema/ y echarle un vistazo al código generado sobre el mapeo de la bd. La mayor parte del tiempo no se utiliza la información tal y como se toma de la base de datos, entonces podemos comenzar por personalizar el nombre de las clases que intervienen en la persistencia, para ello vamos a Entidades -> Gestionar. Seleccionamos la tabla y podremos modificar los nombres de la clase Finder, Fixture, IdentityMap, Mapper y Entidad. Para el presente caso se mantienen tal y como se generaron. Como se puede apreciar en la vida real se puede modificar el namespace completo de cada clase.
Para gestionar la información de loa atributos y métodos de las entidades vamos a Atributos -> Gestionar allí seleccionamos la tabla, el atributo y damos click en la lupa para buscar la información del mismo. Una vez modificado damos click en el botón Actualizar.
Para el presente caso, las columnas id se llamarán id y los métodos accesores y mutadores getId y setId respectivamente. En la columna curso_id de la tabla estudiante, el atributo se llamará curso y los métodos getCurso y setCurso, si nos fijamos aquí el tipo de dato es un objeto, este es un elemento importante a tener en cuenta para más adelante en las relaciones.
Para gestionar las relaciones (en este caso de uno a muchos entre curso y estudiante) vamos a Entidades -> Relaciones. La relación se establece siempre en la tabla de muchos, para el presente caso estudiante.
En esta aplicación no se crearán eventos, pero para gestionar los mismos en CormxDesktop vamos a Entidades -> Eventos. Los eventos son aquellos métodos que se ejecutarán automáticamente antes y después de insertar, actualizar o eliminar datos en una entidad.
Una vez terminada toda la gestión conforme a lo que se desea generamos el esquema en Esquema -> Generar y las entidades en Entidades -> Generar -> Generar todas las entidades, si queremos generar una por una las entidades Entidades -> Generar -> Generar entidad, donde seleccionamos la tabla de la entidad que deseamos generar.
Ahora solo nos queda exportar el proyecto, esta acción realiza una copia del esquema en el directorio correspondiente de la aplicación escuela/source/resource/persistence/, así como las clases en su namespace escuela/source/class/escuela/entity/, creando automáticamente el directorio entity. Para ello vamos a Proyecto -> Exportar.
La aplicación CormxDesktop tiene otras funcionalidades como: cambiar al idioma inglés, gestionar el workspace y proyecto en que se trabaja, entre otras.
Finalmente podremos hacer los arreglos pertinentes desde el mismo proyecto, pues hay que recordar que esta es la primera versión de CormxDesktop. Dichos arreglos son:
- En los métodos
setIdde las entidades remplazamos:
1 | this.__id = id; |
Por:
1 2 3 | if(this.__id == null){ this.__id = id;} |
- En las entidades, clases finder, fixture y mapper arreglamos el nombre del constructor
constructorporconstruct. - En las clases identity-map podemos eliminar toda la sección de las propiedades y los métodos, quedando:
1 2 3 4 5 6 7 8 9 10 11 12 13 | //clase identity-map de cursoqx.Class.define("escuela.entity.im.CursoIdentityMap",{ type : "singleton", extend : cormx.opm.im.IdentityMap}); //clase identity-map de estudianteqx.Class.define("escuela.entity.im.EstudianteIdentityMap",{ type : "singleton", extend : cormx.opm.im.IdentityMap}); |
- En las clases identity-map y mapper debemos encerrar el nombre del tipo de clase entre comillas
type : "singleton",. - En el esquema generado
escuela/source/resource/persistence/schema.jsona los campos id ponerle una longitud de 10"length" : "10".
Las entidades
Una vez realizados todos estos arreglos las clases de las entidades quedan:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 | /** * Enitdad de la tabla curso * * @class Curso * @public * @extends qx.core.Object * @author ... * @namespace escuela.entity * @copyrigth ... * @license ... * @version ... * * @use(escuela.entity.mapper.CursoMapper) * @use(escuela.entity.im.CursoIdentityMap) * @use(escuela.entity.finder.CursoFinder) * para relaciones */qx.Class.define("escuela.entity.Curso",{ extend : qx.core.Object, /** * @property */ properties : { /** * atributo para la columna de id_curso * * @name id_curso * @private * @type {Integer} */ __id : {}, /** * atributo para la columna de nombre * * @name nombre * @private * @type {String} */ __nombre : {} }, /** * metodo de inicializacion de la clase * * @constructor * @public */ construct : function() { }, /** * @method */ members : { /** * Metodo para devolver ... * * @method getId * @public * @return {Integer} */ getId : function(){ return this.__id; }, /** * Metodo para establecer ... * * @method setId * @public * @return {this} */ setId : function(id){ if(this.__id == null){ this.__id = id; } return this; }, /** * Metodo para devolver ... * * @method getNombre * @public * @return {String} */ getNombre : function(){ return this.__nombre; }, /** * Metodo para establecer ... * * @method setNombre * @public * @return {this} */ setNombre : function(nombre){ this.__nombre = nombre; return this; } } }); /** * Enitdad de la tabla estudiante * * @class Estudiante * @public * @extends qx.core.Object * @author ... * @namespace escuela.entity * @copyrigth ... * @license ... * @version ... * * @use(escuela.entity.mapper.EstudianteMapper) * @use(escuela.entity.im.EstudianteIdentityMap) * @use(escuela.entity.finder.EstudianteFinder) * para relaciones * @use(escuela.entity.Curso) */qx.Class.define("escuela.entity.Estudiante",{ extend : qx.core.Object, /** * @property */ properties : { /** * atributo para la columna de id_estudiante * * @name id_estudiante * @private * @type {Integer} */ __id : {}, /** * atributo para la columna de nombre * * @name nombre * @private * @type {String} */ __nombre : {}, /** * atributo para la columna de curso_id * * @name curso_id * @private * @type {Object} */ __curso : {} }, /** * metodo de inicializacion de la clase * * @constructor * @public */ construct : function() { }, /** * @method */ members : { /** * Metodo para devolver ... * * @method getId * @public * @return {Integer} */ getId : function(){ return this.__id; }, /** * Metodo para establecer ... * * @method setId * @public * @return {this} */ setId : function(id){ if(this.__id == null){ this.__id = id; } return this; }, /** * Metodo para devolver ... * * @method getNombre * @public * @return {String} */ getNombre : function(){ return this.__nombre; }, /** * Metodo para establecer ... * * @method setNombre * @public * @return {this} */ setNombre : function(nombre){ this.__nombre = nombre; return this; }, /** * Metodo para devolver ... * * @method getCurso * @public * @return {Object} */ getCurso : function(){ return this.__curso; }, /** * Metodo para establecer ... * * @method setCurso * @public * @return {this} */ setCurso : function(curso){ this.__curso = curso; return this; } } }); |
Desarrollo de la aplicación
Ahora pasemos a lo importante, la funcionalidad de nuestra aplicación, para ello abrimos el vendor Criax-IDE …/Criax/vendors/Criax-IDE/spket (.exe o .bin) y creamos un nuevo proyecto, a diferencia de lo que se piensa la ruta del proyecto en el IDE no será el directorio raíz de la aplicación sino escuela/source/class/escuela/. Seguidamente lo que haremos será crear un modelo que se encargará de trabajar la lógica de negocio de la aplicación. Para ello en el IDE seleccionamos el directorio model y allí dentro creamos un archivo llamado Escuela.js. Con la ayuda de los snippets creamos una clase modelo.
Lo primero que haremos será mostrar un listado de los estudiantes y cuando seleccionemos el estudiante la información del curso en el que se encuentra y debajo todos los demás estudiantes de ese mismo curso. De esta manera lo primero que haremos sera un método que devuelva todos los estudiantes.
1 2 3 4 | getEstudiantes : function(){ var em = this.container('entityManager'); return em.all(escuela.entity.Estudiante);} |
Ahora en el controlador de la página 1, preparamos el entorno para que se carguen los datos de la aplicación si es la primera vez o si ya estan cargados previamente, para ello en el método index ponemos:
1 2 3 4 5 6 7 | var self = this;var em = this.container("entityManager");em.loadData(function(){ },function(){ }); |
Como las aplicaciones móviles deben crear toda la estructura de la base de datos desde el inicio, crearemos una clase en el namespace vendor cuyos métodos tendrán el código sql para crear la estructura de la base de datos.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 | qx.Class.define("escuela.vendors.SqlDb",{ type : "static", statics : { tablaCurso : function(){ return "CREATE TABLE curso (" + "id_curso INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE, " + "nombre VARCHAR NOT NULL" + ");"; }, datoCurso : function(){ return "INSERT INTO [curso] ([id_curso], [nombre]) VALUES (1, 'curso1');" + "INSERT INTO [curso] ([id_curso], [nombre]) VALUES (2, 'curso2');" + "INSERT INTO [curso] ([id_curso], [nombre]) VALUES (3, 'curso3');" + "INSERT INTO [curso] ([id_curso], [nombre]) VALUES (4, 'curso4');"; }, tablaEstudiante : function(){ return "CREATE TABLE estudiante (" + "id_estudiante INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE, " + "nombre VARCHAR NOT NULL, " + "curso_id INTEGER NOT NULL REFERENCES curso ( id_curso )" + ");"; }, datoEstudiante : function(){ return "INSERT INTO [estudiante] ([id_estudiante], [nombre], [curso_id]) VALUES (1,'estudiante1',1);"+ "INSERT INTO [estudiante] ([id_estudiante], [nombre], [curso_id]) VALUES (2,'estudiante2',1);"+ "INSERT INTO [estudiante] ([id_estudiante], [nombre], [curso_id]) VALUES (3,'estudiante3',1);"+ "INSERT INTO [estudiante] ([id_estudiante], [nombre], [curso_id]) VALUES (4,'estudiante4',2);"+ "INSERT INTO [estudiante] ([id_estudiante], [nombre], [curso_id]) VALUES (5,'estudiante5',3);"+ "INSERT INTO [estudiante] ([id_estudiante], [nombre], [curso_id]) VALUES (6,'estudiante6',3);"; }, crearBd : function(){ var db = [ escuela.vendors.SqlDb.tablaCurso(), escuela.vendors.SqlDb.datoCurso(), escuela.vendors.SqlDb.tablaEstudiante(), escuela.vendors.SqlDb.datoEstudiante() ]; return db.join(" "); } }}); |
Ahora dentro de la segunda función anónima de load en el controlador de la página 1, ponemos el código correspondiente a la creación de la base de datos:
1 2 | var sql = escuela.vendors.SqlDb.createDb();self.container('dbal').simple(sql); |
Luego obtenemos el listado de todos los estudiantes, para ello utilizamos un método aparte que también se llamará en la primera función anónima del método load, esto con el objetivo de poder obtener el listado tanto si es la primera vez que carga la aplicación o no.
1 | self.__listadoEstudiantes(); |
Dicho método quedaría:
1 2 3 4 5 6 7 | __listadoEstudiantes : function(){ var modelo = new escuela.model.Escuela(); var estudiantes = modelo.getEstudiantes(); var list = this.getView().listadoCursos; list.setArrayModel(estudiantes);} |
Dentro del mismo se le dice a un componente de lista de la vista cual será su contenido para ello en la vista Page1, debemos crear un componente de lista para mostrar los estudiantes, ello lo haremos en una propiedad llamada listadoCursos : {} y:
1 2 3 4 5 6 7 8 9 | //en el metodo componentsthis.listadoCursos = new criax.mobile.list.List(null,criax.mobile.list.List.BLACK); //en el metodo initializethis.addContent(new criax.mobile.basic.Header('Listado de estudiantes:'));this.listadoCursos.addListener("changeSelection",this._controller.goPage2,this._controller);this.listadoCursos.setConfigureItem(this.__createItemList);this.listadoCursos.setArrayModel([]);this.addContent(this.listadoCursos); |
El método que gestionará la información de la lista, en el título mostramos el nombre del estudiante y en el subtítulo el nombre del curso en el que se encuentra:
1 2 3 4 5 6 | __createItemList : function(item, data, row){ item.setName(data.getId().toString()); item.setTitle(data.getNombre()); item.subTitle(data.getCurso()().getNombre()); item.setShowArrow(true);} |
Fijémonos particularmente en la línea que contiene:
1 | item.subTitle(data.getCurso()().getNombre()); |
El método getCurso, devuelve una función anónima, la cual será la encargada de buscar el curso asociado a este estudiante. De esta manera la búsqueda del objeto asiciado mediante el id del mismo se hace automáticamente (tener cuidado con esto). El detalle radica en que cada vez que deseemos obtener el curso de ese estudiando se ejecutará la funcion anónima y buscará el objeto de la entidad del curso en al base de datos. Para que ello no ocurra lo mejor seria que una vez obtenido el objeto del curso del estudiante, no se procese más la función anónima, para ello vamos a la entidad Estudiante y en el método getCurso agregamos antes de devolver el atributo __curso:
1 2 3 | if((this.__curso instanceof escuela.entity.Curso) == false){ this.__curso = this.__curso();} |
De esta manera verificamos antes, que el valor del atributo no sea un objeto de la entidad escuela.entity.Curso, si no es así guardamos el resultado de la función anónima en el atributo y devolvemos el mismo. Así cada vez que pidamos el curso del estudiante, si ya se encuentra simplemente lo devolverá y sino lo buscará y almacenará para futuros usos. Entonces en la vista podemos obtener el curso del estudiante de manera normal:
1 | item.subTitle(data.getCurso().getNombre()); |
id.
La información del curso y el resto de los estudiantes
En el momento que seleccionemos un estudiante de la lista, iremos a otra página y veremos la información del curso del mismo, así como un listado del resto de los estudiantes del curso. Esto será en la vista Page2. Para ello anteriormente habíamos escrito la línea:
1 | this.listadoCursos.addListener("changeSelection",this._controller.goPage2,this._controller); |
Esta llamaría el método del controlador que iría a la segunda página, pasándole por parámetro el item del estudiante seleccionado por lo tanto este método quedaría:
1 2 3 4 5 6 7 | goPage2 : function(objEvent){ var view = this.getView(); var pos = objEvent.getData(); var listadoCursos = view.listadoCursos.getModel(); var item = listadoCursos.getItem(pos); this.assambler(Page2Controller,Page2,"index",[item]);} |
A partir de la entidad del estudiante podemos obtener la entidad del curso, pero como obtener el listado de estudiantes de un curso, bueno para ello vamos a la entidad Curso y agregamos un atributo privado llamado estudiantes, el mismo inicialmente será un arreglo vacio.
1 2 3 4 5 6 7 8 9 10 11 12 | //en las propiedades/** * columna de ... * * @name __$$ * @private * @type {Integer} */__estudiantes : {} //en el constructorthis.__estudiantes = []; |
Después agregamos un método para devolver el listado de estudiantes:
1 2 3 4 5 6 7 8 9 | getEstudiantes : function(){ if(this.__estudiantes.length == 0){ var em = criax.dic.DiContainer.getInstance().get('entityManager'); var finderEstudiante = em.getFinder(escuela.entity.Estudiante); finderEstudiante.extend('findByCurso'); this.__estudiantes = finderEstudiante.findByCurso(this.__id); } return this.__estudiantes;} |
Lo primero es verificar que el listado de estudiantes este vacío, para entonces realizar la búsqueda (cuidado con esto), obtenemos el servicio del entity-manager y con él, el finder de la entidad Estudiante. Ahora viene lo novedoso, extenderemos el finder de la entidad Estudiante, mediante la llamada del método extend, pasándole por parámetro el nombre del nuevo método que queramos que tenga.
findBy o findOneBy, y seguido el nombre del atributo por el cual buscamos.
Por último llamamos al método extendido, pasándole el valor del id del curso. O sea buscaremos en todos los estudiantes donde el id de su curso sea igual al id pasado al método extendido. Como es un método del tipo findBy, el resultado será un arreglo de entidades de tipo Estudiante.
De esta manera en el controlador de la segunda vista en el método index, obtenemos el curso y del mismo el listado de estudiantes:
1 2 3 4 5 6 7 8 | //en el metodo index del controlador de la segunda vistavar curso = item.getCurso();var estudiantes = curso.getEstudiantes();var vars = { 'curso' : curso, 'estudiantes' : estudiantes};this.setVar(vars); |
En la vista, creamos un label para el nombre del curso y una lista para los estudiantes de ese curso:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | //en las propiedades/** * label de contenido * * @name label * @public * @type {} */label : {}, /** * propiedad para ... * * @name $$ * @public * @type {} */listadoEstudiantes : {} //en el metodo componentsthis.label = new criax.mobile.basic.Header(this.vars['curso'].getNombre(),3);this.listadoEstudiantes = new criax.mobile.list.List(); //en el metodo initializethis.base(arguments);this.addListener("back",this._controller.goPage1,this._controller);this.addContent(this.label);this.listadoEstudiantes.setConfigureItem(this.__createItemList);this.listadoEstudiantes.setArrayModel(this.vars['estudiantes']);this.addContent(this.listadoEstudiantes); |
Por supuesto siempre queda el método que se encargará de procesar y mostrar cada uno de los elementos de la lista:
1 2 3 4 | __createItemList : function(item, data, row){ item.setName(data.getId().toString()); item.setTitle(data.getNombre());} |
En este caso la lista es simple con fondo blanco. Para que no salga el nombre del estudiante seleccionado que por supuesto estará en el listado de ese curso, podemos simplemente filtrar a los estudiantes en el controlador:
1 2 3 4 5 6 7 8 9 10 11 12 13 | var curso = item.getCurso();var estudiantes = curso.getEstudiantes();var estudiantesFiltrados = [];for(var i=0;i<estudiantes.length;i++){ if(item.getId() !== estudiantes[i].getId()){ estudiantesFiltrados.push(estudiantes[i]); }}var vars = { 'curso' : curso, 'estudiantes' : estudiantesFiltrados};this.setVar(vars); |
Claro está la mejor opción sería una consulta que devolviera los estudiantes ya filtrados, esto simplemente fue un ejemplo didáctico.
Todas las consultas llevadas a cabo en la aplicación se muestran en la consola, así podremos tener referencias de las mismas y cuantas veces se ejecutan.
Hasta aquí un ejemplo muy simple de como se usa la persistencia para el desarrollo de aplicaciones con CRIAX, este caso en particular para aplicaciones móviles, pero funciona exactamente igual para las aplicaciones de escritorio.















