Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

la metaweb


Ensayos no destructivos en el ecosistema Java-Web, y algún off topic para desconectar ;-)

Desarrollo de una aplicación desde cero: La capa de negocio.

Como os comenté en el Post anterior, antes de comenzar con la implementación de la capa de negocio veremos brevemente la herramienta que trae Derby para inspeccionar un esquema de datos. El acceso al esquema de datos se hace necesario por dos motivos: Por un lado en ocasiones necesitaremos comprobar si los datos persistidos son correctos, y por otro lado frecuentemente tendremos que comprobar si las tablas, índices, restricciones, y demás elementos generados por Hibernate son los esperados.

Uso del cliente de línea de comandos ij de Derby

La herramienta cliente que Derby proporciona se llama ij y se encuentra en la carpeta \bin\ del paquete de Derby, que os recuerdo podéis bajar desde este link, y que, si recordáis, cuando montamos el entorno de trabajo descomprimimos en la ruta C:\TALLER\BD\db-derby-10.11.1.1-bin\. ij es una herramienta de tipo línea de comando, y que por tanto tenemos que ejecutar desde una ventana de comando en Windows, o en Linux desde un terminal. Para inspeccionar nuestro esquema de datos en la ruta C:\BD\drones\ (ver elemento <connection-url> de la configuración del datasource en el fichero pom.xml) que será creado en breve cuando despleguemos nuestra aplicación, tendremos que llamar a ij desde esa carpeta. Para usar ij más cómodamente creamos/editamos estas variables del sistema:

Table 1. Variables de sistema para Derby

Variable

Acción

Valor

CLASSPATH

Crear/Añadir

%DERBY_HOME%\lib\derby.jar;%DERBY_HOME%\lib\derbytools.jar;

DERBY_HOME

Crear

C:\TALLER\BD\db-derby-10.11.1.1-bin

PATH

Añadir

;%DERBY_HOME%\bin

Como aún no hemos desplegado la aplicación no tenemos aún nada en la ruta C:\BD\drones\. Os indico ahora de todos modos como usar ij para ir cerrando las cuestiones relacionadas con la capa de datos. Podéis volver a este post cuando tengáis la base de datos tras un primer despliegue.

Abrimos una ventana de comando y nos vamos a la carpeta de la base de datos escribiendo cd C:\BD\drones. Para invocar el cliente ij simplemente escribimos ij, y pulsamos la tecla Enter (↵). El cliente mostrará la versión de Derby instalada y a continuación el prompt ij>. Para establecer la conexión con la base de datos escribimos el comando:

connect 'jdbc:derby:c:\BD\drones;';

Todos los comandos deben acabar con el carácter punto y coma. Después de unos instantes se vuelve a mostrar el prompt. La conexión debería estar ahora establecida. Escribimos a continuación el comando:

show tables in root;

para mostrar las tablas generadas por Hibernate en nuestro esquema de datos junto a otras tablas de sistema. El nombre de nuestro esquema de datos es el nombre del usuario con el que accedemos a los datos, es decir ROOT (ver elemento <user-name> de la configuración del datasource en el fichero pom.xml). Para ver la estructura de una tabla usamos el comando:

describe root.trabajo;

que nos muestra las propiedades de cada campo de la tabla. Podemos ver por ejemplo que el tipo de datos del campo descripción es un CLOB, o que sobre el campo numeroderegistro existe la restricción NOT_NULL. Y para ver los datos de una tabla usamos el habitual:

select * from root.drone;

Cerramos la sesión con el comando:

disconnect;

Y terminamos la sesión ij con:

exit;

Os muestro la sesión de ij completa en la siguiente figura:

post003 fig068.png

Para finalizar esta introducción a ij comentaros que Derby restringe el número de máquinas virtuales a una a menos que usemos el Derby Network Server, de modo que debéis tener el cuidado de desconectar la sesión de ij, si la teníais abierta, antes de desplegar la aplicación, y recíprocamente, parar el servidor antes de iniciar una sesión de ij.

La capa de negocio, confluencia de tres tecnologías: EJB, JPA y JTA

