Ejemplo 4: Reporte agrupado usando JPA

Ups, si que me tardé para esta nueva entrega, y es que estuve bastante ocupado, a parte que no podía presentar un ejemplo sencillo como cuarto capítulo, sino que tenía que esmerarme en entregar algo más interesante.
Como dice el título, este ejemplo es un reporte agrupado, es decir que el diseño del reporte contiene grupos, además de la característica que implementé unas entidades JPA para acceder a la información. Pienso que lo más complejo (desde el punto de vista de las configuraciones) fue hacer que el contenedor JPA trabaje en una aplicación de escritorio (por lo general lo hace dentro un servidor de aplicaciones), utilicé Hibernate como proveedor de JPA.

Vamos con el ejemplo.

La estructura de la aplicación de ejemplo, es la misma que vamos llevando en los anteriores ejemplos, salvo algunas cosas que han cambiado.

Inicialmente, tuve que añadir librerías para poder ejecutar el contenedor JPA, fue algo complicado, puesto que no encontré una documentación muy fiable acerca de qué librerías hay que añadir exactamente, al final pude dar con un conjunto interesante de librerías (aunque no estoy cien por ciento seguro que todas sean requeridas), revisen el ejemplo y vayan probando ustedes cuales son las requeridas (y me avisan después jeje).

Luego, tuve que añadir el archivo persistence.xml para definir las configuraciones de JPA, y a implementar las Entidades, que en este caso solo son dos: Car y Person, que representan a una persona que es dueña de uno o varios automóviles.

JPA Entities class diagram

Figura 1: Diagrama de clases del las entidades JPA

Una pequeña aclaración, luego de crear la base de datos con el script que viene en el proyecto (sample4_mysql.sql), deben configurar el acceso a dicha base de datos, en el archivo persistence.xml, es decir deben cambiar las siguientes líneas de acuerdo a su configuración local de mysql:

<property name="hibernate.connection.driver_class"
value="org.gjt.mm.mysql.Driver"/>
<property name="hibernate.connection.username"
value="burro"/>
<property name="hibernate.connection.password"
value="burro"/>
<property name="hibernate.connection.url"
value="jdbc:mysql://localhost:3306/burrotest"/>

Nota: Como el tutorial es de JasperReports, no ahondaré mucho en los detalles de JPA, pueden revisar el ejemplo, y hacerme sus preguntas en los comentarios.

En el capítulo anterior, usamos una conexión JDBC para acceder a los datos, lo logramos proveyendo una instancia de java.sql.Connection al motor de JasperReports al momento de llenar el reporte. Ahora, como vamos a ejecutar una consulta JPQL (Java Persistence Query Language), necesitamos proveer al motor de una instancia de javax.persistence.EntityManager para ejecutar la consulta. Para esto implementé una clase utilitaria EntityManagerUtil, podemos ver como esta clase instancia el EntityManager en el siguiente listado de código:

String persistenceUnitName=properties.getProperty("persistenceUnitName");
factory = Persistence.createEntityManagerFactory(persistenceUnitName);
manager = factory.createEntityManager();

Esta instancia del EntityManager la pasamos al motor de JasperReports al momento de llenar el reporte (clase ReportExecutor):

.....
EntityManagerUtil entityManagerUtil=new EntityManagerUtil();
EntityManager manager=entityManagerUtil.getManager();
//Set entityManager
fillParameters.put(JRJpaQueryExecuterFactory.PARAMETER_JPA_ENTITY_MANAGER, manager);
try {
jasperPrint =JasperFillManager.fillReport(
jasperReport,
fillParameters);
} catch (JRException e) {
....
}

Hasta ahí, las configuraciones y el trabajo sucio, ahora vamos con el reporte en si.

Empecemos con la consulta:

JRDesignQuery query=new JRDesignQuery();
query.setLanguage("ejbql");
query.setText(
"SELECT car.model, car.color, person.id, person.name, person.age " +
"FROM Car car JOIN car.owner person " +
"ORDER BY person.id, car.model"
);

Como podemos notar, el lenguaje de la consulta ya no es sql, sino que es una consulta “ejbql”, y lo que la consulta está haciendo es seleccionar el: modelo, color, id_dueño, nombre_dueño y edad_dueño de un auto. Dado que una persona puede tener muchos automóviles, las últimas tres columnas se repetirán cuando encontremos una persona con mas de uno. Ordenamos por el id de la persona para poder agrupar nuestro reporte por dueño del coche.

