sábado, 18 de abril de 2009

Creación de Reportes con JasperRepots y iReports - Parte 7: Subreportes

Visita la parte 1 de este tutorial: Reportes con Conexión a Bases de Datos
Visita la parte 2 de este tutorial: Usando DataSources Personalizados
Visita la parte 3 de este tutorial: Parámetros y Variables
Visita la parte 4 de este tutorial: Reportes en aplicaciones web
Visita la parte 5 de este tutorial: Gráficas en Reportes
Visita la parte 6 de este tutorial: Grupos

Los subreportes son una característica importante de una herramienta generadora de reportes. Permiten crear reportes más complejos y simplificar el trabajo de diseño.

Los subreportes son muy útiles cuando se crea un reporte maestro de detalles o cuando la estructura de un solo reporte no es suficiente para describir la complejidad del documento de salida deseado.

Un subreporte es solamente un reporte que ha sido incorporado a otro reporte. De hecho podemos tener subreportes dentro de otros subreportes.

En este último tutorial de la serie de JasperReports y iReports veremos cómo crear subreportes haciendo uso de estas dos herramientas. Veremos dos ejemplos, el primero mostrará cómo hacer los subreportes mediante una conexión JDBC a la base de datos. El segundo mostrará como hacerlo con un DataSource propio.

En ambos casos crearemos un reporte de alumnos de una escuela y subreportes de cada uno de los alumnos con la lista de materias que tiene cada uno (aunque por facilidad todos tendrán las mismas materias cuando hagamos el ejemplo con el DataSource propio).



1 - Subreportes con conexión a base de datos

Lo primero que haremos es generar una base de datos de prueba. Yo hare uso de MySql 5.1, pero pueden usar el manejador que más les guste. Creamos una base de datos llamada "pruebaReportes" y usamos el siguiente script para crear la tabla "alumnos":
CREATE TABLE `alumnos` (            
                `ID` bigint(21) NOT NULL,               
                `NOMBRE` varchar(100) NOT NULL,         
                `CLAVE` varchar(100) NOT NULL,       
                PRIMARY KEY  (`ID`)                     
              ) ENGINE=InnoDB DEFAULT CHARSET=latin1
después este para las materias:
CREATE TABLE `materias` (            
                `ID` BIGINT(21) NOT NULL,               
                `NOMBRE` VARCHAR(100) NOT NULL,         
                PRIMARY KEY  (`ID`)                     
              ) ENGINE=INNODB DEFAULT CHARSET=latin1

y finalmente esta será la tabla intermedia, o tabla de unión de las dos anteriores (ya que se generará una relación N:M entra ambas):
CREATE TABLE `alumnos_materias` (
    `ALUMNO_ID` BIGINT(21) NOT NULL, 
    `MATERIA_ID` BIGINT(21) NOT NULL, 
     PRIMARY KEY (`ALUMNO_ID`, `MATERIA_ID`),

    FOREIGN KEY (`ALUMNO_ID`) REFERENCES `alumnos` (`ID`),
    FOREIGN KEY (`MATERIA_ID`) REFERENCES `materias` (`ID`) ) ENGINE=INNODB DEFAULT CHARSET=latin1


Ahora que tenemos las tablas usaremos el siguiente script para llenarlas:

INSERT INTO alumnos VALUES (1, 'Alumno 1', '00001');
INSERT INTO alumnos VALUES (2, 'Alumno 2', '00002');
INSERT INTO alumnos VALUES (3, 'Alumno 3', '00003');
INSERT INTO alumnos VALUES (4, 'Alumno 4', '00004');
INSERT INTO alumnos VALUES (5, 'Alumno 5', '00005');
INSERT INTO alumnos VALUES (6, 'Alumno 6', '00006');
INSERT INTO alumnos VALUES (7, 'Alumno 7', '00007');
INSERT INTO alumnos VALUES (8, 'Alumno 8', '00008');
INSERT INTO alumnos VALUES (9, 'Alumno 9', '00009');
INSERT INTO alumnos VALUES (10, 'Alumno 10', '000010');


INSERT INTO materias VALUES (1, 'Matematicas');
INSERT INTO materias VALUES (2, 'Fisica');
INSERT INTO materias VALUES (3, 'Quimica');
INSERT INTO materias VALUES (4, 'Biologia');
INSERT INTO materias VALUES (5, 'Historia');
INSERT INTO materias VALUES (6, 'Geografia');


INSERT INTO alumnos_materias VALUES (1, 1);
INSERT INTO alumnos_materias VALUES (1, 3);
INSERT INTO alumnos_materias VALUES (1, 5);

INSERT INTO alumnos_materias VALUES (2, 1);
INSERT INTO alumnos_materias VALUES (2, 2);
INSERT INTO alumnos_materias VALUES (2, 3);

INSERT INTO alumnos_materias VALUES (3, 2);
INSERT INTO alumnos_materias VALUES (3, 4);
INSERT INTO alumnos_materias VALUES (3, 6);

INSERT INTO alumnos_materias VALUES (4, 4);
INSERT INTO alumnos_materias VALUES (4, 5);
INSERT INTO alumnos_materias VALUES (4, 6);

INSERT INTO alumnos_materias VALUES (5, 1);
INSERT INTO alumnos_materias VALUES (5, 4);
INSERT INTO alumnos_materias VALUES (5, 5);

INSERT INTO alumnos_materias VALUES (6, 2);
INSERT INTO alumnos_materias VALUES (6, 5);
INSERT INTO alumnos_materias VALUES (6, 6);

INSERT INTO alumnos_materias VALUES (7, 1);
INSERT INTO alumnos_materias VALUES (7, 3);
INSERT INTO alumnos_materias VALUES (7, 5);

INSERT INTO alumnos_materias VALUES (8, 1);
INSERT INTO alumnos_materias VALUES (8, 2);
INSERT INTO alumnos_materias VALUES (8, 3);

INSERT INTO alumnos_materias VALUES (9, 2);
INSERT INTO alumnos_materias VALUES (9, 4);
INSERT INTO alumnos_materias VALUES (9, 6);

INSERT INTO alumnos_materias VALUES (10, 4);
INSERT INTO alumnos_materias VALUES (10, 5);
INSERT INTO alumnos_materias VALUES (10, 6);


Ya con los datos listos procedemos a crear el proyecto en NetBeans ("File -> New Project... -> Java -> Java Application"). Con lo que se creará una clase Main que aparecerá en nuestro editor.

Por el momento dejaremos así el proyecto de NetBeans y procederemos a crear la plantilla de nuestro reporte desde iReport.

Como en esta ocasión tenemos una base de datos, usaremos el "Report Wizard", por lo que vamos al menú "Archivo -> New.. -> Report Wizard". Con esto se abrirá el wizard de 7 pasos que expliqué en el primer tutorial.

El paso 2 (en el que empieza el wizard) nos pide dar un nombre al archivo del reporte y una ubicación. Denle el nombre que gusten (en mi caso "reporteMaestro.jrxml") y guardenlo en la carpeta raíz del proyecto de NetBeans que acabamos de crear.

Presionamos el botón "Siguiente >" para ir la paso 3. Aquí debenos seleccionar el Data Source que se usará para generar el reporte. Si ya tienen creado el datasource para la base de datos "pruebaReportes" pueden seleccionarla aqui. Sino no se preocupen, este es el momento para crearlo.

Para crear el datasource que usaremos hacelos click en el botón "New":


Esto abrirá una ventana en la que tendremos que seleccionar el tipo de Datasource que queremos usar. En nuestro caso será un "Database JDBC connection" (la primer opción).


Presionamos el botón "Next >". En la siguiente pantalla debemos darle un nombre y algunos parámetros al datasource, como la dirección del servidor y el nombre de la base de datos.


Para probar que la conexión puede establecerse, hagan click en el botón "Test" y, si todo estpa bien, debe aparecer un mensaje como el siguiente:


Si obtuvieron el mensaje anterior hagan click en "Aceptar" y posteriormente en el botón "Save" para guardar el datasource.

Veremos que en la ventana del paso 3 del "Report Wizard" ahora tenemos un textarea que nos indica que debemos introducir un query. Pueden usar el query designer si gustan (haciendo click en el botón "Design Query") para crear la query que usaremos para obtener todos los datos de la tabla "alumnos" (solo de esa tabla). Al final la query debe ser más o menos así:
SELECT 
alumnos.`ID` AS alumnos_ID, 
    alumnos.`NOMBRE` AS alumnos_NOMBRE, 
    alumnos.`CLAVE` AS alumnos_CLAVE
FROM 
    `alumnos` alumnos