Llegamos a la capa de negocio, espacio donde tendremos que situar la lógica del conjunto de acciones necesarias para cubrir todos los casos de uso definidos en la fase de análisis. En general estas acciones serán transaccionales, contra depósitos de datos, sistemas externos a través de webservices, o una mezcla de ambos. En nuestra aplicación se reduce a devolver la lista de drones realizando un trabajo en una fecha y hora suministrada y al tratarse de una simple consulta podemos prescindir de transacciones y así ahorrar recursos.

Observemos nuestro ya conocido diagrama de clases de la fase de diseño:

post003 fig045.png

La clase DroneFacade representa la capa de negocio. Contiene un único método, que recibe una fecha y hora y devuelve una colección de Drones. Entre esta clase y la clase Drone existe una relación uso, que es un tipo de relación de dependencia, no estructural. Es la relación más débil del diagrama.

Usaremos la tecnología EJB, en concreto la versión 3.1, incluida en Java EE 6. Como ya vimos en el post anterior esta tecnología nos permite centrarnos en nuestro negocio al gestionar por nosotros las transacciones, que en nuestro caso de momento no usaremos, apoyándose en JTA, y realizar la inyección del contexto de persistencia a través del objeto EntityManager. JTA creará una transacción en el inicio del método de negocio y hará commit de la misma al final, si todo ha ido bien, o bien un rollback si se produce una excepción en alguna línea del método. En cuanto al objeto EntityManager es creado exclusivamente para las acciones implementadas en el método y es destruido a la salida del mismo. Para profundizar en las distintas alternativas de implementación podéis darle una lectura al apartado correspondiente a las transacciones del tutorial de Java EE 6 en esta dirección.

Antes de crear e implementar la clase comprobamos si necesitamos añadir alguna dependencia al fichero de proyecto. Para EJB sólo preciso la dependencia:

<dependency>
    <groupId>org.jboss.spec.javax.ejb</groupId>
    <artifactId>jboss-ejb-api_3.1_spec</artifactId>
    <scope>provided</scope>
</dependency>

que ya fue incluida cuando añadimos el bean EJB stateful singleton para la carga inicial de datos. Por lo tanto tenemos que tocar el pom.xml.

Vamos entonces con la clase de negocio. En primer lugar creamos la carpeta negocio/ dentro de la carpeta de proyecto src/main/java/com/lametaweb/jdrone/. A continuación pulsamos botón derecho sobre la carpeta negocio/ y elegimos la opción New > Other…​ , escribimos la cadena "ejb" en el cuadro de texto de la pantalla de nuevos elementos para filtrar el listado y seleccionamos la opción Session Bean (EJB 3.x). Se abre la pantalla de configuración del nuevo bean donde lo único que hacemos es introducir en el campo nombre el valor DroneFacade. Antes de pulsar el botón Aceptar podéis ver como por defecto se marca la opción No-interface View que hará que se cree un bean sin interfaz. Dejamos marcada la opción para simplificar la aplicación.

Una vez creado el bean sin estado y sin interfaz añadimos el método de negocio indicado en el diagrama, quedando la clase así:

package com.lametaweb.jdrone.negocio;
import java.util.Date;
import java.util.List;
import javax.ejb.LocalBean;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import com.lametaweb.jdrone.persistencia.Drone;

/**
 * Session Bean implementation class DroneFacade
 */
@Stateless
@LocalBean
public class DroneFacade {

	@PersistenceContext(unitName = "datosdrones")
    private EntityManager em;

    /**
     * Default constructor.
     */
    public DroneFacade() {
        // TODO Auto-generated constructor stub
    }

    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    public List<Drone> obtenEstadoDronesPorFecha(Date fecha){
    	String consulta = "select d " +
    		"from Drone d inner join d.trabajosAsignados t " +
    		"where t.fechaHoraInicio < :fecha " +
			"and t.fechaHoraFinalizacion > :fecha " +
    		"order by d.numeroDeSerie";

    	return em.createQuery(consulta, Drone.class).
    	setParameter("fecha", fecha).
    	getResultList();

    }
}

