Payara Micro + Oracle JET = Solución Fullstack - Parte 2: CRUD completo a la base de datos SAKILA

Por Diego Silva
Publicado en Marzo 2019

Revisado por Juan Pablo Guizado




Esta es la segunda parte del artículo Payara Micro + Oracle JET = Solución Fullstack. Parte 1: Listado paginado simple. En esta parte veremos como armar un CRUD completo a la base de datos SAKILA.



Introducción - Sakila Project


Desarrollaremos el proyecto Sakila Project desde dos frentes: Servicios REST, usando Payara Micro; y Aplicación, usando Oracle JET.

La base de datos que usaremos es llamada “sakila” utilizada en MySQL y disponible en la siguiente dirección.

Utilizaremos conceptos y acciones definidas en el anterior tutorial.

Además, solo se definirá algún aspectos básicos de algunas entidades de ejemplos y se asumirá que se replicarán en las demás entidades.






Base de datos


La base de datos SAKILA está definida en la siguiente página: https://dev.mysql.com/doc/sakila/en/

La instalación está definida en los siguientes pasos: https://dev.mysql.com/doc/sakila/en/sakila-installation.html

Y se puede obtener la base de datos desde la siguiente dirección: https://dev.mysql.com/doc/index-other.html




Instalación de la base de datos SAKILA

Primero se debería descargar el archivo comprimido de la web.




Una vez descargado, se deberá descomprimir el contenido.




El contenido obtenido cuenta con la creación del esquema de la base de datos (sakila-schema.sql), la data en sí (sakila-data.sql) y el modelo que podrá ser abierto en MySQL Workbench.

Ahora, debemos crear la base de datos. Lo haremos desde la línea de comandos.




Introducir la contraseña cuando se le pida.

Y luego procedemos a insertar la data.




Podemos entrar desde la consola a MySQL y ver que se hayan creado las tablas correspondientes.






Estableciendo usuario para la base datos

Como buena práctica es necesario establecer siempre un usuario que acceda a esa base de datos que no sea el usuario ROOT. Por lo que ejecutaremos los siguientes comandos.




Para probar el acceso, debemos entrar desde una nueva ventana de consola.








Creando proyecto Payara Micro


Usando NetBeans, crearemos el Proyecto con Maven > Payara Micro Application


Clic en Next.




El nombre de proyecto es sakila-service


Clic en Next.




Seleccionar la versión última actual: 5.184

Clic en Finish.






Creando conexión a la base de datos

Una vez creado el proyecto, debemos definir la conexión a la base de datos.

Como estamos usando MySQL, debemos agregar la siguiente dependencia.



 La conexión a la base de datos la definiremos en el archivo web.xml. Para ello debemos primero crearlo. File > New...


Clic en “Next” luego en “Finish”




Dentro del archivo web.xml definiremos el datasource, usando las credenciales creadas en apartado anterior, y también con las propiedades necesarias para crear la conexión a MySQL.






Archivo de Unidad de Persistencia persistence.xml

Ya que usaremos JPA, debemos configurar nuestra unidad de persistencia. Se llamará “sakilaPU” y apuntará al datasource java:app/microprofile/sakila que hemos definido en web.xml

Este archivo se ubicará en la siguiente ubicación:



Y este será su contenido:

Bastante simple.






Entidades JPA

Ahora, vamos a crear las entidades para nuestro proyecto. Si revisamos el modelo, hay varias tablas que mantienen relaciones entre ellas.

Una muestra es la siguiente:



Debemos tener mucho cuidado al crear las entidades, y sobre todo al definir las asociaciones entre ellas.

No detallaremos todas las entidades, solo las más básicas y ciertas configuraciones que serán comunes entre todas. Todas las clases creadas estarán definidas en el proyecto que compartiré al final del documento.




Entidad Country

Esta entidad cuenta con solo tres campos: la PK (que es autogenerada y se llama long countrId), el nombre (String country) y la fecha de actualización (LocalDateTime lastUpdate).



Las fechas las manejaremos como tipo java.time.LocalDateTime en lugar de java.util.Date. Esto se debe a que será más fácil de convertir y manipular en formato JSON.

Pero como este tipo no es compatible con JPA, debemos crear una clase que convertirá automáticamente el tipo a java.sql.Date para ser guardado en la base de datos, y visceversa.

Este será el convertidor para todos los atributos de tipo java.time.LocalDateTime




Lo mismo necesitaremos para el tipo java.time.LocalDate