Hacemos click en el botón "Siguiente >" para ir al paso 4. En este paso tendremos que seleccionar los campos, de los que acabamos de seleccionar, que queremos mostrar en el reporte. En este caso solo queremos mostrar el nombre y la clave del alumno (el id lo usaremos como un elemento auxiliar para obtener los datos del subreporte, por lo que es necesario que también lo releccionemos). Por lo que seleccionamos todos los campos:


Presionamos el botón "Siguiente >". En el paso 5 podemos seleccionar un campo sobre el que queramos crear un grupo. Nosotros no queremos crear ninguno, por lo que dejamos todo como está y presionamos el botón "Siguiente >" para ir al paso 6 en el que seleccionaremos el layout del reporte. La opción por defecto ("Columnar Layout") esta bien, por lo que la dejamos y hacemos click en "Siguiente >" para ir al paso final en el que nos felicitarán por haber creado exitosamente nuestro reporte ^-^!.


Ahora podemos hacer click en el botón "Finalizar" para ver aparecer la plantilla de nuestro reporte, en la pestaña "Designer", el cual debe verse de la siguiente forma:


Modificaremos los textos estáticos para que concuerden un poco más con el tipo de reporte que queremos lograr. También eliminaremos las etiqueta y el texto que muestran el valor de id ya que, como dijimos hace un momento, no nos interesa mostrarlo:



Si generamos la vista previa del reporte veremos lo siguiente:


No es el mejor reporte pero es un principio ^-^. Ahora procederemos a agregar el subreporte. Este subreporte mostrará las materias a las que esta inscrito cada uno de los alumnos.

Para esto arrastramos el elemento "Subreport" desde los "Report Elements" y lo colocamos en la banda details, justo debajo de los campos de texto:


Al hacer esto se abrirá la ventana del "Subreport Wizard" que es la que nos guiará en el proceso de la creación del subreporte:


El primer paso consiste en indicar si nuestro subreporte será un reporte nuevo, uno existente, o si solo queremos crear el elemento subrepote. Nosotros crearemos un nuevo reporte (que es el que mostrará las materias a las que está inscrito cada alumno), así que dejamos seleccionada la primera opción ("Create a new report") y hacemos click en el botón "Siguiente >".

En el paso 2 se nos preguntara qué datasource usaremos para este reporte. Nosotros usaremos el datasource creado hace unos momentos (que en mi caso se llamaba "Prueba Reportes"). Seleccionamos este en el menú desplegable, con los que nos aparecerá un textarea para que coloquemos una query. Pueden usar el query designer si gustan para obtener una query que seleccione todos los campos de las tablas "alumnos_materias" y "materias" y haga un join entre estas (que obtenga las materias que tiene cada alumno). La query final queda de la siguiente forma:
SELECT 
alumnos_materias.`ALUMNO_ID` AS alumnos_materias_ALUMNO_ID, 
alumnos_materias.`MATERIA_ID` AS alumnos_materias_MATERIA_ID, 
materias.`ID` AS materias_ID, 
materias.`NOMBRE` AS materias_NOMBRE
FROM 
`materias` materias INNER JOIN `alumnos_materias` alumnos_materias ON materias.`ID` = alumnos_materias.`MATERIA_ID`


Presionamos el botón "Siguiente >" para ir al paso 3, en el que se nos preguntará qué campos de los regresados por la consulta queremos mostrar en el reporte (o en este caso en el subreporte). A nosotros solo nos interesa mostrar los campos que tienen que ver con la materia, por lo que solo seleccionamos los campos "materias_NOMBRE" y "materias_ID":


Presionamos en botón "Siguiente >" para ir al paso 4. En este paso podemos elegir si queremos crear un grupo, como nosotros no queremos crear ninguno, simplemente dejamos todo como está y volvemos a presionar el botón "Siguiente >".

En el paso 5 podemos elegir el layout del reporte. Puden seleccionar el que quieran. En mi caso dejaré la opción por default ("Columnar Layout") y presionamos el botón "Siguiente >".

En el paso 6 podemos darle un nombe y una ubicación al subreporte. Yo le llamaré "subreporteMaterias.jrxml" y lo guardaré en el mismo directorio que el "reporteMaestro.jrxml" (en la carpeta raíz del proyecto de NetBeans). También podemos ejegir si queremos guardar la ruta del subreporte (para que el reporte maestro sepa donde buscarlo) como un parámetro llamado $P{SUBREPORT_DIR}, o como una ruta absoluta estática. Lo mejor es elegir está ultima ya que modificaremos la ruta para que busque el subreporte en en el mismo directorio que el reporte maestro. Así si los movemos de un directorio a otro, solo tendremos que preocuparnos de llevarnos los dos juntos.


Presionamos una vez más el botón "Siguiente >" para ir al ultimo paso.

En el paso final (el paso 7) debemos seleccionar cómo le pasaremos el datasource al subreporte desde el reporte maestro. Tenemos 5 opciones:
  • Usar la misma conexión que el reporte maestro
  • Usar otra conexión
  • Usar una expresión de un JRDataSource
  • Usar un datasource vacio
  • No usar conexión o datasource alguno
Nosotros seleccionamos la primera opción ya que queremos que el subreporte use la misma conexión que el reporte maestro:


presionamos el botón "Finalizar" para mostrar el subreporte:


Modificamos un poco el diseño del reporte para que se vea bien al mostrarlo dentro del reporte maestro (cambiamos unos textos, eliminamos otros, y eliminamos la banda "Page Footer", también agregamos una línea en la banda "Summary" para identificar en el reporte final cuando termine la lista de materias):


Ahora agregamos un nuevo parámetro al subreporte (click derecho en el nodo "Parameters" del panel "Report Inspector" y seleccionamos "Añadir Parameter") llamado alumnos_ID de tipo java.lang.Long.


Lo siguiente que haremos es el primero de los dos trucos para generar subreportes en base a una conexión con base de datos: hacemos click sobre la ventana del designer, pero fuera del reporte para que en el panel de propiedades aparezcan las propiedades del "subreporteMaterias". Una vez en este panel buscamos la propiedad "Query Text" de la sección "More...":


Hacemos click en el botón "..." para que se abra una ventana que contiene el texto de la consulta que creamos al crear el subreporte. Agregamos el final de la consulta lo siguiente:

AND alumnos_materias.`ALUMNO_ID`= $P{alumnos_ID}


Con esto lograremos que el subreporte se genere solo con las materias que correspondan al alumno con el id que le indiquemos (lo cual haremos en un momento y que de hecho es el segundo secreto).

Presionamos el botón "OK" para guardar la consulta.

Para terminar guardamos el subreporte y cambiamos a la vista "Preview" para compilar el reporte y generar el archivo "subreporteMaterias.jasper". El preview debe verse más o menos así:


Ahora podemos regresar al reporteMaestro, el cual debe verse así (después de ajustar el tamaño y la posición del componente subreporte):


Seleccionamos el elemento subreporte y en el panel de propiedades buscamos el elemento "Subreport Expression" de la sección "Subreport Properties". Eliminamos la ruta absoluta y dejamos solo el nombre del reporte ("subreporteMaterias.jasper"):


Ahora viene el segundo de los secretos para le generación de los subreportes usando conexión a base de datos. Seleccionamos el componente del subreporte y buscamos en panel de propiedades y buscamos la propiedad "Parameters" de la sección "Subreport Properties":


Hacemos click en el botón "..." con lo que se abrirá una ventana que nos permite agregar parámetros que serán pasados del reporte maestro al subreporte.

Si recuerdan nosotros indicamos en el subreporte que queremos que muesre las materias en las que está inscrito un alumno con un particular. Pues bien es aquí en donde pasamos ese dato, el id del alumno del que se están mostrando los datos. Este parametro nos permitirá pasarle el valor al parámetro "alumnos_ID" que agregamos en el subreporte.

Presionamos el botón "Add" y agregamos como nombre del parámetro "alumnos_ID" y como valor "$F{alumnos_ID}":


Presionamos "OK" en ambas ventanas y listo, ya tenemos nuestro reporte maestro con subreportes ^-^.

Ahora guardamos el reporte y cambiamos a la vista de "Preview". Ahora debemos ver algo como lo siguiente:


Con esto terminamos la parte que corresponde a iReport y podemos regresar al NetBeans.

Si lo recuerdan dejamos la clase Main vacia, ahora procederemos a llenarla con el código para generar el reporte.

Recuerden que lo primero que debemos hacer es agregar al nodo "Libraries" del panel "Projects" la biblioteca "JasperReports" que creamos en el primer tutorial de la serie, el jar "commons-logging-1.1.1.jar", y el jar "mysql-connector-java-5.1.7-bin.jar" que es el driver de la base de datos.

