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