Para entender mejor, aquí les muestro la consulta en sql que se genera:

select car0_.model as col_0_0_,
car0_.color as col_1_0_,
person1_.personid as col_2_0_,
person1_.name as col_3_0_,
person1_.age as col_4_0_
from car car0_ inner join person person1_ on car0_.ownerid=person1_.personid
order by person1_.personid, car0_.model;

Para añadir los fields al reporte, no podemos usar los alias de las columnas como en el caso de una consulta sql, sino que debemos usar el número de columna, concatenado al prefijo “COLUMN_” (no me pregunten el porqué de esto).


List fieldsList=new ArrayList();
fieldsList.add(
createReportField("carModel", String.class.getName(), "COLUMN_1")
);
fieldsList.add(
createReportField("carColor", String.class.getName(), "COLUMN_2")
);
.....

Ahora pasemos a los grupos, tenemos que añadir un grupo al diseño del reporte. Para definir un grupo, tenemos que definir: el nombre, las bandas del encabezado, las bandas del pie, y lo más importante, definir una “expresión de grupo”; es decir necesitamos una expresión que podamos evaluar para poder discriminar las filas del resultado de la consulta y acomodarlas en sus respectivos grupos.

JRDesignGroup group=new JRDesignGroup();
//The group name
group.setName("CarOwnerGroup");
//The group expression
JRDesignExpression groupExpression=new JRDesignExpression();
groupExpression.setText("$F{personId}");
groupExpression.setValueClass(Integer.class);
group.setExpression(groupExpression);

//Group header
JRDesignBand groupHeader= createGroupHeaderBand(titleStyle, headerStyle);
((JRDesignSection)group.getGroupHeaderSection()).addBand(groupHeader);
//Group footer
JRDesignBand groupFooter=createGroupFooterBand(footerStyle);
((JRDesignSection)group.getGroupFooterSection()).addBand(groupFooter);

Como podemos ver, la expresión de grupo es: $F{personId} lo que significa que al momento de llenar el reporte se irá iterando el resultado de la consulta, cuando se encuentre el primer valor de $F{personId}, se creará un grupo para este valor; si en la siguiente fila el valor de $F{personId} es el mismo, entonces esa fila entrará en el mismo grupo, y así sucesivamente. Cuando se llegue a una fila con un valor de$F{personId} diferente al anterior, se creará un nuevo grupo, y se continuará de esta manera hasta iterar todas las filas del resultado de la consulta.

También se puede notar que se crearon dos bandas para el grupo, una banda para la cabecera, y la otra para el pie. Estas bandas no son obligatorias, pero debemos tomar en cuenta dónde queremos que aparezcan el encabezado de las columnas, y el pie del grupo. Por lo general cuando trabajamos con grupos y grupos anidados, debemos poner el encabezado de las columnas, no en la banda encabezado del reporte, sino en la banda encabezado del grupo superior (¿esto ya lo sabemos verdad?).

Continuando, veamos como podemos añadir totales al reporte. Cuando añadimos un grupo al reporte, podemos usar las variables que se crean automáticamente (por ejemplo groupName_COUNT) para mostrar algunos totales en el pie del grupo (también podemos definir nuestras propias variables).

JRDesignTextField groupFooterLabel=
createTextField(350, 6, 100, 12,
"groupFooterLabel",
String.class.getName(),
"\"Total cars: \"",
HorizontalAlignEnum.LEFT,
style);

JRDesignTextField groupFooterCounter=
createTextField(450, 6, 40, 12,
"groupFooterCounter",
Integer.class.getName(),
"$V{CarOwnerGroup_COUNT}",
HorizontalAlignEnum.RIGHT,
style);

En el anterior listado de código, estamos creando una etiqueta (“Total cars:”) acompañado de un valor que es el total de filas en un grupo ($V{CarOwnerGroup_COUNT}).

El ejemplo se termina ahí, como saben, pueden ejecutar el ejemplo usando la tarea runSample del script de Ant que viene con el ejemplo, pero, antes de terminar quiero comentar unos cambios en el API de JasperReports que usé en este cuarto ejemplo (usé jasperreports-3.7.4).

Primero, noté que ya no se están usando constantes para definir algunas propiedades del diseño del reporte, sino que se ha cambiado por enumeraciones, por ejemplo antes:

textField.setHorizontalAlignment(JRTextField.HORIZONTAL_ALIGN_CENTER);

Así se definía que la alineación del campo de texto sea “Centrada”. Ahora se usa una enumeración:

textField.setHorizontalAlignment(HorizontalAlignEnum.CENTER);

Y de esta manera se definen muchas propiedades del diseño del reporte, usando enumeraciones en lugar de las constantes de antes.

El otro cambio que noté, es que ahora se soporta múltiples bandas en cada sección de un reporte. Antes así se añadía una banda a una sección del reporte:

jasperDesign.setDetail(detailBand);

Ahora se lo hace de la siguiente manera;

JRDesignSection detailSection=
((JRDesignSection)jasperDesign.getDetailSection());
detailSection.addBand(detailBand);

Y como siempre, les muestro un pequeño diagrama de clases con las clases involucradas en el ejemplo.

Sample4 class diagram

Figura 2: Diagrama de clases para el ejemplo 4

Y este capítulo se terminó, pueden descargar el código del ejemplo de: JRTutorialSample4

P.D. Desde cierta versión de JasperReports (no se exáctamente cual) es requerido añadir un paquete con fuentes para que el exportador las encuentre. En el ejemplo añadí el que viene con el proyecto JasperReports(jasperreports-fonts-3.7.4.jar), que trae la fuente “DejaVu Sans”, y utilicé esa en los estilos.

13 comentarios el “Ejemplo 4: Reporte agrupado usando JPA

  1. Hola, te felicito por el tutorial, está muy bueno… hoy lo vi y está muy interesante… pero no sé si es mucha molestia y me puedes ayudar con el JRTutorialSample4, porque lo descargo pero no me deja descomprimir, me sale como dañado….
    De antemano muchas gracias…

  2. Disculpas a todos por el problema, acabo de subir nuevamente el ejemplo, además hice algunas correcciones, como por ejemlpo el de las fuentes (que menciono en el P.D. del ártículo). En mi mac el ejemplo funcionaba sin problemas, pero en Windows, veo que es requerido ese archivo con fuentes. Gracias a Miguel por hacerme notar el problema.

  3. Muy bueno el tutorial, pero considero que es mejor instanciar el JasperPrint enviandole un lista de objetos y realizar el diseño del reporte con la herramienta iReport.
    Pues hasta ahora no he encontrado la forma de trabajar los reportes a nivel de entidades directamente en el jrxml.
    En fin me gusto mucho el tutorial y me aclaró muchas dudas.

  4. Bueno, yo considero que JasperReports es una herramienta bastante genérica que no quiere apegarse mucho a alguna tecnología de Java en particular, y por eso la integración no es tan directa, no hay la interacción con las entidades JPA directamente en la plantilla del reporte.
    Por otro lado, lo que dices es correcto, es siempre mas sencillo diseñar con iReport, pero el objetivo de mi tutorial, era ir apuntando como lecciones todo lo que aprendí acerca del API, puesto que tuve que implementar un front end en entorno web, y me costó algo averiguar esas cosas, especialmente cuando hablamos de gráficos.
    Me alegra que te haya gustado el tutorial, aunque me falta escribir un par de ejemplos aún😉.

  5. Pingback: Jpa | TagHall

  6. Estaba buscando la forma de generar los reports dinámicamente en función de los resultados de una consulta SQL y cuando eché un vistazo al API no sabía ni por dónde empezar pero en tus tutoriales he encontrado la base que necesitaba.

    Esto me permite generar N reports con sólo una plantilla creada en iReport. Antes tenía que hacer una plantilla por cada report, y lo que es peor, una plantilla por cada agrupación que tuviera el report.

    Ahora estoy intentando generar informes usando los grupos que explicas en este tutorial y después intentaré hacer lo mismo con los crosstab. Mi idea es hacer una librería Java con la que pueda crear todo tipo de reports sin tocar para nada el iReport, pero me queda un arduo trabajo por delante.

    Gracias por estos tutoriales.

  7. Aprovecho para preguntar un tema que me lleva por la calle de la frustración más absoluta… quiero añadir una property expression de tipo “net.sf.jasperreports.text.truncate.at.char” por ejemplo a un field mediante el API pero no lo consigo. Sabes cómo puedo añadir estas propiedades?

  8. Estimado, si me puedes ayudar con el siguiente error que tengo al momento de ajecutar el reporte. No Persistence provider for EntityManager named sample4PU

  9. Buenas tardes, por favor podrias poner un ejemplo de imprimir un campo CLOB y también otro para BLOB. Te agradeceria infinitamente.

  10. Ante tanta amabilidad ¿cómo negarme?, estos días me haré un tiempito para hacer un ejemplo.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s