Una vez que tenemos esto, escribimos el siguiente códifo en el método main de la clase Main:
    Class.forName("com.mysql.jdbc.Driver"); 
    Connection conexion = DriverManager.getConnection("jdbc:mysql://localhost:3306/pruebareportes", "user", "pass");      

    JasperReport reporte = (JasperReport) JRLoader.loadObject("reporteMaestro.jasper");      
    JasperPrint jasperPrint = JasperFillManager.fillReport(reporte, null, conexion);  

    JRExporter exporter = new JRPdfExporter(); 

    exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint); 
    exporter.setParameter(JRExporterParameter.OUTPUT_FILE, new java.io.File("reportePDF.pdf")); 

    exporter.exportReport();

La explicación de este código está en el primer tutorial de esta serie.

Al ejecutarlo se creará el archivo "reportePDF.pdf" en el directorio raíz del proyecto. Si abrimos el archivo veremos más o menos lo siguiente:



Con lo que comprobamos que el reporte, junto con su subreporte, se ha generado correctamente ^-^. Ahora veremos cómo hacer esto mismo usando un DataSource propio.


2 - Subreportes con datasources propios

Para hacer un subreporte con un datasource propio también es necesario conocer un par de secretos, los cuales revelaré un unos momentos.

Lo primero que haremos es crear un nuevo proyecto en NetBeans ("File -> New Project... ->Java -> Java Application"). Con lo que se creará una clase Main que aparecerá en nuestro editor.

Recuerden que también debemos agregar al nodo "Libraries" del panel "Projects" la biblioteca "JasperReports" que creamos en el primer tutorial de la serie y el jar "commons-logging-1.1.1.jar"

Crearemos las clases que mantendrán los datos que mostraremos en el reporte ("Alumno" y "Materia"). La clase "Alumno" tendrá solo los atributos "id", "clave" y "nombre" (con sus correspondientes setters y getters):
public class Alumno 
{   
    private int id;   
    private String clave;   
    private String nombre;    

    public Alumno()   
    {   
    }    

    public Alumno(int id, String clave, String nombre)   
    {       
        this.id = id;       
        this.clave = clave;       
        this.nombre = nombre;   
    }    

    public String getClave()   
    {       
        return clave;   
    }    

    public void setClave(String clave)   
    {       
        this.clave = clave;   
    }    

    public int getId()   
    {       
        return id;   
    }    

    public void setId(int id)   
    {       
        this.id = id;   
    }    

    public String getNombre()   
    {       
        return nombre;   
    }    

    public void setNombre(String nombre)   
    {       
        this.nombre = nombre;   
    } 
}

La clase "Materia" tendrá los atributos "id" y "nombre":
public class Materia 
{   
    private int id;   
    private String nombre;    

    public Materia()   
    {   
    }    

    public Materia(int id, String nombre)   
    {       
        this.id = id;       
        this.nombre = nombre;   
    }    

    public int getId()   
    {       
        return id;   
    }    

    public void setId(int id)   
    {       
        this.id = id;   
    }    

    public String getNombre()   
    {       
        return nombre;   
    }    

    public void setNombre(String nombre)   
    {       
        this.nombre = nombre;   
    } 
}

Aún falta agregar algunas cosas a la clase "Alumno", pero dejaremos este proyecto a un lado por el momento mientras creamos la plantilla de nuestro reporte desde iReport.

Abrimos el iReport y creamos un nuevo reporte vacio (menú Archivo -> New... -> Empty report). Asignamos al reporte un nombre (que en mi caso será "reporteMaestroDS.jrxml") y lo guardamos en la raíz del proyecto de NetBeans. Hacemos click en el botón "Finalizar" con lo que veremos aparecer la plantilla vacia dentro de la vista "Designer".

Agregamos un par de fields (clave y nombre), y unos textos estáticos para que este reporte maestro sea similar al anterior (eliminé algunas bandas para que el reporte se vea más claro):


Agregamos un field adicional llamado "materiasDS" de tipo "net.sf.jasperreports.engine.JRDataSource". Como este tipo no aparece en el combo para selección de tipo del field debemos escribirlo en la ventana que se abre al presionar el botón "..." junto a este combo box:


Este field será el que dirá a JasperReports que debe llamar al getter correspondiente de la clase Alumno que crearemos más adelante para preguntar a cuáles materias está inscrito (quedará claro cuando escribamos el código Java).

Ahora agregamos arrastramos el elemento "Subreport" de los "Report Elements" a la banda "Details":


Al hacer esto se abrirá la ventana "Subreport wizard". Como ahora no usaremos una conexión no necesitamos un query, entonces no todos los pasos aplican.

En el primer paso seleccionamos si queremos crear un nuevo reporte que servirá de subreporte, usar un reporte existente, o solo crear un elemento subreporte. Seleccionamos la primera opcion ("Create a new report") y presionamos el botón "Siguiente >".

En el paso 2 debemos seleccionar un datasource. Nosotros seleccionaremos el "Empty datasource". Presionamos el botón "Siguiente >" para ir al paso 3, en el que debemos seleccionar los fields que queremos agregar al subreporte. Como nosotros no estamos usando una conexión, no tenemos fields para seleccionar, así que simplemente presionamos el botón "Siguiente >".

En el paso 4 podemos escoger crear un grupo, aqui pasa lo mismo que en el paso 3 así que seguimos adelante.

En el paso 5 seleccionamos un layout para el subreporte. Pueden seleccionar el que quieran, yo usaré el "Columnar Layout". Presionamos el botón "Siguiente >".

Vamos al paso 6. Aquí le damos una ubicación y un nombre al proyecto. Le daré de nombre "subreporteMateriasDS.jrxml". Lo guardamos en el mismo directorio que el "reporteMaestroDS.jrxml". Además indicamos que queremos que la ruta del subreporte se almacene en una referencia estática absoluta ("Use a static absolute path reference"):


Pulsamos el botón "Siguiente >" para ir al último paso.

En el paso 7 debemos indicar cómo queremos pasar los datos del reporte maestro al subreporte. En este caso queremos usar una "JRDatasource expression" (la tercer opción), seleccionamos esta opción y agregamos la expresión creada anteriormente ("$F{materiasDS}"), que será la que nos permita conectar el reporte con el subreporte:


Presionamos el botón "Finalizar" con lo que aprecerá el subreporte en la vista de diseño:


Agregamos dos fields: uno para el identificador de la materia (de tipo java.lang.Integer), y uno para el nombre de la materia (de tipo java.lang.String):


Ahora arreglamos un poco el diseño del subreporte para que se vea mejor: eliminamos la página "Page Footer", agregamos los fields que acabamos de crear a la banda "Detail", y arreglamos un poco el título. También agregamos una línea en la banda "Summary" para saber cuando termina el subreporte:


Guardamos el reporte y cambiamos a la vista preview para compilar el subreporte. En la vista de preview deberian ver algo asi (recuerden seleccionar que quieren usar el "Empty datasource"):


Ahora regresemos al reporteMaestro, el cual debe verse así (después de ajustar el tamaño y la posición del componente subreporte):


Seleccionamos el elemento subreporte y en el panel de propiedades buscamos el elemento "Subreport Expression" de la sección "Subreport Properties". Eliminamos la ruta absoluta y dejamos solo el nombre del reporte ("subreporteMateriasDS.jasper"):


Esto es todo lo que se necestita hacer para generar el reporte, así que cambiaremos a la vista "Preview" para compilar el reporte. Deben ver algo asi:


Ahora regresamos al proyecto de NetBeans.

Hace unos momentos creamos dos clases: "Alumno" y "Materia" y dijimos que aún les faltaba algo: falta crear la relación entre un objeto alumno y las materias que tiene. Para esto agregaremos un atributo de tipo java.util.List a la clase "Alumno" que mantendrá la lista de materias a las que está inscrito el alumno (con sus correspondientes getters y setters). Además agregaremos un método auxiliar que nos permitirá agregar las materias de una forma más simple:
private List materias = new ArrayList();    

public List getMaterias()   
{       
return materias;   
}    

public void setMaterias(List materias)   
{       
    this.materias = materias;   
}    

public void addMateria(Materia materia)   
{       
    this.materias.add(materia);   
}

Ahora viene el segundo secreto para crear subreportes con datasource propios: si lo recuerdan, en el reporteMaestro agregamos un parémetro llamado "materiasDS" de tipo "net.sf.jasperreports.engine.JRDataSource". Es por medio de este parámetro que el reporte maestro pregunta a cada uno de los alumnos con cuáles materias está relacionado (a cuáles está inscrito) para poder generar el subreporte.