Al tratarse de un bean EJB podemos inyectar el entity manager directamente en el atributo em.

El método de negocio recibe un parámetro de tipo Date, que como veremos en el próximo Post formará parte del Modelo de nuestra capa de presetanción MVC (Modelo-Vista-Controlador), y devuelve una lista de objetos Drone ordenada por el número de serie, que actualizará el Modelo con la información a mostrar al usuario. Dentro del método ejecutamos la consulta apoyándonos en el entity manager. Podéis observar como la ejecución de la consulta se implementa en una sola línea usando la característica de encadenamiento de método de la API JPA, que se basa en que un método de un objeto A devuelve ese mismo objeto A, tras ejecutarse.

Al tratarse de una consulta podemos prescindir del comportamiento transaccional, añadiendo una simple anotación al método.

Una consulta puede montarse principalmente de dos maneras, una programática, a través del API Criteria, y otra textual basada en el lenguaje de consulta JPQL de JPA, o HQL de Hibernate. HQL es una extensión de JPQL. Aquí como veis he optado por la segunda alternativa. Las consultas JPQL tienen una estructura similar a las SQL y por tanto no es complicado aprender lo básico. Analicemos nuestra consulta a modo de breve introducción.

select d
from Drone d inner join d.trabajosAsignados t
where t.fechaHoraInicio < :fecha and t.fechaHoraFinalizacion > :fecha
order by d.numeroDeSerie

En la primera línea defino los atributos o entidades que quiero que la consulta devuelva. Al indicar el alias d en select d estamos diciendo que queremos que la consulta sólo devuelva objetos Drone. En Hibernate a diferencia de JPA es posible prescindir de esta parte de la consulta. Si lo hacemos así no habrá filtrado de datos y la consulta devolverá todas los objetos que intervienen en la misma, definidos en la cláusula from. Nuestra consulta devolvería entonces una lista de array de objetos List<Object[]> donde el primer elemento del array es un objeto Drone y el segundo un objeto Trabajo.

En la segunda línea como se ha comentado se determinan las entidades que intervienen en la consulta. En este caso el conjunto de datos consiste en un inner join entre Drone y Trabajo. El join, a diferencia de lo que ocurre en SQL, se hace indicando el campo de la entidad padre que da acceso a las entidades hijas relacionadas: d.trabajosAsignados.

La tercera línea establece el filtrado de los elementos a devolver, de manera similar a lo que hago en SQL con los registros. Podemos ver cómo se ha definido el parámetro nombrado :fecha para inyectar su valor dentro de la consulta.

Por último definimos un orden para el conjunto de elementos devueltos. En nuestro caso como lo que devolvemos son "disponibilidades de drones" tiene sentido que el orden lo definamos sobre el campo que identifica al drone, de modo que el usuario lo pueda localizar con facilidad.

Cuando en una aplicación tenemos un número elevado de consultas podemos agruparlas en cada uno de los beans de entidad dependiendo de la relación semántica entre la consulta y el bean. De esta manera podremos reutilizar la misma consulta en varios puntos de la aplicación y mantenerlas de forma más eficiente. Para esto usamos la anotación @NamedQuery. Hagamos este cambio en nuestra aplicación. Abrimos la clase Drone y añadimos la siguiente anotación justo debajo de la anotación @Entity:

@NamedQueries({
    @NamedQuery(name="Drone.estadoDronesPorFecha",
                query="select d " +
    			"from Drone d inner join d.trabajosAsignados t " +
    			"where t.fechaHoraInicio < :fecha " +
    			"and t.fechaHoraFinalizacion > :fecha " +
    			"order by d.numeroDeSerie"
				)
})

Como de costumbre usamos la hotkey Crtl + O para traernos las nuevas importaciones. Y añadimos a la clase de negocio el método siguiente, que es equivalente al obtenEstadoDronesPorFecha pero usando una NamedQuery:

public List<Drone> obtenEstadoDronesPorFechaNamed(Date fecha){

    return em.createNamedQuery("Drone.estadoDronesPorFecha", Drone.class).
    setParameter("fecha", fecha).
    getResultList();

}