Estas clases tienen la anotación @javax.persistence.Converter(autoApply = true) que permitirá aplicar a todos los tipos que se encuentren dentro del ámbito de JPA.

Para mayor información de cómo se implementa la interfaz revisar su documentación en la siguiente dirección https://javaee.github.io/javaee-spec/javadocs/javax/persistence/AttributeConverter.html





Entidad City

Esta entidad tiene la particularidad que está asociada a Country. City HAS-A Country.

Por tanto, la asociación entre ambas entidades debe anotarse como @ManyToOne indicando el campo con clave foránea.




Entidad Actor con asociacion Muchos a Muchos con Film

Las tablas Actor y Film tienen una tabla intermedia llamada film_actor que resuelve el problema de Muchos-a-Muchos.




En JPA tenemos dos opciones para implementar:

  1. Crear la entidad asociada a la tabla film_actor
  2. Crear la asociación Muchos-a-Muchos

La primera opción no es posible implementarlo ya que toda entidad JPA debe tener una clave primaria y esta tabla no tiene. Y no sería buena opción agregar un campo solo para resolver el problema.

Ya que la función de esta tabla es implementar el Muchos-a-muchos entonces usemos la anotación  @javax.persistence.ManyToMany.

En la entidad Actor consideremos lo siguiente:



La entidad Film deberá tener la misma asociación, pero allí debemos definir cómo encaja la tabla film_actor para que resuelva la solución.




Campo compartido en dos asociaciones. Entidad Inventory.

Si seguimos revisando las relaciones, encontraremos la tabla Inventory que tiene se relaciona con las tablas film_text y film, pero ambas relaciones utilizan el campo inventory.film_id.




Si aplicamos la asociación en entidades de la siguiente manera:


Al ejecutar el proyecto, no se lograría desplegar, y lanzaría la siguiente excepción:

Exception Description: Multiple writable mappings exist for the field [inventory.film_id].  
Only one may be defined as writable, all others must be specified read-only.
Mapping: org.eclipse.persistence.mappings.ManyToOneMapping[filmText]
Descriptor: RelationalDescriptor(com.apuntesdejava.sakila.domain.Inventory -->  
[DatabaseTable(inventory)])

Y es porque se comparte el mismo campo en ambas asociaciones. Por tanto, solo uno de ellos debe ser modificable y el otro no. Podemos solucionar eso de la siguiente manera:



Finalmente, las entidades que lograremos mapear son las siguientes:






Repositorios de entidades

Hasta ahora sólo hemos creado las entidades y cómo crear las asociaciones entre ellas, basadas en las relaciones de las tablas de SAKILA. Ahora necesitamos crear las clases que manejan esas entidades. Son llamadas “Repositorios” porque manejarán objetos que están en la base de datos.



JPAProducer

Esta clase no es un repositorio de entidades, pero sí es un objeto Singleton que tendrá la conexión a EntityManager, de tal manera que las clases Repositorios solo lo reutilicen, y no nos llenemos de varios objetos que hacen lo mismo.

Esta clase tendrá el siguiente contenido:


Al ejecutarse se instanciará una sola vez (porque tiene alcance de toda la aplicación por la anotación @javax.enterprise.context.ApplicationScoped) y se reutilizará el atributo EntityManager usando CDI ya que tiene la anotación @javax.enterprise.inject.Produces.





Repositorio ActorRepository

Esta clase se encargará de manejar los objetos de la entidad Actor. Será de alcance global para toda la aplicación, de tal manera que solo habrá una única instancia del repositorio. Reutilizará por inyección CDI el objeto EntityManager.




Además, tendrá métodos para manejar los objetos. Por ejemplo, el listado de todos los objetos, pero delimitado por un rango de inicio y cantidad de objetos, a fin de poder usarlo en un listado paginado.




También podemos obtener un objeto en base al ID de la clave primaria:




Es necesario, además, poder registrar un nuevo objeto de esta entidad. Debido a que es una operación transaccional, debemos marcar el método con la anotación @javax.transaction.Transactional.




También, en caso que deseamos actualizar el contenido de un registro, necesitamos hacerlo utilizando el ID del registro que se desea actualizar.






Servicios REST

Ya estamos por terminar en crear los servicios. Para poder preparar nuestro proyecto para servicios REST necesitamos crear una clase que indicará el URI base de nuestros recursos REST. Esta clase debe extender a la clase javax.ws.rs.ApplicationPath y deberá tener la anotación @javax.ws.rs.core.Application indicando cual es la URI base.

Esta clase se llamará ApplicationConfiguration y tendrá el siguiente contenido:

A partir de ahora, todos los endpoints de los servicios REST que hagamos estarán dentro del URI /api.




Endpoint ActorREST

Nuestra clase Endpoint para manejar los objetos de Actor tendrá la siguiente declaración.

  • Para acceder será através de la ruta /api/actor. (Línea 25)
  • Recibirá objetos JSON y devolverá objetos JSON (líneas 26 y 27 respectivamente)
  • También es de alcance de toda la aplicación (línea 28)
  • Tendrá el objeto por CDI el repositorio de actores ActorRepository (línea 35) El servidor se encargará de instanciarlo, por tanto solo debo utilizarlo.

Necesitamos obtener el listado de objetos delimitado por un rango de valores. Por omisión se tomarán otros valores. Este listado se obtendrá por método GET y los parámetros serán por Query.



Luce bastante simple. Así que lo compilamos y tratamos de ejecutar.




Y el mensaje en el log del IDE dice:

Esto se debe a que Actor y Film tienen asociación Muchos-a-muchos. Ambas entidades se tienen mutuamente (Actor tiene un listado de Film y Film tiene un listado de Actor).

Por tanto, el JPA preparar la respuesta de tal manera que los objetos de uno están contenidos en el otro. Semánticamente a nivel de objetos es válido: son los mismos objetos, tienen coherencia… pero cuando se convierte a JSON esta referencia cruzada se hace infinita ya que habrá por cada objeto de Actor un listado de Film, y cada objeto de Film un listado de Actor, y nuevamente, por cada Actor un listado de Film.. y nunca termina. Por eso lanza el error StackOverflowError.




Resolviendo la referencia cruzada en la respuesta.

¿Cómo se soluciona eso?

  1. Podríamos anular la asociación de Film y no de Actor, de tal manera que no colapse el JSON. Pero esta modificación alteraría la semántica de las asociación de Muchos-a-Muchos
  2. Ya que el problema no es en la entidad, sino al momento de generar el JSON, lo que debemos hacer es decirle al JSON: esos campos no me lo conviertas en JSON, ignóralos.

Por tanto, debemos marcar los campos que no queremos que se exporten a JSON con la anotación @javax.json.bind.annotation.JsonbTransient.


Modifiquemos la entidad Actor agregando esa anotación en la propiedad films.



De la misma manera, en la entidad Film ignoramos el listado de actores. Aunque para este caso no es necesario, pero sí nos hace que el listado sea más ligero.



Ahora bien, una vez resuelto esto, ejecutamos nuevamente:




Como tiene paginación, podemos modificar los parámetros a nuestro gusto.





Obtener un Actor por su ID.

También podemos obtener la información de un solo actor, indicando su ID por URL. Este es el método.

  • Es de tipo GET (línea 45)
  • El ID estará indicado en el mismo URL (línea 46 y línea 48)
  • Este ID se buscará en el repositorio (línea 49)
  • Y el resultado es devuelto a la petición (línea 50)

Por tanto, al ser invocado por el ID - por ejemplo - 123, este será el resultado.





Listado de Films de un Actor

Bien, ya no podremos ver los films por Actor, pero si queremos verlo, debemos indicar de qué actor queremos ver sus films.

Podemos hacer un URL que, si se indica el ID del Actor, y después una ruta llamada “films” podemos hacer que devuelva ese listado en el response.



Probamos la invocación.





Insertar un nuevo registro en la base de datos.

Este método tiene que ser de tipo POST, por tanto los datos de entrada serán en formato JSON. En este caso se recibirá un objeto JSON con dos campos: firstName y lastName.

Por tanto, crearemos una clase ActorRequest.java que tendrá esos dos atributos.




Ahora crearemos el método en el servicio que recibirá via POST el JSON y que guardará el objeto en el repositorio:



Este método no tiene la anotación @Path ¿cómo sabe que este es para registrar un nuevo objeto? Pues lo hará porque solo es llamado por el método POST de la petición.

Si queremos hacer una actualización del un objeto debemos, también, enviarle un JSON con los datos a modificar, y el ID del objeto que se desea actualizar. El ID lo damos vía parámetro del URL, así como lo usamos para obtener información de un actor por ID.



Nuevamente ¿cómo sabía que esta petición es para actualizar y no para obtener información a pesar que tiene el mismo @Path? Pues para actualizar usaremos el método de petición PUT.

Por tanto, si queremos:

  • Leer datos, usamos petición GET
  • Insertar datos, usamos petición POST
  • Actualizar datos, usamos petición PUT
  • Eliminar datos, usamos petición DELETE