Es el alumno el encargado de regresar esta información a JasperReports por medio de un getter, que debe estár formado siguiente el patrón de getters como si tuviera un atributo llamado "materiasDS" de tipo "net.sf.jasperreports.engine.JRDataSource". Por lo que el método será de la siguiente forma:
public JRDataSource getMateriasDS()     {       
{
    return new JRBeanCollectionDataSource(materias);   
}
El código completo de la clase "Alumno" queda de la siguiente forma:
import java.util.ArrayList; 
import java.util.List; 
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;  

/** 
* @author Alex 
*/ 

public class Alumno 
{   
    private int id;   
    private String clave;   
    private String nombre;    
    private List materias = new ArrayList();    

    public Alumno()   
    {   
    }    

    public Alumno(int id, String clave, String nombre)   
    {       
        this.id = id;       
        this.clave = clave;       
        this.nombre = nombre;   
    }    

    public String getClave()   
    {       
        return clave;   
    }    

    public void setClave(String clave)   
    {       
        this.clave = clave;   
    }    

    public int getId()   
    {       
        return id;   
    }    

    public void setId(int id)   
    {       
        this.id = id;   
    }    

    public String getNombre()   
    {       
        return nombre;   
    }    

    public void setNombre(String nombre)   
    {       
        this.nombre = nombre;   
    }    

    public List getMaterias()   
    {       
        return materias;   
    }    

    public void setMaterias(List materias)   
    {       
        this.materias = materias;   
    }    

    public void addMateria(Materia materia)   
    {       
        this.materias.add(materia);   
    }    
  
    public JRDataSource getMateriasDS()   
    {       
        return new JRBeanCollectionDataSource(materias);   
    } 
}

Este es todo el cambio que necesita la clase "Alumno" para que nuestros subreportes se generen exitosamente.

Ahora regresaremos a la clase "Main". Aqui agregaremos 2 ciclos for, uno que creará las instancias de "Alumno" y las agregará a la lista que posteriormente pararemos como DataSource y JasperReports, y un ciclo interno que creará instancias de "Materia" y las relacionará con los alumnos (todos los alumnos tendrán las mismas tres materias en este caso). Los cliclos quedán así (listaAlumnos es un objeto List que mantendrá los objetos Alumno):
for (int i = 1; i <= 5; i++) 
{ 
    Alumno alumno = new Alumno(i, "0000" + i, "Alumno " + i); 
    listaAlumnos.add(alumno);

    for (int j = 1; j <= 3; j++)   
    {       
        alumno.addMateria(new Materia(j, "Materia " + j));   
    }
}

El resto de la clase Main es el que hemos estado viendo a lo largo de la serie de tutoriales y que está explicado detalladamente en el segundo tutorial. El código completo de Main es el siguiente (omitiendo los imports):
public class Main 
{    
    public static void main(String[] args) throws Exception
    {       
        List listaAlumnos = new ArrayList();        

        for (int i = 1; i <= 5; i++)       
        {           
            Alumno alumno = new Alumno(i, "0000" + i, "Alumno " + i);           
            listaAlumnos.add(alumno);            

            for (int j = 1; j <= 3; j++)           
            {               
                alumno.addMateria(new Materia(j, "Materia " + j));           
            }       
        }        

        JasperReport reporte = (JasperReport) JRLoader.loadObject("reporteMaestroDS.jasper");       
        JasperPrint jasperPrint = JasperFillManager.fillReport(reporte, null, new JRBeanCollectionDataSource(listaAlumnos));        
      
        JRExporter exporter = new JRPdfExporter();       
        exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);       
        exporter.setParameter(JRExporterParameter.OUTPUT_FILE, new File("reporteMaestroDS.pdf"));        
        exporter.exportReport();   
    }
}


Esto generará un archivo llamado "reporteMaestroDS.pdf" en el directrio raíz del proyecto de NetBeans. El archivo tendrá el siguiente contenido:


Con lo que comprobamos que el reporte maestro junto con sus subreportes se ha creado correctamente ^-^.

Bien, esta es el último tutorial de la serie de JasperReports. Tal vez haga algunos otros post referentes a JasperReports, pero serán aparte de la serie. Espero que todo les sea de utilidad.

No olviden dejar sus dudas, comentarios y sugerencias.

Saludos.

Visita la parte 1 de este tutorial: Reportes con Conexión a Bases de Datos
Visita la parte 2 de este tutorial: Usando DataSources Personalizados
Visita la parte 3 de este tutorial: Parámetros y Variables
Visita la parte 4 de este tutorial: Reportes en aplicaciones web
Visita la parte 5 de este tutorial: Gráficas en Reportes
Visita la parte 6 de este tutorial: Grupos