Y con esto tendríamos lista la capa de negocio.

Un editor de consultas JPA en nuestro IDE

A continuación vamos a configurar una utilidad, de las muchas que trae el paquete JBoss Tools, que nos va a facilitar bastante las cosas cuando necesitemos incluir nuevas consultas en la capa de negocio, usando HQL/JPQL o el API Criteria. Se trata de las Hibernate Tools.

Para que en nuestra aplicación el mapeo de entidades que hace Hibernate Tools sea correcto éstas deben aparecer de forma explícita en el archivo persistence.xml de definición de la unidad de persistencia. Así que abrimos el fichero y eliminamos la línea <exclude-unlisted-classes>false</exclude-unlisted-classes> sustituyendo, con un copia pega, el contenido del fichero por el siguiente:

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0">
    <persistence-unit name="datosdrones" transaction-type="JTA">
        <jta-data-source>java:jboss/datasources/DerbyDS</jta-data-source>
        <class>com.lametaweb.jdrone.persistencia.Drone</class>
        <class>com.lametaweb.jdrone.persistencia.PuntoRuta</class>
        <class>com.lametaweb.jdrone.persistencia.Trabajo</class>
        <properties>
            <property name="hibernate.dialect" value="org.hibernate.dialect.DerbyDialect" />
            <property name="hibernate.hbm2ddl.auto" value="create" />
        </properties>
    </persistence-unit>
</persistence>

Lo primero que haremos será crear una conexión a Derby, que será la que use Hibernate Tools para alcanzar la base de datos. Cambiamos a la perspectiva Hibernate seleccionando la opción de menú Window > Open Perspective > Other…​ y elegimos Hibernate:

post003 fig071.png

A continuación abrimos la vista Data Source Explorer en la opción Window > Show View > Other…​ filtramos por la cadena "data" y seleccionamos Data Source Explorer:

post003 fig072.png

Nos vamos a esta vista, pulsamos botón derecho sobre la carpeta Database Connections y seleccionamos la opción New…​. Elegimos el tipo Derby y en el campo Name escribimos Pruebas JPQL. Pulsamos el botón Next para ir a la pantalla de propiedades. Configuramos el driver para el datasource pulsando sobre el icono post003-fig073.png. Esto nos lleva a la pantalla New Driver Definition. En la solapa Name/Type seleccionamos Derby Embedded JDBC Driver con la versión 10.2. En la solapa JAR List pulso el botón Add JAR/Zip, localizo el fichero derby.jar en el disco duro y lo selecciono. Recuerda que este fichero debe estar en una ruta similar a C:\TALLER\BD\db-derby-10.11.1.1-bin\. En la solapa Properties rellenáis estos campos:

Property

Value

Connection URL

jdbc:derby:c:\BD\drones;create=true

Database Name

drones

Password

root

UserID

root

Y pulsamos OK. En el apartado Properties en la solapa General actualizamos los siguientes campos:

Property

Value

Database location

c:\BD\drones

User name

root

Password

root

Save password

marcado

Desmarcamos las dos opciones en la parte inferior de la ventana y pulsamos el botón Test Connection para comprobar que llegamos a la base de datos. Recordad que de momento no tenéis creada la base de datos y obtendréis un mensaje de error. Antes de probar la conexión además tenemos que asegurarnos de que no exista otra máquina virtual accediendo a la base de datos, en nuestro caso bien porque el servidor esté arrancado o bien porque hayamos dejado una conexión abierta con el cliente ij. Para terminar pulsamos Finish.

La conexión que acabamos de crear para las Hibernate Tools podemos sin embargo usarla simplemente para inspeccionar la base de datos y los datos al igual que hacíamos con la utilidad ij, pero ahora más cómodamente desde el IDE. Sobre la nueva conexión pulsamos botón derecho y seleccionamos Connect. Navegando por la jerarquía accedemos a los distintos elementos:

post003 fig074.png