Ejecutamos la aplicación, y veremos que al desplegarse se mostrará las URI disponibles del servicio.




Probando peticiones con httpie

Con httpie podemos realizar las peticiones desde una línea de comandos. Para enviar objetos JSON bastará con un parámetro --json y así convertirá cada parámetro como atributos del objeto json.
Insertando registros




Leyendo ese objeto creado




Actualizando ese objeto:





Probando peticiones con postman

Una herramienta muy conocida es Postman. Las peticiones son más visuales. Probemos
Insertando registros

 




Actualizando registro









Creando cliente con Oracle JET


Para este proyecto lo haremos usando una plantilla de Oracle JET. Usaremos la plantilla “Nav Bar Starter”, y luce así: https://www.oracle.com/webfolder/technetwork/jet/public_samples/JET-Template-Web-NavBar/public_html/index.html





Creando el proyecto sakila-app

Desde la línea de comandos, nos ubicamos en la carpeta donde deseamos crear el proyecto y escribimos:

ojet create sakila-app  --template=navbar







Editando el proyecto con NetBeans

Personalmente yo uso NetBeans para muchas cosas, y con este IDE también puedo editar el contenido del proyecto creado en Oracle JET. Entramos a File > Open Project y seleccionamos la carpeta del proyecto recientemente creado.




Clic en “Open Project”



Pero, para que se vea más ordenado, separaremos las carpetas del proyecto y del código fuente. Para ellos hagamos clic derecho en el ícono del proyecto y seleccionamos “Propiedades”



Y escribimos src en la caja de “Source Folder”




Clic en “OK” y veremos mejor organizado los archivos del proyecto.






Primera ejecución del Proyecto

Desde el IDE podemos entrar a la opción Tools > Open in Terminal para abrir la ventana del terminal; o podemos entrar desde una ventana de comandos del sistema operativo, y dentro de la carpeta del proyecto ejecutamos la siguiente línea

ojet serve



Finalmente, se abre el navegador predeterminado y se mostrará la aplicación web.




Al hacer clic en cada una de las opciones que se encuentra en la parte superior, se verá cómo cambia el URL y el contenido.


Cada uno representa un modelo visual establecido en el proyecto.  Tiene su HTML y su JavaScript Model View correspondiente:


Para listar nuestros actores, usaremos la página correspondiente a “Customers”






Listando los Actores

Una buena manera de poder usar correctamente los componentes de Oracle JET es mirando su página “Cookbook” que es una especie de Showcase de todos los componentes, con instrucciones para caso. Esta página es https://www.oracle.com/webfolder/technetwork/jet/jetCookbook.html

Para el listado de actores nos basaremos en el componente ListView. La lógica será la siguiente:

  1. Al cargar la página, se consultará todos los registros usando el endpoint del listado de actores.
  2. El resultado de esa petición es un arreglo de objetos Javascript.
  3. Estos objetos se guardarán en un Knockout Observable Array.
  4. El componente ListView actualizará el contenido a mostrar ya que estará asociado al Observable Array.

Así que trabajaremos en dos frentes: el Modelo de la Vista, y la Vista en sí.




Preparando el listado

Abrimos el archivo js/viewModels/customers.js Veremos que la clase del modelo es llamada  CustomerViewModel, tiene preconfigurado tres métodos, y al final de todo el archivo se instancia una sola vez. Además, en la parte superior está la declaración define que funciona como los import en un código Java.

Más o menos luce así:



Vamos a crear un Observable Array, allí es donde se guardarán los valores tomados del servicio. Esto lo pondremos después de la declaración del objeto self.



Para manipular el contenido del componente ListView a través de un arreglo, es necesario el componente ojarraydataprovider. Debemos declararlo en el define, asociarlo a un objeto de parámetro:



Y luego, definir nuestro proveedor de data para el ListView. Este componente se asociará al Observable Array, y se identificará a cada elemento del arreglo a través de un campo clave:



Luego, debemos crear un método que nos permitirá invocar   los datos del servicio de actores y ponerlo en el Observable Array.



Y será invocado cuando se termine de crear la página. Yo lo pondré en el método de completada a transición de la vista.



Por el lado del js/views/customers.js solo deberíamos agregar el siguiente contenido



Con solo guardar el proyecto se actualizará el contenido y se recarga la página. Si no, habría que volverlo a ejecutar con ojet serve.

El resultado es el siguiente:






Formulario para entrada de datos