45 comentarios:

  1. Buenísim blog, lo seguiré de cerca. Y gracias por compartir informacion

    ResponderEliminar
  2. Que tal como estás muchas gracias por este manual está muy bien explicado, pero quisiera preguntarte algo, seguí el manual paso a paso tengo un reporte en el que el reporte maestro se llena con datos de dos tablas y el subreporte de una tercera tabla, pero al dar el parametro algunas veces me dice que el documento no tiene páginas y algunas veces me sale lo siguiente:
    Error filling print... Unknown column name : Folio
    net.sf.jasperreports.engine.JRException: Unknown column name : Folio      at net.sf.jasperreports.engine.JRResultSetDataSource.getColumnIndex(JRResultSetDataSource.java:359)      at net.sf.jasperreports.engine.JRResultSetDataSource.getFieldValue(JRResultSetDataSource.java:116)      at net.sf.jasperreports.engine.fill.JRFillDataset.setOldValues(JRFillDataset.java:807)      at net.sf.jasperreports.engine.fill.JRFillDataset.next(JRFillDataset.java:771)      at net.sf.jasperreports.engine.fill.JRBaseFiller.next(JRBaseFiller.java:1413)      at net.sf.jasperreports.engine.fill.JRVerticalFiller.fillReport(JRVerticalFiller.java:111)      at net.sf.jasperreports.engine.fill.JRBaseFiller.fill(JRBaseFiller.java:899)      at net.sf.jasperreports.engine.fill.JRBaseFiller.fill(JRBaseFiller.java:802)      at net.sf.jasperreports.engine.fill.JRFiller.fillReport(JRFiller.java:63)      at net.sf.jasperreports.engine.JasperFillManager.fillReport(JasperFillManager.java:421)      at net.sf.jasperreports.engine.JasperFillManager.fillReport(JasperFillManager.java:251)      at com.jaspersoft.ireport.designer.compiler.IReportCompiler.run(IReportCompiler.java:896)      at org.openide.util.RequestProcessor$Task.run(RequestProcessor.java:561)      at org.openide.util.RequestProcessor$Processor.run(RequestProcessor.java:986) 
    Print not filled. Try to use an EmptyDataSource...
    Podrías ayudarme? Me urge es para mi trabajo Gracias

    ResponderEliminar
  3. Hola, antes que nada muchas gracias por tus comentarios.

    En cuanto a tu duda esto puede deberse a que la consulta que estes realizandó (ya sea en el reporte maestro o en el subreporte) no estpe regresando el valor que pasa a la propiedad "Parameters" de la sección "Subreport Properties" (que supongo que se llama "Folio"). Recuerda que este parámetro debe de ser regresado en la consula que regresa el reporte maestro. Debes tener algo como "SELECT ..., Folio FROM ...) y un field llamado algo así como $F{Folio}


    También podría ser que el nombré este mal escrito, hay que tener cuidado con las mayúsculas y las minúsculas.

    Espero que esto te ayude, sino dime y seguimos buscando.

    Saludos

    ResponderEliminar
  4. Estas son mis dos consultas tal vez tengas razón y algo por aquí esté mal, primero el Reporte maestro:

    SELECT
    empleados.`Nombre` AS empleados_Nombre,
    solicitud_viat.`Folio` AS solicitud_viat_Folio,
    solicitud_viat.`Origen` AS solicitud_viat_Origen,
    solicitud_viat.`Destino` AS solicitud_viat_Destino,
    solicitud_viat.`Dia_inicio` AS solicitud_viat_Dia_inicio,
    solicitud_viat.`Mes_inicio` AS solicitud_viat_Mes_inicio,
    solicitud_viat.`Anio_inicio` AS solicitud_viat_Anio_inicio,
    solicitud_viat.`Dia_fin` AS solicitud_viat_Dia_fin,
    solicitud_viat.`Mes_fin` AS solicitud_viat_Mes_fin,
    solicitud_viat.`Anio_fin` AS solicitud_viat_Anio_fin,
    solicitud_viat.`Dias_duracion` AS solicitud_viat_Dias_duracion,
    solicitud_viat.`Tipo_moneda` AS solicitud_viat_Tipo_moneda,
    solicitud_viat.`Autorizador` AS solicitud_viat_Autorizador,
    transferencias.`Dia_transfer` AS transferencias_Dia_transfer,
    transferencias.`Mes_transfer` AS transferencias_Mes_transfer,
    transferencias.`Anio_transfer` AS transferencias_Anio_transfer,
    transferencias.`Cantidad` AS transferencias_Cantidad
    FROM
    `solicitud_viat` solicitud_viat INNER JOIN `empleados` empleados ON solicitud_viat.`Id_empleado` = empleados.`Id_empleado`
    INNER JOIN `transferencias` transferencias ON solicitud_viat.`Folio` = transferencias.`Folio`
    WHERE
    solicitud_viat.`Folio` = $P{Folio}

    Y este es el subreporte:

    SELECT
    conceptos.`Id_concepto` AS conceptos_Id_concepto,
    conceptos.`Folio` AS conceptos_Folio,
    conceptos.`Concepto` AS conceptos_Concepto,
    conceptos.`Cantidad` AS conceptos_Cantidad,
    conceptos.`Porcentaje_iva` AS conceptos_Porcentaje_iva,
    conceptos.`Iva_cant` AS conceptos_Iva_cant,
    justif_gastos.`Folio` AS justif_gastos_Folio
    FROM
    `conceptos` conceptos INNER JOIN `justif_gastos` justif_gastos ON conceptos.`Folio` = justif_gastos.`Folio`
    where conceptos.`Folio`=$P{Folio}

    En el maestro tengo un parámetro porque estoy trabajando con una aplicación web y esta pide un parámetro para sacar el reporte
    Gracias por tu ayuda

    ResponderEliminar
  5. Hola, me parece que el nombre del field que debes pasar a la propiedad "Parameters" de la sección "Subreport Properties" es $F{solicitud_viat_Folio} y el nombre del mismo parámetro debe ser Folio para que puedas hacer esto "where conceptos.`Folio`=$P{Folio}" como indicas al final para que el subreporte se genere correctamente. Te recomiendo que cambies el nombre de ese parámetro para que no haya confusiones. Podrías cambiarlo de $P{Folio} a $P{Folio_actual} o algo asi

    Saludos

    ResponderEliminar
  6. Excelente muchísimas gracias ya me salió, te agradezco muchísimo tu ayuda ahora no se si te podría pedir algo más de ayuda abusando de tu paciencia y amabilidad ya que necesito pasar los parámetros de mi aplicación web a un servlet pero me ha costado algo de trabajo adaptarlo al manual que pusiste en este post porque tengo que pedir los parámetros desde una JSP, ya mandarlos al servlet para que mande el reporte así que busqué de otra forma pero no me saca el reporte, te explico lo que tengo y tal vez me podrías ayudar.
    Mi aplicación Web la manejo con un paquete llamado beans donde estan los getters y setters de cada tabla, como te habrás dado cuenta mis reportes son de varias tablas así que no supe adaptarlo a como lo tienes en el tutorial de aplicaciones web, también tengo una JSP donde pido el parámetro y mando llamar al servlet:

    form action="RepFolioServlet" method="post"
    fieldset
    Folio:
    input type="text" name="Folio" size="15"
    input type="submit" value="ACEPTAR"
    input type="reset" value="CANCELAR"
    fieldset
    form
    En mi página lleva sus signos correspondientes pero no las puse aquí porque no me las aceptaba para poner el mensaje
    Luego está mi servlet lo cual adapté de otra forma tal vez podrías ayudarme a decirme que está mal o como se podría adaptar a la forma como tu lo haces:

    package Servlet;
    import net.sf.jasperreports.engine.*;
    import java.util.*;
    import java.io.*;
    import ConexionBD.*;
    import java.io.IOException;
    import javax.servlet.ServletException;
    import javax.servlet.ServletOutputStream;
    import javax.servlet.http.HttpServlet;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
    File reportFile = new File(getServletContext().getRealPath("repfolio6.jasper"));
    Map parameters = new HashMap();
    parameters.put("Folio", request.getParameter("Folio"));
    try {
    Conexion conn=new Conexion();
    byte[] bytes = JasperRunManager.runReportToPdf(reportFile.getPath(), parameters, conn.getConexion());
    response.setContentType("application/pdf");
    response.setContentLength(bytes.length);
    ServletOutputStream ouputStream = response.getOutputStream();
    ouputStream.write(bytes, 0, bytes.length);
    ouputStream.flush();
    ouputStream.close();
    } catch (Exception e) {
    e.printStackTrace();
    }
    }
    Por último esta es mi conexión la cual también está en otro paquete:
    package ConexionBD;
    import java.sql.*;
    public class Conexion {
    public Connection getConexion(){
    Connection c=null;
    try{
    Class.forName("com.mysql.jdbc.Driver");
    c=DriverManager.getConnection("jdbc:mysql://localhost:3306/viaticosver2","root","unitec");
    }
    catch(Exception e){
    e.printStackTrace();
    }
    return c;
    }
    }
    Y esque lo quería hacer como tu porque quiero que tenga las opciones de guardar y abrir para que se pueda guardar y abrir en formato de excel
    Te agradezco mucho tu ayuda anterior y esta

    ResponderEliminar
  7. Que tal como estás oye una pregunta estoy tratando de realizar esto otra vez a como tu lo manejaste pero esque no se cuales pasos seguir, por ejemplo yo estoy haciendo una aplicacion web y mi reporte maneja un subreporte, además recibe un parámetro y no se si tenga que seguir los pasos del manual que dice datasource propio porque este es parecido al de la aplicación web que pusiste arriba.
    Estoy desesperado cualquier ayuda te la agradecería mucho, tengo dos días para entregar mi aplicación, te agradezco mucho tu ayuda

    ResponderEliminar
  8. Hola; si estás trabajando en una aplicación web puedes fijarte aquí:

    http://javatutoriales.blogspot.com/2009/04/creacion-de-reportes-con-jasperrepots-y.html

    como agregar los reportes.

    Si estás usando un datasource propio los pasos a seguir son los mismos. Sino, si estas usando una conexión a la base de datos y tienes las consultas directamente en el reporte, es casí lo mismo, solo tienes que pasar la conexión en vez del datasource. Todos los demás pasos son exactamente iguales.

    Saludos

    ResponderEliminar
  9. OK gracias pero eso es precisamente lo que no entiendo muy bien, tengo las consultas en mi reporte porque siento que así será un poco más fácil lo que no entiendo muy bien es lo siguiente:

    1.- Como le paso la conexión, tengo una clase con un método que realiza la conexión:
    package ConexionBD;
    import java.sql.*;
    public class Conexion {
    public Connection getConexion(){
    Connection c=null;
    try{
    Class.forName("com.mysql.jdbc.Driver");
    c=DriverManager.getConnection("jdbc:mysql://localhost:3306/viaticosver2","root","unitec");
    }
    catch(Exception e){
    e.printStackTrace();
    }
    return c;
    }
    }
    lo que hago es que dentro del servlet y dentro del try pongo lo siguiente:
    Conexion conn=new Conexion();
    esto está bien?

    2.- No entiendo muy bien algunas partes de este código:

    List listaPariticipantes = new ArrayList(); for (int i = 1; i <= 10; i++) { Participante p = new Participante(i, "Particpante " + i, "Usuario " + i, "Pass " + i, "Comentarios para " + i); p.setPuntos(i); listaPariticipantes.add(p);

    Se tiene que crear dos ciclos for con los fields tanto del reporte maestro como del subreporte? y los datos que están entre comillas son los nombres de los fields que se van a rellenar?
    Si tengo más de diez registros que le puedo cambiar al for?
    Por último a que hace referencia este fragmento
    p.setPuntos
    Gracias y disculpa si te molesto con tantas preguntas esque estos manuales son los que me han ayudado más, pero es la primera vez que uso el ireports y me sorprendieron pidiendome que la aplicación sacara reportes.
    Te agradeceré cualquier ayuda

    ResponderEliminar
  10. Hola, creo que necesitas un poco más de ayuda. Si quieres enviame tu proyecto a la siguiente dirección:

    programadorJavaBlog@gmail.com

    para que pueda revisarlo con más cuidado.

    Saludos

    ResponderEliminar
  11. Enseguida te lo mando muchas gracias

    ResponderEliminar
  12. hola, no se si me pueden ayudar, soy haciendo un reporte en iReport que tiene 3 parametros de fechas, empresa y almacen, no siempre es necesario que ingrese los tres parametros para que me salga el informe, basta que sea uno de ellos o los tres. el problema es cuando no ingreso un parametro al final no me muestra nada solo un mensaje de NO PAGES. AQUI VA EL SELECT QUE HICE

    SELECT R.REQUIREMENT_ID AS IDREQ,
    R.REQUIRMENT_TYPE_ID AS REQTIPOID,
    R.FACILITY_ID AS ALMACENID,
    R.PRODUCT_ID AS IDPRODUCTO,
    S.STATUS_ID AS ESTADO,
    TO_CHAR()R.REQUIRMENT_START_DATE,"YYYY-MM-DD") AS DIA,
    R.QUANTITY AS CANTIDAD,
    NF.DESCRIPCION AS DESCRIPCIONAL,
    P.PRODUCT_NAME AS NOMBREPROD,
    PA.P_NAME AS EMPRESA
    FROM REQUIREMENT R, PRODUCT P, N_FACILITY NF, PARTY PA, PARTY_ACCTG_PREFERENCE PAP
    WHERE R.FACILITY_ID= NF.ID_NAME
    AND NF.ID_NAME=$P{ALMACENID}
    AND R.PRODUCT_ID=P.PRODUCT_ID
    AND PA.PARTY_ID=PAP.PARTY_ID
    AND NF_OWNER_PARTTY_ID = PAP.PARTY_ID
    AND TRUNC(R.REQUIREMENT_START_DATE) BETWEEN TO_DATE($P{FECHAINICIAL},""YYYYY-MM-DD" AND TO_DATE($P{FECHAFIN},"YYYY-MM-DD")

    ME FALTA EL PARAMETRO DE CODIGO DE EMPRESA QUE ES PAP.PARTY_ID PERO NO SE DONDE PONERLO, BUENO PERO CON EL ALMCEN Y FECHAS SI SALIA PERO LO MALO ES QUE CUANDO NINGUN PARAMETRO NO SALE NINGUN REPORTE,PERO CUANDO PONGO TODOS LOS PARAMETROS ME SALE NORMAL.

    AHHH OTRA PREGUNTA, NO SE REALMENTE Q SE PONE EN EL PARAMETRO SUBREPORT_DIR , LE ESTY PONDIENDO LA DIRECCION DEL ARCHIVO DONDE ESTA MI SUBREPORTE PERO ME SALE ERROR.

    BUENO, EN VERDAD ESPERO Q ME PUEDAN AYUDAR..

    ResponderEliminar
  13. Hola;

    Respecto a lo que dices de que cuando no pasas ningún parámetro al reporte te dice que no hay páginas. Esto es en realidad un comportamiento normal, ya que tu consulta depende de esos parámetros, por lo tanto al no existir no se regresan resultados de la consulta y por lo tanto el reporte piensa que no hay nada que mostrar. Si no quieres que pase esto puedes enviar valores por default desde Java para que siempre se muestre por default un resultado. O si quieres que se muestre en el reporte un mensaje, por ejemplo: "No hay datos para los parámetros ingresados" o algo así puedes colocar este texto en una banda especial llamada "No Data" que por defaul está deshablitida. Solo dale click derecho y agregar banda para que puedas trabajar con ella.

    Por otro lado, el parámetro "SUBREPORT_DIR" debe apuntar al lugar en el que está el subreporte, no al subreporte mismo. Por ejemplo. su tu reporte está en c:\aplicaciones\reportes\subreportes\subreporte.jasper

    el SUBREPORT_DIR debe ser "c:\aplicaciones\reportes\subreportes", ya que después este se concatenerá al nombre del reporte para llegar a el.

    Espero que esto te ayude.

    Saludos.

    ResponderEliminar
  14. se puede imprimir un reporte con el nombre de usuario quien lo esta imprimiento en ireport o sea del usuario logueado al sistema y quien es la persona q esta imprimiento el reporte??

    ResponderEliminar
  15. Si, si pasas esos datos como parámetros al reporte

    ResponderEliminar
  16. NO, HAY OTRA FORMA QUE NO SEA QUE EL MISMO USUARIO LO INGRESE, SINO QUE DE ACUERDO AL LOGIN CAPTURE ESE DATO?(uSUARIO)

    ResponderEliminar
  17. Hola; No se si te estoy entendiendo mal. Pero lo que se me ocurre es que puedes tener un objeto Usuario (en sesión si es que tienes una aplicación web) y en el reporte tengas un parámetro "usuario". Al momento de generar el reporte podrías recuperar este objeto usuario (o donde sea que estes almacenando el nombre del mismo, tomado en el momento del login) y hacer algo asi:

    parametros.put("usuario", getSession().getAttribute("usuario").getNombre());

    o algo por el estilo. No se si te estoy entendiendo mal.

    Saludos

    ResponderEliminar
  18. mmmm osea que en el reporte pongo el usuario solo en un parametro,y en la aplicacion web tomo este parametro como objeto, es asi? igual gracias por la ayuda

    ResponderEliminar
  19. Si, colocas un parámetro en el reporte como dice aquí:

    http://javatutoriales.blogspot.com/2009/03/creacion-de-reportes-con-jasperrepots-y_30.html

    y pasas el nombre de quien lo está imprimiendo. Creo que es la forma más fácil de hacerlo aunque no se si eso resuelve tu problema

    Saludos.

    ResponderEliminar
  20. BUENO, SERIA UNA FORMA, PERO NO SE SI LA MAS SEGURA, SUPONGO QUE DESDE LA APLICACION TOMARA ESTE PARAMETRO COMO OBJETO Y YA DESDE ALLI SERIA MAS SEGURO, GRACIAS

    ResponderEliminar
  21. hola, tengo un nuevo problema estoy haciendo subreportes, y el subreporte solo sale perfecto, el problema es cuando llamo al subreporte desde el padre me sale el reporte pero con los datos repetidos varias veces osea si en el subreporte llamado x me salia los 5 requerimientos de la fecha 05-02-2009 y 3 requerimientos de la fecha 06-02-2009. Ahora en el reporte padre llamado y me sale como 3 veces los 5 requerimientos de la fecha de 05-02-2009 y asi igual de las demas fechas sea cual sea el rango del parametro fecha, te agradeceria si me ayudas, gracias

    ResponderEliminar
  22. Hola Alex.

    Primero que nada quiero darte las gracias por toda la información que compartes ya que es exelente y en lo personal iluminaste mucho mi mente con todo lo que compartes., estoy seguro que como ami hay muchos mas que tambien le sirvio el tutorial..

    Todo esta muy bien expliado te felicito...

    Yo solo quiero hacerte una pregunta espero puedas iluminar un poco mas mi mente :) Bueno mira mi pregunta es la siguiente:

    Se puede meter una imagen a un reporte pero dinamicamente, osea que esta sea cargada desde una clase con su metodo get..?

    ResponderEliminar
  23. Hola Nick;

    Antes que nada muchas gracias por tus comentarios, pongo mi mayor esfuerzo en que lo que entiendo quede claro y le sirva a la mayor cantidad de personas.

    En cuanto a tu pregunta. Si, es posible cargar una imagen de forma dinámica. Lo que debes hacer es colocar la imagen en iReport, y en la ventana de propiedades (o en la ventana que se abre al momento de colocar la imagen) en vez de poner una ruta en tu disco duro (C:\algo\algo\algo) colocar la url de donde estpa la imagen (http://tuServidor/imagen.jpg). Lo unico que no estoy seguro es con respecto al ancho y el alto de la imagen. Si siempre van a tener el mismo, entonces lo puedes colocar directamente en el iReport. Sino no estoy muy seguro cómo se pueda manejar eso.

    Espero que eso te ayude.

    Saludos

    ResponderEliminar
  24. Hola Alex.

    Muchas gracias por contestar., y tu respuesta claro que si me ayuda y mucho gracias...

    Yo no tengo mucho tiempo con java apenas desarrolle un modulo de un sistema y la verdad me sorprendio mucho el lenguaje.

    Con la información que tu compartiste pretendo generar reportes en el siguiente modulo web del sistema que estoy desarrollando espero lograrlo.. sera en unos EJB utilizando JPA con Toplink (le entendi mas rapido que a hibernate).

    Bueno de nuevo gracias y si en algo te puedo apoyar con gusto lo hare...

    Saludos...

    ResponderEliminar
  25. Alex eres un Master compañero, te saludo desde Guatemala actualmente me solicitaron generar unos reportes en diferentes formatos y creeme q tus tutoriales han sido los mas completos q halle en la intranet... me han servido de mucho... estoy teniendo algun tipo de problema en la generacion de reportes cuando le pasas parametros a la consulta como tal ejemplo:

    SELECT * FROM tipos_formatos WHERE clase LIKE '%$P{condicion}%'

    el parametro $P{condicion} lo tengo declarado de tipo java.lang.String pero cuando me lo pide el prompt le pongo: C-5 y ejecuta no me tira nada y en la consola me pone:

    Print not filled. Try to use an EmptyDataSource...

    lo extraño es que si copio y ejecuto la consulta en mi navigator de oracle si me la hace:

    SELECT * FROM tipos_formatos WHERE clase LIKE '%C-5%'

    es decir q el query es correcto pero no entiendo x que no me hace nada el iReport y me tira ese error, aparte quisiera saber si le puedo pasar una consulta completa ejemplo:

    SELECT * FROM tipos_formatos $P{condicion}

    esto es porque los reportes q necesito generar tienen varios filtros y necesito q si solo usan uno o 2 filtros pues en base a los filtros se genere la condicion para la generacion del reporte ejemplo:

    SELECT * FROM tipos_formatos WHERE clase LIKE '%C-5%' AND fecha='01/01/2009' and alumno='pedro';

    por lo que quisiera poder pasarle el el parametro condicion algo asi:

    $P{condicion} = WHERE clase LIKE '%C-5%' AND fecha='01/01/2009' and alumno='pedro';

    Saludos Maestro y espero puedas ayudarme... ya q me urge un poquito....

    ResponderEliminar
  26. Hola Oscar, muchas gracias por tus comentarios, este blog es para ayudar a todo el que lo necesite ^-^.

    Sobre tus dudas, si, puedes pasar consultas tan complejas como quieras como parametros para que tenga todos los filtros que necesitas y, en teoría, si haces eso, ya no deberías tener el primer problema, sobre el cual por cierto me parece que su consulta debería ser algo así:

    '%" + $P{condicion} + "%'

    o sea, concatenar el valor del parámetro en caso de que pases solo el valor, si lo pasas como en el último ejemplo que pusiste:

    $P{condicion} = WHERE clase LIKE '%C-5%' AND fecha='01/01/2009' and alumno='pedro';

    tambien debería funcionar.

    Prueba a hacer el cambio y si no funciona me dices y vemos cómo resolverlo.

    Saludos

    ResponderEliminar
  27. Muchas gracias por tu pronta respuesta Alex, fijate que modifique lo q me dijiste pero me sigue saliendo el mismo error y no generara nada, probe ejecutar mi reporte desde el JSP y tampoco pero descubri q le pasa una interrogante q sera? mira esto me sale:

    net.sf.jasperreports.engine.JRException: Error preparing statement for executing the report query :
    SELECT * FROM tipos_formatos WHERE clase LIKE '%" + ? + "%'

    lo extraño es q lo estoy declarando igual q como los otros 2 parametros q si funcionan por cierto:

    Map parametros = new HashMap();
    parametros.put("autor", "Juan");
    parametros.put("titulo", "Reporte");
    parametros.put("condicion", "C-5");

    JasperPrint jasperPrint = JasperFillManager.fillReport(reporte, parametros, conexion);

    Salu2 y espero puedas ayudarme.....

    asi fue como coloque el query en el iReport:

    SELECT * FROM tipos_formatos WHERE clase LIKE '%" + $P{condicion} + "%'

    ResponderEliminar
  28. Hola Oscar, pues eso si que está raro. No me imagino por qué puede estar apareciendo ese signo de interrogación. Aparentemente estas pasando todos los parámetros igual.

    ¿Intentaste pasar la consulta haciendo uso de todos los filtros cómo me habias comentado antes?

    ResponderEliminar
  29. Hola Alex fijate q q extraño estuvo, la verdad lo que hice fue hacer mi reporte de nuevo paso a paso e ir compilando cada linea q ponia y listo me funcion por arte de magia jejejejeje.... y si probe solo con una instruccion y luego si pasarle la consulta completa y funciono perfecto. Muchisimas gracias x tu ayuda, como te comente tu tutorial es una guia muy buena y la UNICA comprensible q encontre en el internet. esta mas enfocada a un programador en JAVA pero logre montarlo en un JSP sin necesidad de llamar alguna clase de JAVA. a mis amigos q andaban sufriendo igual q yo les queria dejar mi codigo para q les sirviera pero no me deja ingresar el codigo me dice: Su HTML no es aceptable: PHP, ASP, and other server-side scripting is not allowed.

    Entonces no se como pasarselo Alex hay q habilitar algo para poder dejarles el codigo aqui o adjuntarlo? sino pues con gusto les puedo mandar un mail con el codigo, solo dejen su mail.

    Salu2 y Alex gracias nuevamente....

    ResponderEliminar
  30. Hola Oscar;

    Caso extraño el tuyo, jejeje, pero suele ourrir. Gracias por tus comentarios.

    Sobre lo de poner el código, seguramente no te deja por que estas colocando las etiquetas < y > para colocarlo, ¿no es así?.

    Y pues no te deja ponerlo por que el editor los toma como etiquetas HTML (lo cual no permite introducir en los comentarios).

    Prueba sustituyendo estos caracteres por su versión codificada. Para < usa & lt ; (sin espacios entre ellos), y para > usa & gt ; igual sin espacios.

    Saludos

    ResponderEliminar
  31. Listo aqui va:

    <%@ page contentType="text/html; charset=utf-8" language="java" import="java.sql.*" errorPage="" %>
    <%@ page import="java.io.*" %>
    <%@ page import="java.net.*" %>
    <%@ page import="javax.servlet.*" %>
    <%@ page import="javax.servlet.http.*" %>
    <%@ page import="java.sql.Connection" %>
    <%@ page import="java.sql.DriverManager" %>
    <%@ page import="net.sf.jasperreports.engine.JRExporter" %>
    <%@ page import="net.sf.jasperreports.engine.JRExporterParameter" %>
    <%@ page import="net.sf.jasperreports.engine.JasperFillManager" %>
    <%@ page import="net.sf.jasperreports.engine.JasperPrint" %>
    <%@ page import="net.sf.jasperreports.engine.JasperReport" %>
    <%@ page import="net.sf.jasperreports.engine.export.JRPdfExporter" %>
    <%@ page import="net.sf.jasperreports.engine.export.*" %>
    <%@ page import="net.sf.jasperreports.engine.util.JRLoader" %>
    <%@ page import="net.sf.jasperreports.extensions.ExtensionsEnvironment" %>
    <%@ page import="org.apache.log4j.Logger" %>
    <%@ page import="org.apache.log4j.PropertyConfigurator" %>
    <%@ page import="java.sql.*" %>
    <%@ page import="java.util.*" %>
    <%

    String reporttype = request.getParameter("tipo");
    ServletOutputStream outs = response.getOutputStream();
    Class.forName("oracle.jdbc.driver.OracleDriver");
    Connection conexion = DriverManager.getConnection("jdbc:oracle:thin:@192.198.10.10:1521:XE", "miusuario", "micontraseña");
    JasperReport reporte = (JasperReport) JRLoader.loadObject(application.getRealPath("tpl_reports/reporte1.jasper"));

    Map parametros = new HashMap();
    parametros.put("autor", "Juan");
    parametros.put("titulo", "Reporte");
    parametros.put("query", "WHERE activo=1 ORDER BY id");

    JasperPrint jasperPrint = JasperFillManager.fillReport(reporte, parametros, conexion);

    JRExporter exporter = null;
    if( "pdf".equalsIgnoreCase(reporttype) )
    {
    response.setContentType("application/pdf");
    response.setHeader("Content-Disposition","attachment; filename=\"file.pdf\";");
    exporter = new JRPdfExporter();
    }
    else if( "rtf".equalsIgnoreCase(reporttype) )
    {
    response.setContentType("application/rtf");
    response.setHeader("Content-Disposition", "inline; filename=\"file.rtf\"");

    exporter = new JRRtfExporter();
    }
    else if( "html".equalsIgnoreCase(reporttype) )
    {
    exporter = new JRHtmlExporter();
    }
    else if( "xls".equalsIgnoreCase(reporttype) )
    {
    response.setContentType("application/xls");
    response.setHeader("Content-Disposition", "inline; filename=\"file.xls\"");

    exporter = new JRXlsExporter();
    }
    else if( "csv".equalsIgnoreCase(reporttype) )
    {
    response.setContentType("application/csv");
    response.setHeader("Content-Disposition", "inline; filename=\"file.csv\"");

    exporter = new JRCsvExporter();
    }else{
    response.setContentType("application/pdf");
    response.setHeader("Content-Disposition","attachment; filename=\"file.pdf\";");
    exporter = new JRPdfExporter();
    }

    exporter.setParameter(JRExporterParameter.JASPER_PRINT, jasperPrint);
    exporter.setParameter(JRExporterParameter.OUTPUT_STREAM, outs);
    exporter.exportReport();
    %>

    Cabe mencionar q yo estoy usando una conexion ORACLE y mi reporte1.jasper esta en una carpeta llamada tp_reports en el mismo directorio q mi JSP...

    ResponderEliminar
  32. Oscar una pregunta. ¿Por casaualidad no obtienes una excepción de tipo IllegalStateException en tu consola?

    Explico el por qué de mi pregunta:

    Se supone que las JSPs están pensadas solo para regresar al cliente información textual (caracteres). Por lo que al tratar de obtener un ServletOutputStream usadno response.getOutputStream() para regresar un flujo de datos (en este caso el reporte) no debería dejarte y lanzar la excepción por la que pregunto.

    Y un cometario sobre esté código:

    Las "buenas prácticas" de programación "dicen" que no deberíamos colocar código Java (scriptles) dentro de una JSP, ya que las JSP están pensadas para la vista mientras que el código Java es el que ejecuta la lógica del negocio.

    Aceptan los scriptlets por compatibilidad con las primeras versiones dela especificación.

    Ahora, en vez de colocar código Java usando scriptles, como tu lo has hecho, se deben usar custom tags, de las cuales hablaré en un totorial posterior.

    Saludos

    ResponderEliminar
  33. pues fijate que no Alex el unico error q me ha tirado la consola es el siguiente:

    09/07/31 11:09:19 log4j:WARN No appenders could be found for logger (net.sf.jasperreports.extensions.ExtensionsEnvironment). 09/07/31 11:09:19 log4j:WARN Please initialize the log4j system properly.

    pero hasta alli, x lo demas di me lanza el cuadro de dialogo para abrir y guardar o incluso modificando un poco el codigo te deja verlo en la misma pantalla como un iframe....

    y se ve interesante lo q dices de los custom tags, me gustaria poder implementar mi codigo de la mejor manera posible, te explico yo aprendi JSP a las malas y entonces no estoy muy familiarizado con el, entonces creeme q cuando logro q me corra algo doy de brincos x lograrlo pero despues trato la manera de ir simplificando y depurando mi codigo y pues si los custom tags de los q hablas me ayudan, con gusto asistire a ese tutorial q haras jejejejeje.....

    ResponderEliminar
  34. Alex una consulta es posible q al presentar los datos cada fila tenga un fondo intercalado? ejemplo la primera gris, la segunda blanco,la tercera gris y asi consecutivamente....

    ResponderEliminar
  35. Hola Oscar;

    Si, hay una forma, pero digamos que es un pequeño truquito.

    Pudes poner de fonde de la fila un rectangulo (que tomas de la paleta de componentes). Pones el rectangulo del color que queres que estén, por ejemplo, las filas pares.

    Cuando ya tengas el rectangulo del color que quieres, le das click para que aparezcan sus propiedades en el panel de propiedades. Ahi busca, dentro del grupo Propiedades (el primer grupo), la propiedad "Print When Expression", que en mi versión de iReport es la penultima. Ahi das click en el botón marcado con tres puntos y te abrirá una nueva ventana. En la ventana escribes lo siguiente:

    new Boolean($V{PAGE_COUNT}.intValue() % 2 == 0)

    O sea, la barra solo se mostrará con los elementos pares ($V{PAGE_COUNT} es una variable que nos dice el número de fila actual).

    Asegurate que mandar el rectangulo hasta el fondo para que no tape nada de la información que quieres mostrar.

    Si quieres que las filas pares se muestren con el color de fondo (por defaul blanco) ya tienes todo terminado. Si quieres que se muestren de otro color solo tienes que agregar otra barra del color que quiras y hacer que se muestre con los elementos impares.

    Ojala y esto te ayude.

    Saludos

    ResponderEliminar
  36. Mi amigo Alex, mira tengo un pequeño problema en el cual quiza me puedas ayudar, fijate que mis reportes se estan generando nitidos, incluso cuando le pones fondos a las celdas y todos... pero tengo el inconveniente de que en PDF me sale nitido el reporte pero cuando exporto a excel, pareciera q no estan los titulos de las columnas o los totales pero es x que el color de la letra es blanco pero lo q no aparece es el rectangulo gris q le puse de fondo x eso no se ve... q puedo hacer? x q en PDF si sale el fondo gris pero en excel no =o(

    tambien te queria consultar fijate q desde ireports cuando le envias en el parametro ejemplo: "Reporte de Comunicación" nitido en el reporte aparece la tilde pero cuando ese parametro lo envio desde el JSP me tira caracteres raros en las ñ o tildes.

    Espero puedas darme una mano salu2

    ResponderEliminar
  37. Hola Oscar;

    Lo del color de fondo puede ser por que en PDF lo coloa como si fuera una imagen (no es exactamente así, pero creo que es la forma más sencilla de verlo). O en otras palabras: el PDF tiene la capacidad de mostrar elementos más complejos que el Excell. Por eso, en excell, un poco mas limitado en este sentido, simplmente ignora el elemento (pensaría que lo colocaría como color de fondo pero aparentemente no). ¿Has probado establecer el background del campo del color que quieres?

    Con respecto a los caracteres especiales (ñ, acentos, y algunos otros signos) puede deberse al encodign de tu proyecto web, y de tus páginas. Normalmente recomiendan que uses UTF-8, sin embargo a mi esta codificación no siempre me da los resultados esperados, así que en lo personal siempre uso el ISO-8859-1. Puedes probar verificando esto y si no lo revisamos con más calma.

    Saludos

    ResponderEliminar
  38. Alex mi buen amigo, mira necesito tu ayuda en algo crucial para mi proyecto... te cuento que todos los reportes de mi proyecto se generan perfectamente gracias a tu ayuda. sin embargo se me presento una solicitud y no se si es posible con JasperReports... la solicitud se trata de mandar el reporte directamente a impresora, es decir que no genere un pdf o excel si no que al darle click en el reporte de una vez lo mande a imprimir a la impresora sin presentarse en pantalla o generarse otro archivo. o bien que se genere el archivo lo imprima y se cierre en el momento. espero puedas ayudarme amigo.

    ResponderEliminar
  39. Hola Oscar;

    Como siempre gracias por tus comentarios.

    Si, existe una forma de hacer esto que preguntas. Mandar a imprimir directamente tu reporte sin generar un archivo intermedio:

    JasperPrintManager.printReport(jasperPrint, true);

    donde el primer parámetro es el jasperPrint y el segundo te indica si se muestra el cuadro de diálogo de impresión o no.

    Espero que esto sea lo que estas buscando y que ayude a terminar tu proyecto.

    Saludos

    ResponderEliminar
  40. Ok mi amigo pero dime al agregar esto sigo mandando a llamar mi reporte igual? ya que las instrucciones van al exporter.exportReport(); pero para agregar esto los tengo que quitar y solo dejar eso o como seria?

    ResponderEliminar
  41. Hola Oscar;

    Así es, usas

    JasperPrintManager.printReport(jasperPrint, true);

    en vez de

    JRExporter exporter = new JRPdfExporter();
    exporter.setParameter(JRExporterParameter.JASPER_PRINT, exporter.exportReport();

    Y el jasperPrint que le pasas como parámetro es el mismo que le pasabas a

    exporter.setParameter

    O sea, en vez de generar tu reporte como un archivo ahora lo mandas directo a imprimir.

    Saludos.

    ResponderEliminar
  42. Hola Alex..
    Te escribo desde Paraguay.. muy buena tu ayuda.. excelente, porque a los tutoriales q encontramos siempre l faltan algo y p/los q recien comenzamos s nos complica...

    tengo una duda q no he encontrado en ningún material (ya q estoy aprendiendo ireports con materiales d internet).. tengo q pasar x parámetro a un subreporte un dato de tipo numérico, específicamente en mi base de datos postgres está como numeric, pero tnego entendido q los parámetros solo pueden ser String?

    Muchas gracias x compartir tus conocimientos..

    ResponderEliminar
  43. Hola patito;

    Muchas gracias por tus comentarios.

    Sobre lo de los parámetros, estos pueden ser de distintos tipos, entre ellos números, la única restricción es que deben ser objetos, ya que los pasas como parámetro en un Map (que solo acepta objetos). Aunque a apartir de la versión 5 de Java existe algo llamado autoboxing y unboxing que permite convertir los primitivos (enteros, caracteres, flotentes, booleanos) en objetos.

    JasperReports soporta muchos tipos de objetos como parámetros, entre ellos java.lang.Integer y java.lang.Float que representan a los int y float respectivamente, si quieres pasar un número solo tienes que declarar tu parámetro como uno de estos tipos.

    Si quieres pasar ese mismo parámetro a un subreporte solo tienes que pasarlo desde el reporte maestro usando la propiedad "Parameters" de la sección "Subreport properties" de las propiedades del subreporte, y declarar el parámetro indicado dentro del subreporte.

    Saludos

    ResponderEliminar
  44. muchas gracias Alex.. lo probaré y te aviso..

    gracias x compartir tus conocimientos!!

    Saludos

    ResponderEliminar
  45. Muhas gracias a todos por sus comentarios. Este tutorial ha superado los 40 comentarios, así que aplicaré la regla de los 40 comentarios y desactivaré los comentarios. Si tienen alguna duda pueden enviarla a:

    programadorjavablog@gmail.com

    Saludos

    ResponderEliminar