Y para visualizar los datos pulsamos botón derecho y opción Data > Edit sobre cualquiera de las tablas. Los datos se presentan en la solapa SQL Results.

Bien, continuamos. Volvemos a pulsar botón derecho sobre el icono de la conexión Pruebas JPQL y seleccionamos la opción Disconnect. En la ventana de la solapa Hibernate Configurations, en el lado izquierdo de la pantalla, pulsamos botón derecho y seleccionamos la opción Add Configuration…​:

post003 fig076.png

Lo que vamos a hacer es crear una configuración nueva dentro de las Hibernate Tools. Escribimos jdrone en el campo Name y los siguientes valores:

Solapa Main

Campo

Valor

Type

JPA (jdk 1.5+)

Hibernate Version

4.0

Project

pulsar Browser y seleccionar jdrone

Database connection

Pruebas JPQL

Persistence unit

pulsar Browser y seleccionar datosdrones

Solapa Classpath

Campo

Valor

Classpath

Si está vacío seleccionar User Entries, pulsar el botón Add Projects…​ y seleccionar nuestro proyecto jdrone

La nueva configuración aparecerá en la solapa Hibernate Configurations. Para abrir un editor de consultas pulsamos botón derecho sobre la nueva configuración jdrone y seleccionamos la opción HQL Editor:

post003 fig077.png

Se abre una nueva ventana con el nombre jdrone donde podremos escribir cualquier consulta y visualizar el resultado. Escribamos la consulta de nuestro método de negocio:

select d
from Drone d inner join d.trabajosAsignados t
where t.fechaHoraInicio < :fecha and t.fechaHoraFinalizacion > :fecha
order by d.numeroDeSerie

Para asignar un valor al parámetro fecha nos vamos a la vista Query Parameters a la derecha y hacemos click sobre el icono post003-fig081.png. Automáticamente se asigna el nombre fecha y el tipo string. En el campo Value introducimos una información de fecha y hora análoga a ésta:

2015-06-16 20:36:49

y que se corresponda aproximadamente con la hora en que la aplicación fue desplegada para que la consulta saque resultados. Pulsamos el icono en forma de flecha en el extremo izquierdo de la barra de herramientas post003-fig079.png para ejecutar la consulta. Se muestra una ventana para confirmar la apertura de una session factory, pulsamos Yes y la consulta JPQL se ejecuta y muestra el único registro de nuestra carga inicial de datos.

En la siguiente figura se muestra la perspectiva Hibernate. Para visualizar los atributos de los beans resultado de la consulta basta con seleccionar alguno de los beans. Los datos son mostrados en la parte inferior izquierda del IDE en la solapa Properties en forma de lista vertical de parejas Propiedad/Valor. En esta lista podremos además navegar hacia los objetos relacionados.

post003 fig082.png

En la figura se puede observar como hemos navegado desde el objeto Drone hacia el objeto Trabajo relacionado a través del atributo trabajosAsignados. Es interesante señalar que, a pesar de que al ejecutar la consulta el objeto Trabajo relacionado no es leído desde la base de datos, ya que las relaciones uno a muchos en JPA son anotadas por defecto como fetchType.EAGER, sí que accederemos al objeto Trabajo al solicitarlo pulsando el elemento de la lista correspondiente a la relación.

Respecto del editor de consultas HQL recordad que como la base de datos Derby sólo puede ser accedida por una única máquina virtual a la vez es importante parar el servidor antes de usarlo y cerrar la Configuración Hibernate en la solapa Hibernate Configurations cuando hayamos terminado de usar el editor tal como muestra la figura:

post003 fig083.png
Durante la elaboración del contenido de este post sin embargo eventualmente al cerrar la configuración Hibernate la base de datos no se desbloqueaba de manera automática. En este caso la solución pasa por cerrar y abrir Eclipse.

Bien, pues hasta aquí llegamos con la capa de negocio. En el próximo post completaremos finalmente nuestra aplicación con la implementación de la capa de presentación, basada en el framework MVC JSF. Hasta entonces!


About the author

La metaweb


Discussions

comments powered by Disqus