Ahora nos toca hacer un formulario para poder agregar nuevos registros, y para editar el registro seleccionado.

Necesitamos:

  1. Un formulario HTML. Usaremos componentes Oracle JET
  2. Un objeto Javascript que esté asociado (binding) con el formulario HTML
  3. El formulario se mantendrá oculto mientras se muestre el listado. Cuando se abra el formulario, se ocultará el listado. Al inicio el formulario aparecerá oculto.
  4. Un botón llamado “Nuevo” para activar el formulario.

Para que funcione el punto 3 debemos envolver cada parte en un div, el del formulario lo pondremos oculto por css.




El formulario es bastante simple: dos input text, y dos botones. Está envuelto en un tag llamado oj-form-layout para que tenga una mejor formación. Los input-text está asociados a un objeto JavaScript que ya lo vamos a ver en un momento.

También hay que notar que está la etiqueta on-oj-action en cada botón. Estos apuntan (binding) a un método que tendremos en el modelo de la vista.

También en el ListView tenemos el método on-current-item-changed apuntando a otro método llamado loadActor. Así que tendremos los siguientes métodos:

  • newActor
  • loadActor
  • formSave
  • formCancel

Este es el contenido que tendrán los métodos.


Método slide() para intercambiar la visión de ambas ventanas:



newActor:



loadActor:



formSave. Guarda los datos del formulario.



Aunque este método se puede reducir a uno más simple, considerando lo siguiente:

  • Si el ID del actor es cero (0) es porque es nuevo, por tanto debe enviar POST y el URL sin el ID del Actor
  • Si el ID del actor no es cero es porque es actualización, por tanto debe enviar PUT y en el URL debe enviarse el ID del actor.

Entonces, quedaría así:



Método formCancel, simplemente regresa a como estaba.






Ejecutando el código

Podemos guardar el proyecto, y si se estaba ejecutando la aplicación entonces el Oracle JET se recargó, o sino volvemos a ejecutar ojet serve desde la línea de comandos.



Listado simple




Boton Nuevo

- Formulario



Guardando, y revisión de la petición XHR

 





EdiciÓn

Cuando se hace clic en un elemento del listado



Actualizando el contenido






Búsqueda por nombre

Como cereza de helado para este artículo, implementaremos una búsqueda por nombre.

Para comenzar, debemos modificar el endpoint para buscar por un nombre, o parte de él. Por tanto, a nuestro método de listado findAll() agregaremos un parámetro llamado hint.



Además, agregaremos un método (overload a findRange) en nuestra clase ActorRepository y le pasamos el parámetro hint.

Este método hará un query buscando a los actores que tengan el nombre o apellido con parte de ese hint., solo si ese hint. no está en blanco.



Y lo probamos así:




Ahora bien, para poderlo usar en la aplicación debemos hacer algunos cambios: agregar la caja de texto, y que se actualice el contenido a medida que se va escribiendo:

En customers.html justo antes del botón “Nuevo” podemos poner este código. La parte importante es el oj-input-text. El div que lo envuelve es meramente decorativo.



Como podemos ver, existe el evento on-raw-value-changed que apunta a un método que aún no hemos creado: handleRawValueChanged. En este método haremos que llame al servicio que hemos modificado.

Pero antes, recordemos que ese URI está siendo reutilizado ya muchas veces, por lo que crearemos una constante que tenga el prefijo para nuestras peticiones:



Ahora sí, crearemos nuestro método:



Y listo, ejecutamos el código y podremos probar:






Conclusión


Como hemos podido ver, se puede crear una aplicación bastante completa con pocos pasos, utilizando los recursos proporcionados por el Oracle JET como Frontend; además Payara Micro, utilizando el estándar de MicroProfile, permite crear servicios con pocas clases, poca configuración, y en una sintaxis ligera que ya viene heredado de Java EE.




Código fuente.


Tanto la aplicación como el servicio se encuentran disponibles en esta dirección:

https://bitbucket.org/apuntesdejava/sakila-example

Se puede hacer clone vía GIT.




Diego Silva. En Noviembre de 2013 obtuvo el Oracle Certified Associate Java SE 7 Programmer.
Ha Trabajado para el Estado peruano, desarrollando y llevando a producción sistemas de información en vía web. Actualmente trabaja en una empresa privada desarrollando aplicaciones Java Web con Oracle ADF y con la plataforma Liferay.

Este artículo ha sido revisado por el equipo de productos Oracle y se encuentra en cumplimiento de las normas y prácticas para el uso de los productos Oracle.