Tag Archive: netbeans



Parte 1Parte 2Parte 3Parte 4Parte 5

Olá a todos! Nesta quinta e última parte do nosso tutorial, iremos aprender a como usar os nossos relatórios em um projeto Web. Esse parte do tutorial vai ser rápida, pois já temos praticamente tudo o que precisamos. Primeiro gostaria de pedir para quem não está acompanhando o tutorial, que baixe o projeto finalizado na Parte 4 clicando aqui. Iremos criar um novo projeto no NetBeans, só que agora do tipo Web e vamos configurá-lo com base no que já fizemos no nosso projeto original. Vamos lá então!

No NetBeans, vá em File -> New Project. No assistente de criação de projetos, escolha “Java Web” na lista de categorias e na lista de tipos de projetos, escolha “Web Application”. Clique em “Next”. Em “Project Name” dê o nome do projeto. Eu sugiro “TutorialRelatoriosWeb” (sem as aspas). Em “Project Location” escolha onde o projeto vai ser salvo. Eu vou deixar na mesma pasta do projeto original. Marque a opção “Use Dedicated Folder for Storing Libraries” e deixe o valor padrão (.\lib). Se quiser, marque a opção “Set as Main Project”. Clique em “Next”.

No próximo passo, onde é configurado o servidor que a aplicação vai ser executada, eu vou deixar o Tomcat escolhido. Se vocês preferirem usar outro servidor, não tem problema, basta selecioná-lo na lista. Deixe desmarcada a opção “Use dedicated library folder for server JAR files”. Em “JavaEE version”, deixe escolhida a versão padrão, pois não precisamos nos preocupar com isso no nosso projeto de testes. No meu caso, ficou selecionado Java EE 5. Em “Context Path” deixe o valor sugerido. No meu caso, é “/TutorialRelatoriosWeb”. Como não vamos usar nenhum framework MVC, você já pode clicar em “Next”. Feito isso, o projeto será criado e será aberto no NetBeans.

Com o projeto criado, acesse suas propriedades clicando com botão direito na raiz do projeto e escolhendo a opção “Properties”, que é a última da lista. Em “Categories” procure pelo item “Run” e selecione-o. Desmarque a opção “Deploy on Save” para evitar que seja feito um deploy a cada vez que salvarmos algo do nosso projeto. Já que estamos aqui, vamos aproveitar para configurar as bibiliotecas. Não vou colocar as figuras desse processo, pois já foi explicado nas primeiras partes do tutorial.

Selecione o item “Libraries” na lista de categorias e clique no botão “Add Library”. Primeiro vamos importar o driver do MySQL que vamos utilizar. Na janela que foi aberta, clique no botão “Import”. Procure pela biblioteca “MySQL JDBC Driver”, selecione-a e clique no botão “Import Library”. A biblioteca vai ser importada e aparecerá na janela anterior, mas ela ainda não foi inserida no projeto. Antes de a inserirmos, vamos criar a biblioteca do JasperReports. Para isso, clique no botão “Create…”. Em “Library Name” entre com o valor “JasperReports-3.7.5” (sem as aspas) e clique em “OK”. Note que eu vou manter a versão do JasperReports utilizado nas partes anteriores do tutorial, sendo assim sua versão pode variar dependendo de quando você estiver seguindo esse tutorial, visto que novas versões do JasperReports são lançadas frequentemente. Se a versão que você estiver usando for mais nova, vamos dizer, 3.7.6, defina um nome da biblioteca que reflita a versão utilizada, ou seja “JasperReports-3.7.6”.

Feito isso, a janela “Customize Library” será exibida. Clique no botão “Add JAR/Folder”. Como já fizemos isso uma vez nas partes anteriores do tutorial, o projeto original já tem essa biblioteca configurada com os JARs necessários. Então vamos usá-los. Procure pela pasta do projeto original (TutorialRelatorios). Dentro dela, entre na pasta “lib”. Dentro da pasta “lib”, existirá uma pasta chamada “JasperReports-3.7.5”. O número da versão pode variar de acordo com a sua versão. Entre nela, selecione todos os JARs e clique no botão “Add JAR/Folder”. O NetBeans vai perguntar se você quer criar um diretório com o nome da biblioteca dentro da pasta “lib” do projeto atual (TutorialRelatoriosWeb). Diga que sim e os JARs serão listados na janela “Customize Library”. Clique em OK.

Novamente a janela “Add Library” será exibida, agora contendo a também a biblioteca do JasperReports que acabamos de configurar. Selecione tanto a biblioteca do JasperReports quanto a biblioteca do MySQL e clique no botão “Add Library”. Agora as bibliotecas serão adicionadas no projeto.

Ainda na janela de propriedades, procure a categoria “Sources” (primeira) e selecione-a. Vamos criar agora o diretório onde guardaremos os nossos fontes do relatório. Na tabela “Source Package Folders”, clique no botão “Add Folder…”. Serão exibidas os diretórios contidos no projeto. Crie então uma nova pasta, chamada “relatorios” (sem acentos e sem aspas), selecione-a e clique em “Open”. A pasta será referenciada na tabela. Clique duas vezes na célula correspondente à coluna “Label” da pasta, preencha com “Relatórios” (sem as aspas) e tecle <ENTER> para trocar o “Label” da pasta. Esse “Label” vai ser utilizado para mostrar a pasta de relatórios na árvore do projeto.

Estamos quase lá. Por fim, ainda na janela de propriedades, selecione a categoria “Packaging”, dentro da categoria “Build”. Em “Exclude From WAR File”, adicione uma vírgula e o valor “**./*.jrxml” (sem as aspas). Isso fará com que os nossos arquivos fonte de relatórios não sejam empacotados no arquivo WAR. O valor final do campo deve ficar assim: “**/*.java,**/*.form, **/*.jrxml” (sem as aspas). Pronto! Clique em OK na janela de propriedades e aguarde o NetBeans escanear as novas configurações do projeto.

Agora, abra o projeto anterior, vá na pasta de “Relatórios” dele, copie o arquivo “ClientesPorNome.jrxml” e cole na pasta “Relatórios” do novo projeto. Ainda não feche o projeto anterior. No projeto atual, abra o relatório que foi copiado e faça um Preview dele para ver se está tudo ok. Lembre-se que o datasource correto (Sakila – JDBC) tem que estar selecionado lá na barra de ferramentas do NetBeans. Se o relatório for renderizado e os dados aparecerem é porque está tudo ok.

Até agora nenhuma novidade. Criamos e configuramos o novo projeto e copiamos e testamos o arquivo de relatório que já fizemos para o novo projeto. Agora vamos às novidades.

Vamos criar um Servlet que vai ser responsável em pegar os possíveis dados do request que serão usados como parâmetros para os relatórios e invocar o JasperReports para criar o relatório. O funcionamento é parecido com o que fizemos no nosso programa desktop, entretanto agora não iremos mais criar um JFrame para exibir os relatórios, pois estamos usando um navegador não é mesmo? O que o nosso Servlet vai fazer é criar diretamente um arquivo .pdf do relatório e mandar exibir no navegador – caso haja algum plugin para leitura de PDF instalado – ou então o navegador vai sugerir que você faça o download do arquivo gerado.

Em “Source Packages”, crie três pacotes: “tutorialrelatoriosweb.jdbc”, “tutorialrelatoriosweb.servlets” e “tutorialrelatoriosweb.util” (todos sem as aspas). Vá no projeto anterior, no pacote “tutorialrelatorios.jdbc”, copie a classe ConnectionFactory e cole no pacote “tutorialrelatoriosweb.jdbc” do novo projeto. Se quiser, feche o projeto anterior. Agora no novo projeto, vá na pasta “tutorialrelatoriosweb.util” e crie uma classe com o nome de “ReportUtils” (sem as aspas). Ainda não vamos implementar nada nela. Feito isso, clique com o botão direito no pacote “tutorialrelatoriosweb.servlets”, vá em New -> Servlet. Se a opção Servlet não estiver sendo exibida, selecione “Other” e em “Categories” selecione “Web” e em “File Types” escolha “Servlet” e clique em “Next”.

O assistente para criar um novo Servlet será exibido. Em “Class Name” preencha com “ReportServlet” (sem as aspas) e clique em “Next”. Tanto “Servlet Name” quanto “URL Pattern” vão ser deixados da forma que o NetBeans sugeriu, ou seja, “ReportServlet” e “/ReportServlet” respectivamente. Clique em “Finish”.

O NetBeans vai gerar por padrão uma implementação padrão do método processRequest(…). Faça com que o seu fique assim:

protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

    OutputStream out = null;

    // aqui nós geramos o relatório...

    if ( out != null ) {
        out.close();
    }

}

Note que ainda não implementamos o método processRequest(…). Da mesma forma que fizemos no projeto anterior, vamos agora criar um método utilitário na classe ReportUtils que vai ser responsável em gerar o relatório. Segue a implementação comentada da classe ReportUtils.

tutorialrelatoriosweb.util.ReportUtils.java

package tutorialrelatoriosweb.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Connection;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JRExporter;
import net.sf.jasperreports.engine.JRExporterParameter;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.export.JRPdfExporter;

/**
 * Classe com métodos utilitários para gerar relatórios.
 *
 * @author David Buzatto
 */
public class ReportUtils {

    /**
     * Gera o relatório em PDF.
     *
     * @param inputStream InputStream que contém o relatório.
     * @param parametros Parâmetros utilizados pelo relatório.
     * @param conexao Conexão utilizada para a execução da query.
     * @param response HttpServletResponse que será usado como base para
     * gerar o relatório.
     * @return O OutputStream do HttpServletResponse passado.
     * @throws JRException Caso ocorra algum problema na geração do relatório.
     * @throws IOException Caso ocorra algum problema na obtenção do
     * OutputStream.
     */
    public static OutputStream createPDFReport(
            InputStream inputStream,
            Map<String, Object> parametros,
            Connection conexao,
            HttpServletResponse response ) throws JRException, IOException {

        // configura o content type do response
        response.setContentType( "application/pdf" );

        // obtém o OutputStream para escrever o relatório
        OutputStream out = response.getOutputStream();

        /*
         * Cria um JasperPrint, que é a versão preenchida do relatório,
         * usando uma conexão.
         */
        JasperPrint jasperPrint = JasperFillManager.fillReport(
                inputStream, parametros, conexao );

        // Exporta em PDF, escrevendo os dados no output stream do response.
        JRExporter exporter = new JRPdfExporter();
        exporter.setParameter( JRExporterParameter.JASPER_PRINT,
                jasperPrint );
        exporter.setParameter( JRExporterParameter.OUTPUT_STREAM,
                out );

        // gera o relatório
        exporter.exportReport();

        // retorna o OutputStream
        return out;

    }

}

Como vocês podem perceber, o método createPDFReport(…) da classe ReportUtils gera um relatório em PDF e o escreve no OutputStream do response do Servlet. Caso vocês queiram outros tipos de exportação, basta mudar o tipo do exportador. Clicando aqui, vocês podem ver a documentação da interface JRExporter e de todas as classes que a implementam. Note que ao mudar o exportador, você também precisa alterar o content type do response para refletir o tipo de arquivo gerado.

Vamos agora atualizar o nosso Servlet. O método processRequest(…) vai ficar assim:

protected void processRequest(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {

    OutputStream out = null;

    // obtém o relatório compilado
    InputStream inputStream = getClass().getResourceAsStream( "/ClientesPorNome.jasper" );

    // preenche o mapa de parâmetros
    Map<String, Object> parametros = new HashMap<String, Object>();
    parametros.put( "primeiroNome", "D%" );

    try {

        // gera o relatório e atribui o OutputStream gerado
        out = ReportUtils.createPDFReport( inputStream, parametros,
                ConnectionFactory.getSakilaConnection(), response );

    } catch ( SQLException exc ) {
        exc.printStackTrace();
    } catch ( JRException exc ) {
        exc.printStackTrace();
    } finally {

        // se não aconteceu nenhum problema, fecha o output stream
        if ( out != null ) {
            out.close();
        }

    }

}

Com isso feito, rode a aplicação e aponte o navegador para o endereço “http://localhost:8084/TutorialRelatoriosWeb/ReportServlet&#8221; (sem as aspas). Por padrão o Tomcat usado em desenvolvimento no NetBeans roda na porta 8084. Certifque-se que o seu está rodando nesta porta e se não estiver, use a porta correta. Se você seguiu corretamente o tutorial até aqui, será gerado então um .pdf com todos os clientes que tenham o primeiro nome iniciando com a letra “D” (veja o parâmetro “primeiroNome” passado para o relatório).

Note que agora, para passar parâmetros para o relatório, basta você obter os parâmetros pelo request e então adicionar os parâmetros desejados no mapa de parâmetros do relatório. Se quiser outros tipos de exportação, por exemplo, para Excel, basta criar um novo método na classe ReportUtils, que configura o content type apropriado (“application/ms-excel”) e o exportador necessário (JRXlsExporter ou JRXlsxExporter). Tanto a passagem de parâmetros quanto a geração de outros tipos de arquivos ficam como exercício para vocês. O Servlet ReportServlet pode ser generalizado também, permitindo que o nome do arquivo do relatório a ser gerao seja passado via request :).

Com isso terminamos nosso tutorial sobre relatórios em Java! Espero que tenham gostado! Para baixar o projeto criado nesta parte, clique aqui. Nos próximos tutoriais iremos aprender a usar a biblioteca JavaScript jQuery, que é extremamente útil e facilita muito a nossa vida.

Então é isso pessoal! Grande abraço a todos! Até a próxima 😉

Parte 1Parte 2Parte 3Parte 4Parte 5


Parte 1Parte 2Parte 3Parte 4Parte 5

Olá a todos! Finalmente, depois de um bom tempo sem postar, vamos à quarta parte do tutorial sobre relatórios! Nesta parte iremos aprender a usar outros tipos de datasources para obtermos os dados que serão apresentados nos nossos relatórios. Como de costume, vou explicar como fazer para utilizar a funcionalidade, permitindo que vocês consigam caminhar sozinhos posteriormente.

Nas três partes anteriores, a nossa fonte de dados era uma conexão JDBC que era usada para executar uma query SQL. A partir do resultado da query, o JasperReports preenchia o relatório para nós. Imagine agora a seguinte situação: Eu tenho uma lista de objetos do tipo “Cliente” e quero exibir essa lista de clientes em um relatório. Note que eu já tenho a lista, que foi obtida de alguma forma, ou seja, usando um DAO, ou executando uma query pelo Hibernate.

Para conseguir fazer isso, utilizamos outros tipos de datasources. Se você está seguindo o tutorial desde o início, seu projeto do NetBeans deve estar preparado para iniciarmos. Caso não esteja seguindo, faça o download do projeto clicando aqui. Abra o projeto, e procure pela classe “ReportUtils” no pacote “tutorialrelatorios.util”. O código dela deve estar assim:

tutorialrelatorios.util.ReportUtils.java

package tutorialrelatorios.util;

import java.awt.BorderLayout;
import java.io.InputStream;
import java.sql.Connection;
import java.util.Map;
import javax.swing.JFrame;
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.swing.JRViewer;

/**
 * Classe com métodos utilitários para executar e abrir relatórios.
 *
 * @author David Buzatto
 */
public class ReportUtils {

    /**
     * Abre um relatório usando uma conexão como datasource.
     *
     * @param titulo Título usado na janela do relatório.
     * @param inputStream InputStream que contém o relatório.
     * @param parametros Parâmetros utilizados pelo relatório.
     * @param conexao Conexão utilizada para a execução da query.
     * @throws JRException Caso ocorra algum problema na execução do relatório
     */
    public static void openReport(
            String titulo,
            InputStream inputStream,
            Map parametros,
            Connection conexao ) throws JRException {

        /*
         * Cria um JasperPrint, que é a versão preenchida do relatório,
         * usando uma conexão.
         */
        JasperPrint print = JasperFillManager.fillReport(
                inputStream, parametros, conexao );

        // abre o JasperPrint em um JFrame
        viewReportFrame( titulo, print );

    }

    /**
     * Abre um relatório usando um datasource genérico.
     *
     * @param titulo Título usado na janela do relatório.
     * @param inputStream InputStream que contém o relatório.
     * @param parametros Parâmetros utilizados pelo relatório.
     * @param dataSource Datasource a ser utilizado pelo relatório.
     * @throws JRException Caso ocorra algum problema na execução do relatório
     */
    public static void openReport(
            String titulo,
            InputStream inputStream,
            Map parametros,
            JRDataSource dataSource ) throws JRException {

        /*
         * Cria um JasperPrint, que é a versão preenchida do relatório,
         * usando um datasource genérico.
         */
        JasperPrint print = JasperFillManager.fillReport(
                inputStream, parametros, dataSource );

        // abre o JasperPrint em um JFrame
        viewReportFrame( titulo, print );

    }

    /**
     * Cria um JFrame para exibir o relatório representado pelo JasperPrint.
     *
     * @param titulo Título do JFrame.
     * @param print JasperPrint do relatório.
     */
    private static void viewReportFrame( String titulo, JasperPrint print ) {

        /*
         * Cria um JRViewer para exibir o relatório.
         * Um JRViewer é uma JPanel.
         */
        JRViewer viewer = new JRViewer( print );

        // cria o JFrame
        JFrame frameRelatorio = new JFrame( titulo );

        // adiciona o JRViewer no JFrame
        frameRelatorio.add( viewer, BorderLayout.CENTER );

        // configura o tamanho padrão do JFrame
        frameRelatorio.setSize( 500, 500 );

        // maximiza o JFrame para ocupar a tela toda.
        frameRelatorio.setExtendedState( JFrame.MAXIMIZED_BOTH );

        // configura a operação padrão quando o JFrame for fechado.
        frameRelatorio.setDefaultCloseOperation( JFrame.DISPOSE_ON_CLOSE );

        // exibe o JFrame
        frameRelatorio.setVisible( true );

    }

}

Note que nessa classe existem dois métodos chamados “openReport(…)”. O primeiro deles é o que utilizamos até agora, que recebe uma String que vai ser usada como título do JFrame utilizado como container do relatório, um InputStream que vai ser usado para obtermos o arquivo .jasper, um Map que contém os parâmetros passados para o relatório e, por fim, uma Connection para a base de dados que usamos na query do relatório. Note que o segundo método “openReport(…)” tem praticamente a mesma assinatura do primeiro, mudando apenas o último parâmetro, que nessa versão do método é um JRDataSource. A implementação do método é igual ao outro, com a diferença de ser passado o JRDataSource para o JasperFillManager.fillReport(…) ao invés da Connection.

O JRDataSource é uma interface que define o contrato que os datasources devem seguir para que o JasperReports possa usar os dados contidos neles dentro do relatório. A documentação da interface JRDataSource pode ser vista clicando aqui. Ao analisar a documentação, vocês vão ver que existem diversas implementações da interface JRDataSource. Nós vamos focar em duas:

  • JRBeanCollectionDataSource: Utilizada para usar coleções como fonte de dados, ou seja, qualquer classe que implemente – ou interface que estenda – a interface java.util.Collection. Por exemplo, um ArrayList, que implementa a interface List que por sua vez estende a interface Collection. A documentação deste tipo de datasource pode ser vista clicando aqui;
  • JRBeanArrayCollectionDataSource: Utilizado para usar arrays de objetos como fonte de dados. A documentação deste tipo de datasource pode ser vista clicando aqui.

Legal não é? Então, além da conexão JDBC, nós podemos utilizar vários outros tipos de datasources. Vou o primeiro deles, e depois a utilização do segundo vai ficar como exercício. Não vou explicar cada um dos tipos existentes, pois o funcionamento deles é sempre muito parecido. Se você precisar de outro tipo de datasource, basta ler a documentação.

A utilização dos datasources não é complicada, bastando você criar um objeto do tipo do datasource desejado e passar o parâmetro que ele espera, entretanto precisamos alterar uma configuração no iReport para indicar ao compilador onde estão as classes que vamos utilizar para instanciar os objetos e enviar no datasource. Antes de fazer isso, vamos criar uma classe no nosso projeto, sendo que essa classe será utilizada como a nossa entidade. Nós vamos instanciar objetos dessa classe manualmente, mas em um caso real, esses objetos virão de algum outro lugar, como uma query do Hibernate como eu já mencionei. Vamos lá então!

Clique com o botão direito no pacote “tutorialrelatorios”, escolha New -> Java Package. Se esta opção não estiver sendo exibida, clique em Other, escolha a categoria Java e em File Type escolha Java Package. Com o assistente aberto, insira “tutorialrelatorios.entidades” (sem as aspas) em Package Name e clique em Finish. O pacote será criado. Clique com o botão direito neste pacote, escolha New -> Java Class. Preencha o campo Class Name com “Cliente” (sem as aspas) e clique em Finish. A classe será gerada e será aberta no editor. Crie os campos id (privado, Long), nome (privado, String) e sobrenome (privado, String). Com os campos criados, gere os gets e os sets. Segue o código da classe Cliente.

tutorialrelatorios.entidades.Cliente.java

package tutorialrelatorios.entidades;

/**
 * Representa um Cliente.
 *
 * @author David Buzatto
 */
public class Cliente {

    private Long id;
    private String nome;
    private String sobrenome;

    public Long getId() {
        return id;
    }

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

    public String getNome() {
        return nome;
    }

    public void setNome( String nome ) {
        this.nome = nome;
    }

    public String getSobrenome() {
        return sobrenome;
    }

    public void setSobrenome( String sobrenome ) {
        this.sobrenome = sobrenome;
    }

}

Agora, antes de partirmos para o nosso relatório, precisamos configurar o iReport para enxergar as classes do nosso projeto, permitindo assim que possamos utilizar nossas entidades no relatório. Antes disso, dê um build no projeto (botão direito no projeto, Build, ou Clean and Build), para que o diretório de build seja gerado, ou seja, o diretório que contém os arquivos compilados no nosso projeto. Com o build feito, entre no menu Tools e vá em Options. Clique no botão do iReport e procure pela guia Classpath. Com a guia aberta, clique no botão Add Folder. Ao clicar no botão, será exibido um diálogo para você procurar a pasta desejada. Nós precisamos encontrar a pasta build/classes do nosso projeto. No meu caso, eu estou salvando o projeto em “C:\Users\David\Documents\Blog\tutoriais”, então o build/classes está localizado em “C:\Users\David\Documents\Blog\tutoriais\TutorialRelatorios\build\classes”. Procure então pelo o seu build/classes, selecione o diretório e clique em Open. O editor de opções da IDE deve ficar assim:

Figura 1

Editando o Classpath do iReport

Com isso feito, o iReport vai conseguir enxergar as classes do nosso projeto, além de permitir que objetos de nossas classes possam ser utilizadas dentro do relatório. Vamos criar nosso relatório então? Clique então com o botão direito no <default package> da nossa pasta de definições de relatórios, escolha New -> Empty Report. Como já temos um relatório chamado Clientes (que usa uma query lembram?), vamos dar o nome nesse relatório de ClientesCollectionDS, pois vamos utilizar o datasource do tipo JRBeanCollectionDataSource. Assim que clicar em Finish no assistente de criação de relatórios, o arquivo ClientesCollectionDS.jrxml será gerado e será aberto no editor. Para simplificar, remova todas as bandas, exceto a Title, a Column Header e a Detail. Na banda Title crie um campo de texto estático e defina o título do relatório. O meu ficou assim:

Figura 2

Layout Inicial do Relatório de Clientes usando Collection DS

Agora chegou a parte onde vamos obter os atributos da nossa entidade Cliente. Clique no botão usado para editar a query do relatório e acesse a guia “JavaBean Datasource”. Note que não vamos criar uma query. Com a aba aberta, preencha o campo “Class name” com  “tutorialrelatorios.entidades.Cliente” (sem as aspas) e clique no botão Read Attributes. Vão ser lidos 4 atributos: class, id, nome e sobrenome. Selecione todos, exceto o class e clique em  “Add selected field(s)”. Ao fazer isso, os campos serão inseridos na tabela de campos do editor. Veja a Figura abaixo.

Figura 3

Carregando Campos de Uma Classe

Perceba que você precisa inserir o nome completo da classe (pacote + nome da classe) para que o iReport encontre a classe e a possa utilizar no relatório. Com isso feito, clique em OK. Os campos serão criados no Report Inspector da mesma forma que seriam criados caso estivéssemos usando uma query tradicional. Agora que os campos estão criados, basta vocês montarem o relatório, arrastando os campos para a banda Detail e editando o título de cada um deles. O meu ficou assim:

Figura 4

Layout Final do Relatório de Clientes usando Collection DS

Note que se vocês tentarem dar um “Preview” no relatório, vocês serão avisados que o relatório não contém páginas, justamente porque não existem dados para serem exibidos. É possível criar um datasource para ser usado no preview do relatório, mas isso eu vou deixar como exercício para vocês caso vocês queiram descobrir como faz :).

Compilem o relatório e vamos agora modificar o método main da classe Main para chamar o relatório criado, passando os dados para o relatório. Abra a classe Main do projeto e copiem o método abrirRelatorioClientes. Mudem o nome do método copiado para abrirRelatorioClientesDS. Agora, como exercício antes de ver o código pronto, tentem chamar o novo relatório, criando um JRBeanCollectionDataSource e passando para o método openReport. Conseguiram? Espero que sim! Segue o código completo da classe Main.

tutorialrelatorios.Main.java

package tutorialrelatorios;

import java.io.InputStream;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import net.sf.jasperreports.engine.JRDataSource;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import tutorialrelatorios.entidades.Cliente;
import tutorialrelatorios.jdbc.ConnectionFactory;
import tutorialrelatorios.util.ReportUtils;

/**
 * Ponto de entrada do projeto.
 *
 * @author David Buzatto
 */
public class Main {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        new Main().abrirRelatorioClientesDS();
    }

    public void abrirRelatorioClientes() {

        InputStream inputStream = getClass().getResourceAsStream( "/LocacoesPorClientes.jasper" );

        Map parametros = new HashMap();
        parametros.put( "nomeCliente", "F%" );

        try {

            ReportUtils.openReport( "Locações por Clientes", inputStream, parametros,
                    ConnectionFactory.getSakilaConnection() );

        } catch ( SQLException exc ) {
            exc.printStackTrace();
        } catch ( JRException exc ) {
            exc.printStackTrace();
        }

    }

    public void abrirRelatorioClientesDS() {

        InputStream inputStream = getClass().getResourceAsStream( "/ClientesCollectionDS.jasper" );

        Map parametros = new HashMap();

        // criando os dados que serão passados ao datasource
        List dados = new ArrayList();

        for ( long i = 1; i <= 50; i++ ) {
            Cliente c = new Cliente();
            c.setId( i );
            c.setNome( "Nome Cliente " + i );
            c.setSobrenome( "Sobrenome Cliente " + i );
            dados.add( c );
        }

        // criando o datasource com os dados criados
        JRDataSource ds = new JRBeanCollectionDataSource( dados );

        try {

            // passando o datasource para o método de criação e exibição do relatório
            ReportUtils.openReport( "Clientes - Bean Collection Data Source", inputStream, parametros,
                    ds );

        } catch ( JRException exc ) {
            exc.printStackTrace();
        }

    }

}

Ao executar a classe Main, o relatório será exibido. O meu ficou assim:

Figura 5

Exibição Final do Relatório Criado

Como exercício, reaproveitem o método criado para utilizar um JRBeanArrayDataSource. Lembrem-se que um datasource deste tipo espera um array, não uma coleção.

Com isso finalizamos a quarta parte do tutorial! O projeto com o estado atual do nosso tutorial pode ser baixado clicando-se aqui. Na próxima parte do tuorial, iremos aprender como utilizar os relatórios em páginas Web, finalizando então o básico que vocês devem saber para poder trabalhar com relatórios. Até mais! Grande abraço!

Parte 1Parte 2Parte 3Parte 4Parte 5


Parte 1Parte 2Parte 3Parte 4Parte 5

Olá! Hoje vamos aprender a trabalhar com subrelatórios no JasperReports, mas antes de começar quero explicar algumas coisas. Nos últimos posts, eu tenho tentado formatar os termos em inglês em itálico e colocar logo em seguida a tradução. Eu vou parar de fazer isso. Primeiro, pq os termos são simples, e qualquer pessoa que trabalhe com informática tem mais que obrigação de saber. Segundo, gasto muito tempo formatando cada parágrafo, revisando se está tudo ok, etc. Então, para agilizar o processo, estou deixando de lado essa prática. Tenho também tratado os leitores cada hora de um jeito. Uma hora escrevo na primeira pessoa do singular, na outra, escrevo na terceira do plural. Vou tentar manter a mesma narração, mas se eu cometer algum deslize, não se encomodem ok? Então, vamos começar!

Se você já tentou trabalhar com subrelatórios, provavelmente teve algumas frustrações até conseguir fazer funcionar. Se você nunca trabalhou, hoje vai aprender! Trabalhar com subrelatórios não é difícil, o problema é entender como as coisas funcionam, mas antes de falar disso, vamos nos situar no tema. O que é um subrelatório? Como o próprio nome diz é um relatório que fica dentro de um outro relatório, ou seja, é uma parte de um relatório maior, mais geral. Vamos entender por meio de um exemplo. Para isso, abra o diagrama da base de dados sakila clicando aqui e imagine a seguinte situação: você precisa criar um relatório, onde serão exibidos os dados de todos os clientes e de todos os filmes que cada um alugou. O layout do relatório teria que ficar mais ou menos assim:

 

Figura 1

Rascunho do layout do relatório de filmes por cliente

 

Como criar um relatório com esse layout? Usando subrelatórios! A parte mais externa do layout representa o arquivo de relatório mais geral, onde serão listados os clientes e onde será inserido o subrelatório. O subrelatório, destacado em azul, será um outro arquivo de relatório que será utilizado dentro do relatório mais geral. Confuso? Calma, calma, logo vocês vão enteder. Antes disso, um pouquinho mais de teoria.

O funcionamento de um subrelatório é igual ao de um relatório normal, sendo que existe apenas uma diferença e é justamente aqui que as pessoas se confundem. Os parâmetros de um subrelatório não são enviados diretamente do código Java como é feito em um relatório normal. No caso dos subrelatórios, os parâmetros são enviados pelo relatório que os contém, para então serem passados ao subrelatório. Então, caso você passe um parâmetro para o relatório que deve ser usado apenas no subrelatório, o seu relatório vai ter que ter o “mesmo” parâmetro  e você vai ter que criar essa ponte entre o relatório principal e o subrelatório. Veja a Figura abaixo.

 

Figura 2

Parâmetros sendo passados entre as camadas de relatórios

 

Em vermelho é destacada a transmissão dos parâmetros a partir da aplicação em Java para o relatório. Esses parâmetros são enviados usando o mapa da parâmetros lembram? Em laranja é destacada a transmissão dos parâmetros para o subrelatório a partir do relatório. Veja então que o relatório vai servir de ponte entre os parâmetros desejados, além de poder criar novos parâmetros e os enviar para o subrelatório. Outro detalhe é que podem haver diversos níveis de relatórios, ou seja, um subrelatório pode enviar parâmetros para um subsubrelatório, um subsubrelatório pode enviar parâmetros para um subsubsubrelatório, e assim por diante.

Finalmente! Vamos à prática! Abra o NetBeans e o projeto que estamos usando (TutorialRelatorios). Se você não acompanhou a parte anterior do tutorial, ou mesmo que tenha acompanhado e não tenha feito o exemplo, você pode baixar a versão do projeto (finalizada na Parte 2) clicando aqui. Na pasta de definições de relatórios que criamos, crie um novo Empty Report e dê o nome de LocacoesPorClientes. O arquivo LocacoesPorClientes.jrxml vai ser criado. Caso ele não abra automaticamente no editor, clique duas vezes para ser carregado no iReport.

Com o arquivo aberto, vamos primeramente criar um relatório simples, que lista os clientes a partir de uma pesquisa pelo nome. A query deste relatório vai ser um pouco mais complexa que a do relatório “ClientesPorNome”, criado na Parte 2 do tutorial, pois nela iremos obter outros dados do cliente que não estão na tabela customer. Não se esqueça de escolher o datasource correto, o “Sakila – JDBC” ok? Crie também um parâmetro, do tipo String, com o nome de “nomeCliente”. Segue então a query que deve ser inserida no relatório:

SELECT
    c.customer_id idCliente,
    c.first_name nome,
    c.last_name sobrenome,
    c.email email,
    a.address endereco,
    a.phone telefone,
    ci.city nomeCidade,
    co.country nomePais

FROM
    customer c,
    address a,
    city ci,
    country co

WHERE
    /* junções */
    c.address_id = a.address_id AND
    a.city_id = ci.city_id AND
    ci.country_id = co.country_id AND

    /* restrições */
    c.first_name LIKE $P{nomeCliente}

ORDER BY c.first_name;

Ao inserir a query, o iReport vai carregar os campos retornados por ela. Ao clicar em OK, os campos carregados serão criados na definição do relatório. Os fields lembram? Expanda então o nó Fields do Report Inspector e arraste e organize esses campos na banda Detail do relatório. Veja como ficou o meu.

 

Figura 3

Layout do relatório de locações por clientes

 

Note que coloquei campos estáticos na banda Detail, tirei as bandas Column Header, Column Footer e Summary. Criei um parâmetro chamado “nomeUsuario” para mostrar no cabeçalho da página o nome do usuário do “sistema” que gerou o relatório. Outra coisa que fiz foi colocar a data e hora que o relatório foi gerado na banda Page Header. Note que para o uso da data e do formato, foi usado um campo de texto dinâmico, com o tipo java.util.Date e a propriedade pattern alterada para “dd/MM/yyyy’, às’ HH:mm’hs'” (sem as aspas). Note também que o valor deste campo é gerado a partir de um código Java! Segue o fonte em XML do relatório. Basta copiar e colar no seu relatório (usando o botão XML).

LocacoesPorClientes.jrxml

<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="report name" pageWidth="595" pageHeight="842" columnWidth="535" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20">
    <property name="ireport.zoom" value="1.0"/>
    <property name="ireport.x" value="0"/>
    <property name="ireport.y" value="0"/>
    <parameter name="nomeCliente" class="java.lang.String"/>
    <parameter name="nomeUsuario" class="java.lang.String"/>
    <queryString>
        <![CDATA[SELECT
    c.customer_id idCliente,
    c.first_name nome,
    c.last_name sobrenome,
    c.email email,
    a.address endereco,
    a.phone telefone,
    ci.city nomeCidade,
    co.country nomePais

FROM
    customer c,
    address a,
    city ci,
    country co

WHERE
    /* junções */
    c.address_id = a.address_id AND
    a.city_id = ci.city_id AND
    ci.country_id = co.country_id AND

    /* restrições */
    c.first_name LIKE $P{nomeCliente}

ORDER BY c.first_name;]]>
    </queryString>
    <field name="idCliente" class="java.lang.Integer"/>
    <field name="nome" class="java.lang.String"/>
    <field name="sobrenome" class="java.lang.String"/>
    <field name="email" class="java.lang.String"/>
    <field name="endereco" class="java.lang.String"/>
    <field name="telefone" class="java.lang.String"/>
    <field name="nomeCidade" class="java.lang.String"/>
    <field name="nomePais" class="java.lang.String"/>
    <background>
        <band splitType="Stretch"/>
    </background>
    <title>
        <band height="75" splitType="Stretch">
            <staticText>
                <reportElement x="0" y="18" width="555" height="40"/>
                <textElement textAlignment="Center" verticalAlignment="Middle">
                    <font size="25" isBold="true"/>
                </textElement>
                <text><![CDATA[Locações por Clientes]]></text>
            </staticText>
            <line>
                <reportElement x="0" y="1" width="555" height="1"/>
            </line>
            <line>
                <reportElement x="0" y="74" width="555" height="1"/>
            </line>
        </band>
    </title>
    <pageHeader>
        <band height="21" splitType="Stretch">
            <staticText>
                <reportElement x="-1" y="0" width="51" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Usuário:]]></text>
            </staticText>
            <textField>
                <reportElement x="53" y="0" width="150" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$P{nomeUsuario}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="360" y="0" width="68" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Gerado em:]]></text>
            </staticText>
            <textField pattern="dd/MM/yyyy&apos;, às&apos; HH:mm&apos;hs&apos;">
                <reportElement x="433" y="0" width="122" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.util.Date"><![CDATA[new Date()]]></textFieldExpression>
            </textField>
        </band>
    </pageHeader>
    <detail>
        <band height="110" splitType="Stretch">
            <staticText>
                <reportElement x="0" y="3" width="50" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Nome:]]></text>
            </staticText>
            <textField>
                <reportElement x="0" y="23" width="99" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{nome}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="103" y="3" width="70" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Sobrenome:]]></text>
            </staticText>
            <textField>
                <reportElement x="103" y="23" width="136" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{sobrenome}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="242" y="3" width="70" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[E-mail:]]></text>
            </staticText>
            <textField>
                <reportElement x="242" y="23" width="228" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{email}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="0" y="47" width="59" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Endereço:]]></text>
            </staticText>
            <textField>
                <reportElement x="62" y="47" width="177" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{endereco}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="473" y="3" width="82" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Telefone:]]></text>
            </staticText>
            <textField>
                <reportElement x="473" y="23" width="82" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{telefone}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="242" y="47" width="42" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Cidade:]]></text>
            </staticText>
            <textField>
                <reportElement x="287" y="47" width="106" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{nomeCidade}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="397" y="47" width="31" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[País:]]></text>
            </staticText>
            <textField>
                <reportElement x="433" y="47" width="122" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{nomePais}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="167" y="82" width="226" height="20"/>
                <textElement textAlignment="Center" verticalAlignment="Middle" rotation="None">
                    <font size="12" isBold="true"/>
                </textElement>
                <text><![CDATA[O Subrelatório vai vir aqui....]]></text>
            </staticText>
            <line>
                <reportElement x="0" y="1" width="555" height="1"/>
            </line>
        </band>
    </detail>
    <pageFooter>
        <band height="22" splitType="Stretch">
            <textField>
                <reportElement x="455" y="2" width="100" height="20"/>
                <textElement textAlignment="Right" verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.Integer"><![CDATA[$V{PAGE_NUMBER}]]></textFieldExpression>
            </textField>
            <line>
                <reportElement x="0" y="1" width="555" height="1"/>
            </line>
        </band>
    </pageFooter>
</jasperReport>

Depois de colar, volte para o Designer e verifique o que foi feito em cada parte. Após atualizar o seu relatório, teste-o. O iReport vai pedir dois parâmetros, o nomeCliente que vai ser usado na query e o nomeUsuario que vai ser usado no pageHeader. Note que coloquei um campo de texto estático na banda detail, mostrando onde vai ficar o subrealtório. Vamos criá-lo então. Primeiro, remova o campo de texto estático que tem o valor “O Subrelatório vai vir aqui…” e aumente a banda detail na altura cerca de 120 pixels (depois iremos deixar do tamanho correto). Na paleta de componentes, procure por Subreport. Arraste m subreport para a banda Detail. Assim que arrastar e soltar, o assistente de criação de subrelatórios irá abrir. No primeiro passo, escolha “Create a new report” e clique em Next. Veja a Figura abaixo.

 

Figura 4

Primeiro passo do assistente de subrelatório

 

No segundo passo, escolha o layout como Blank A4 e clique em Next. Veja a Figura abaixo.

 

Figura 5

Segundo passo do assistente de subrelatório

 

No terceiro passo, ele vai pedir para inserir uma query. Iremos colocar uma query provisória, só para poder continuar o assistente. Depois iremos mudá-la. Coloque “SELECT * FROM country” (sem as aspas) e clique em Next. Veja a Figura abaixo.

 

Figura 6

Terceiro passo do assistente de subrelatório

 

No quarto passo, ele vai perguntar os campos que queremos usar. Não adicione nenhum, pois não vamos usá-los, pois essa não é a query que queremos lembram? Veja a Figura abaixo.

 

Figura 7

Quarto passo do assistente de subrelatório

 

No quinto passo, o assistente pergunta os grupos que serão usados. Nós não usaremos grupos, então, da mesma forma que fizemos no passo anterior, clique em Next sem fazer nada. Veja a Figura abaixo.

 

Figura 8

Quinto passo do assistente de subrelatório

 

No próximo passo, configuramos o nome do arquivo de relatório que vai ser gerado (Report Name), onde ele vai ser armazenado e como ele vai ser obtido no relatório principal. Sendo assim, em Report Name insira “LocacoesPorClientes_Locacoes” (sem as aspas). Não mexa em Location, pq já deve estar apontanto para o diretório do relatório principal. Abaixo, escolha “Store the directory name in a parameter”. Isso vai ser útil depois, para informarmos onde está o subrelatório. Feito isso, clique em Next. Veja a Figura abaixo.

 

Figuta 9

Sexto passo do assistente de subrelatório

 

No sétimo e último passo é perguntado como os dados do subrelatório serão obtidos. Marque a opção “Use the same connection used to fill the master report”, ou seja, vamos usar o mesmo objeto Connection passado para executar a query do relatório principal. Clique em Finish. Veja a Figura abaixo.

 

Figura 10

Sétimo passo do assistente de subrelatório

 

Ao clicar em Finish, o novo arquivo de relatório será criado e aberto. Mas ainda não vamos mexer nele. Volte ao relatório principal. Você vai perceber que foi inserida uma caixa cinza no relatório. Essa caixa indica onde o subrelatório vai ser inserido. Note então que para cada cliente, teremos um subrelatório renderizado, afinal, estamos na banda Detail não é mesmo? Expanda a caixa na largura, para ocupar quase a totalidade do relatório e altere a largura da banda detail acomodar a caixa do subrealtório. Veja a Figura abaixo.

 

Figura 11

Layout do relatório principal

 

Ainda não teste o relatório! Como estamos obtendo todos os países para cada registro de cliente obtido, o iReport pode travar. Salve o relatório principal e vamos agora para o arquivo do subrelatório. Caso queria, segue o XML do relatório principal.

LocacoesPorClientes.jrxml

<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="report name" pageWidth="595" pageHeight="842" columnWidth="535" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20">
    <property name="ireport.zoom" value="1.0"/>
    <property name="ireport.x" value="0"/>
    <property name="ireport.y" value="0"/>
    <parameter name="nomeCliente" class="java.lang.String"/>
    <parameter name="nomeUsuario" class="java.lang.String"/>
    <parameter name="SUBREPORT_DIR" class="java.lang.String" isForPrompting="false">
        <defaultValueExpression><![CDATA["C:\\Users\\David\\Documents\\Blog\\tutoriais\\TutorialRelatorios\\relatorios\\"]]></defaultValueExpression>
    </parameter>
    <queryString>
        <![CDATA[SELECT
    c.customer_id idCliente,
    c.first_name nome,
    c.last_name sobrenome,
    c.email email,
    a.address endereco,
    a.phone telefone,
    ci.city nomeCidade,
    co.country nomePais

FROM
    customer c,
    address a,
    city ci,
    country co

WHERE
    /* junções */
    c.address_id = a.address_id AND
    a.city_id = ci.city_id AND
    ci.country_id = co.country_id AND

    /* restrições */
    c.first_name LIKE $P{nomeCliente}

ORDER BY c.first_name;]]>
    </queryString>
    <field name="idCliente" class="java.lang.Integer"/>
    <field name="nome" class="java.lang.String"/>
    <field name="sobrenome" class="java.lang.String"/>
    <field name="email" class="java.lang.String"/>
    <field name="endereco" class="java.lang.String"/>
    <field name="telefone" class="java.lang.String"/>
    <field name="nomeCidade" class="java.lang.String"/>
    <field name="nomePais" class="java.lang.String"/>
    <background>
        <band splitType="Stretch"/>
    </background>
    <title>
        <band height="75" splitType="Stretch">
            <staticText>
                <reportElement x="0" y="18" width="555" height="40"/>
                <textElement textAlignment="Center" verticalAlignment="Middle">
                    <font size="25" isBold="true"/>
                </textElement>
                <text><![CDATA[Locações por Clientes]]></text>
            </staticText>
            <line>
                <reportElement x="0" y="1" width="555" height="1"/>
            </line>
            <line>
                <reportElement x="0" y="74" width="555" height="1"/>
            </line>
        </band>
    </title>
    <pageHeader>
        <band height="21" splitType="Stretch">
            <staticText>
                <reportElement x="-1" y="0" width="51" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Usuário:]]></text>
            </staticText>
            <textField>
                <reportElement x="53" y="0" width="150" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$P{nomeUsuario}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="360" y="0" width="68" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Gerado em:]]></text>
            </staticText>
            <textField pattern="dd/MM/yyyy&apos;, às&apos; HH:mm&apos;hs&apos;">
                <reportElement x="433" y="0" width="122" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.util.Date"><![CDATA[new Date()]]></textFieldExpression>
            </textField>
        </band>
    </pageHeader>
    <detail>
        <band height="175" splitType="Stretch">
            <staticText>
                <reportElement x="0" y="3" width="50" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Nome:]]></text>
            </staticText>
            <textField>
                <reportElement x="0" y="23" width="99" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{nome}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="103" y="3" width="70" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Sobrenome:]]></text>
            </staticText>
            <textField>
                <reportElement x="103" y="23" width="136" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{sobrenome}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="242" y="3" width="70" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[E-mail:]]></text>
            </staticText>
            <textField>
                <reportElement x="242" y="23" width="228" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{email}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="0" y="47" width="59" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Endereço:]]></text>
            </staticText>
            <textField>
                <reportElement x="62" y="47" width="177" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{endereco}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="473" y="3" width="82" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Telefone:]]></text>
            </staticText>
            <textField>
                <reportElement x="473" y="23" width="82" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{telefone}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="242" y="47" width="42" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Cidade:]]></text>
            </staticText>
            <textField>
                <reportElement x="287" y="47" width="106" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{nomeCidade}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="397" y="47" width="31" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[País:]]></text>
            </staticText>
            <textField>
                <reportElement x="433" y="47" width="122" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{nomePais}]]></textFieldExpression>
            </textField>
            <line>
                <reportElement x="0" y="1" width="555" height="1"/>
            </line>
            <subreport>
                <reportElement x="7" y="71" width="544" height="100"/>
                <connectionExpression><![CDATA[$P{REPORT_CONNECTION}]]></connectionExpression>
                <subreportExpression class="java.lang.String"><![CDATA[$P{SUBREPORT_DIR} + "LocacoesPorClientes_Locacoes.jasper"]]></subreportExpression>
            </subreport>
        </band>
    </detail>
    <pageFooter>
        <band height="22" splitType="Stretch">
            <textField>
                <reportElement x="455" y="2" width="100" height="20"/>
                <textElement textAlignment="Right" verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.Integer"><![CDATA[$V{PAGE_NUMBER}]]></textFieldExpression>
            </textField>
            <line>
                <reportElement x="0" y="1" width="555" height="1"/>
            </line>
        </band>
    </pageFooter>
</jasperReport>

A primeira coisa que vamos fazer no arquivo do subrelatório é excluir todas as bandas, exceto a Detail e a Column Header. Vamos agora criar um parâmetro chamado “idCliente”, que vai ser usado na nossa query para que possamos obter os filmes de um determinado cliente. O tipo deve deve ser Integer. Vamos agora editar a query do relatório, onde vamos obter o nome e o ano do filme alugado, além da data de devolução, sendo que iremos mostrar apenas os filmes que já foram alugados, ou seja, com data de devolução diferente de NULL. Teremos que fazer junções em algumas tabelas para podermos obter os filmes alugados por um determinado cliente. A query vai ficar assim:

SELECT
    f.title titulo,
    f.release_year anoLancamento,
    r.return_date dataDevolucao

FROM
    customer c,
    rental r,
    inventory i,
    film f

WHERE
    /* junções */
    r.customer_id = c.customer_id AND
    r.inventory_id = i.inventory_id AND
    i.film_id = f.film_id AND

    /* restrições */
    c.customer_id LIKE $P{idCliente} AND
    r.return_date IS NOT NULL

ORDER BY r.return_date, f.title;

Ao clicar em OK, o iReport vai gerar os campos para podermos utilizar no subrelatório. Organize então os campos da mesma forma que mostrado na Figura abaixo.

 

Figura 12

Layout do subrelatório

 

Note que diminui a largura da página para 6,5 polegadas (6.5 inches). Para fazer isso, no Report Inspector, clique com o botão direito no nó raiz (deve estar com o nome do arquivo de relatório) e vá em Page Format (primeira opção) e altere a largura (Width) para 6.5 polegadas e clique em OK. Segue o código XML do subrelatório.

LocacoesPorClientes_Locacoes.jrxml

<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="LocacoesPorClientes_Locacoes" language="groovy" pageWidth="468" pageHeight="802" columnWidth="468" leftMargin="0" rightMargin="0" topMargin="0" bottomMargin="0">
    <property name="ireport.zoom" value="1.0"/>
    <property name="ireport.x" value="0"/>
    <property name="ireport.y" value="0"/>
    <parameter name="idCliente" class="java.lang.Integer"/>
    <queryString>
        <![CDATA[SELECT
    f.title titulo,
    f.release_year anoLancamento,
    r.return_date dataDevolucao

FROM
    customer c,
    rental r,
    inventory i,
    film f

WHERE
    /* junções */
    r.customer_id = c.customer_id AND
    r.inventory_id = i.inventory_id AND
    i.film_id = f.film_id AND

    /* restrições */
    c.customer_id LIKE $P{idCliente} AND
    r.return_date IS NOT NULL

ORDER BY r.return_date, f.title;]]>
    </queryString>
    <field name="titulo" class="java.lang.String"/>
    <field name="anoLancamento" class="java.sql.Date"/>
    <field name="dataDevolucao" class="java.sql.Timestamp"/>
    <background>
        <band splitType="Stretch"/>
    </background>
    <columnHeader>
        <band height="25">
            <staticText>
                <reportElement x="0" y="0" width="100" height="20"/>
                <textElement textAlignment="Center" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Devolvido em]]></text>
            </staticText>
            <staticText>
                <reportElement x="103" y="0" width="47" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Título]]></text>
            </staticText>
            <staticText>
                <reportElement x="326" y="0" width="117" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Ano de Lançamento]]></text>
            </staticText>
            <line>
                <reportElement x="0" y="24" width="468" height="1"/>
            </line>
        </band>
    </columnHeader>
    <detail>
        <band height="21" splitType="Stretch">
            <textField pattern="dd/MM/yyyy">
                <reportElement x="0" y="1" width="100" height="20"/>
                <textElement textAlignment="Center" verticalAlignment="Middle"/>
                <textFieldExpression class="java.sql.Timestamp"><![CDATA[$F{dataDevolucao}]]></textFieldExpression>
            </textField>
            <textField>
                <reportElement x="103" y="1" width="219" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{titulo}]]></textFieldExpression>
            </textField>
            <textField pattern="yyyy">
                <reportElement x="326" y="1" width="117" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.util.Date"><![CDATA[$F{anoLancamento}]]></textFieldExpression>
            </textField>
        </band>
    </detail>
</jasperReport>

Dê um preview no subreport, inserindo o valor “1” (sem as aspas) para o parâmetro idCliente. O preview deve ser parecido com o mostrado na Figura abaixo.

 

Figura 13

Preview do subrelatório

 

Engraçado notar que tem filmes que foram alugados pelo Cliente “1” antes de terem sido lançados 😀 Enfim, estamos quase lá. Falta agora nós fazermos os dois relatórios funcionarem juntos. Lembram que falei que os parâmetros do subrelatório teriam de ser enviados pelo relatório principal? No nosso caso, o nosso subrelatório espera pelo parâmetro “idCliente” ($P{idCliente}) para executar sua query. Então precisamos fazer com que o relatório principal passe este valor, que está sendo guardado do campo idCliente ($F{idCliente}) que é obtido na query do relatório principal.

Para isso, vá no relatório principal e selecione a caixa que vai conter o subrelatório. Ao selecioná-la, procure pela propriedade Parameters no painel de propriedades. O valor vai estar definido como “No parameter defined”. Clique no pequeno botão a direita e a janela mostrada na Figura abaixo vai ser exibida.

 

Figura 14

Configuração de parâmetros

 

Esta janela é utilizada para criar um parâmetro de passagem (eu que inventei esse nome de “parâmetro de passagem”), que deve ter o mesmo nome do parâmetro esperado pelo subrelatório. Os parâmetros de passagem ficam na coluna destacada em roxo da tabela. O valor desse parâmetro é gerado pela expressão que for definida (coluna destacada em verde). Para criar o novo parâmetro de passagem, clique no botão Add. Ao clicar no botão, a janela mostrada na Figura abaixo é mostrada.

 

Figura 15

Janela para adicionar parâmetros de passagem

 

Preencha o campo “Subreport parameter name” com o valor idCliente (nome do parâmetro do subrelatório) e preencha o campo “Value expression” com o valor $F{idCliente}, ou seja, o campo idCliente da query do relatório principal e clique em OK. Veja a Figura abaixo.

 

Figura 16

Criando novo parâmetro de passagem

 

Note que você pode usar o editor de expressões (Expression editor) clicando no botão destacado em laranja. Entendeu o que fizemos? Amarramos o parâmetro idCliente do subrelatório com o valor que vai ser inserido no campo idCliente ($F{idCliente}), ou seja, a cada vez que o subrelatório for chamado (para cada cliente) o valor do parâmetro idCliente vai ser configurado com o valor do campo idCliente. Note que o valor do parâmetro do subrelatório tem que ser do mesmo tipo que o valor gerado pela expressão de valor (value expression). É nesta mesma janela que você vai amarrar todos os parâmetros do subrelatório com valores gerados no relatório ou passados para ele. Por exemplo, o subrelatório tem um parâmetro chamado “paramSub1” e o valor deste parâmetro é passado ao relatório principal no parâmetro chamado “paramPrin1”. Então você teria que amarrar paramSub1 (coluna name, em verde) com a expressão $P{paramPrin1} (coluna expression, em roxo) entendeu? É como se estivéssemos chamando um método, passando parâmetros para ele. Segue então o código XML do relatório principal até o momento.

LocacoesPorClientes.jrxml

<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="report name" pageWidth="595" pageHeight="842" columnWidth="535" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20">
    <property name="ireport.zoom" value="1.0"/>
    <property name="ireport.x" value="0"/>
    <property name="ireport.y" value="0"/>
    <parameter name="nomeCliente" class="java.lang.String"/>
    <parameter name="nomeUsuario" class="java.lang.String"/>
    <parameter name="SUBREPORT_DIR" class="java.lang.String" isForPrompting="false">
        <defaultValueExpression><![CDATA["C:\\Users\\David\\Documents\\Blog\\tutoriais\\TutorialRelatorios\\relatorios\\"]]></defaultValueExpression>
    </parameter>
    <queryString>
        <![CDATA[SELECT
    c.customer_id idCliente,
    c.first_name nome,
    c.last_name sobrenome,
    c.email email,
    a.address endereco,
    a.phone telefone,
    ci.city nomeCidade,
    co.country nomePais

FROM
    customer c,
    address a,
    city ci,
    country co

WHERE
    /* junções */
    c.address_id = a.address_id AND
    a.city_id = ci.city_id AND
    ci.country_id = co.country_id AND

    /* restrições */
    c.first_name LIKE $P{nomeCliente}

ORDER BY c.first_name;]]>
    </queryString>
    <field name="idCliente" class="java.lang.Integer"/>
    <field name="nome" class="java.lang.String"/>
    <field name="sobrenome" class="java.lang.String"/>
    <field name="email" class="java.lang.String"/>
    <field name="endereco" class="java.lang.String"/>
    <field name="telefone" class="java.lang.String"/>
    <field name="nomeCidade" class="java.lang.String"/>
    <field name="nomePais" class="java.lang.String"/>
    <background>
        <band splitType="Stretch"/>
    </background>
    <title>
        <band height="75" splitType="Stretch">
            <staticText>
                <reportElement x="0" y="18" width="555" height="40"/>
                <textElement textAlignment="Center" verticalAlignment="Middle">
                    <font size="25" isBold="true"/>
                </textElement>
                <text><![CDATA[Locações por Clientes]]></text>
            </staticText>
            <line>
                <reportElement x="0" y="1" width="555" height="1"/>
            </line>
            <line>
                <reportElement x="0" y="74" width="555" height="1"/>
            </line>
        </band>
    </title>
    <pageHeader>
        <band height="21" splitType="Stretch">
            <staticText>
                <reportElement x="-1" y="0" width="51" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Usuário:]]></text>
            </staticText>
            <textField>
                <reportElement x="53" y="0" width="150" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$P{nomeUsuario}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="360" y="0" width="68" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Gerado em:]]></text>
            </staticText>
            <textField pattern="dd/MM/yyyy&apos;, às&apos; HH:mm&apos;hs&apos;">
                <reportElement x="433" y="0" width="122" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.util.Date"><![CDATA[new Date()]]></textFieldExpression>
            </textField>
        </band>
    </pageHeader>
    <detail>
        <band height="175" splitType="Stretch">
            <staticText>
                <reportElement x="0" y="3" width="50" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Nome:]]></text>
            </staticText>
            <textField>
                <reportElement x="0" y="23" width="99" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{nome}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="103" y="3" width="70" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Sobrenome:]]></text>
            </staticText>
            <textField>
                <reportElement x="103" y="23" width="136" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{sobrenome}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="242" y="3" width="70" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[E-mail:]]></text>
            </staticText>
            <textField>
                <reportElement x="242" y="23" width="228" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{email}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="0" y="47" width="59" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Endereço:]]></text>
            </staticText>
            <textField>
                <reportElement x="62" y="47" width="177" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{endereco}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="473" y="3" width="82" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Telefone:]]></text>
            </staticText>
            <textField>
                <reportElement x="473" y="23" width="82" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{telefone}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="242" y="47" width="42" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Cidade:]]></text>
            </staticText>
            <textField>
                <reportElement x="287" y="47" width="106" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{nomeCidade}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="397" y="47" width="31" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[País:]]></text>
            </staticText>
            <textField>
                <reportElement x="433" y="47" width="122" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{nomePais}]]></textFieldExpression>
            </textField>
            <line>
                <reportElement x="0" y="1" width="555" height="1"/>
            </line>
            <subreport>
                <reportElement x="7" y="71" width="544" height="100"/>
                <subreportParameter name="idCliente">
                    <subreportParameterExpression><![CDATA[$F{idCliente}]]></subreportParameterExpression>
                </subreportParameter>
                <connectionExpression><![CDATA[$P{REPORT_CONNECTION}]]></connectionExpression>
                <subreportExpression class="java.lang.String"><![CDATA[$P{SUBREPORT_DIR} + "LocacoesPorClientes_Locacoes.jasper"]]></subreportExpression>
            </subreport>
        </band>
    </detail>
    <pageFooter>
        <band height="22" splitType="Stretch">
            <textField>
                <reportElement x="455" y="2" width="100" height="20"/>
                <textElement textAlignment="Right" verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.Integer"><![CDATA[$V{PAGE_NUMBER}]]></textFieldExpression>
            </textField>
            <line>
                <reportElement x="0" y="1" width="555" height="1"/>
            </line>
        </band>
    </pageFooter>
</jasperReport>

Teste o relatório e veja o que acontece. Você vai perceber que para cada cliente será gerado uma lista de filmes. Agora que está tudo pronto, organize o layout do subrelatório e personalize da forma que achar melhor. Eu coloquei a largura com 7,694 polegadas e expandi a linha para ocupar toda a largura. Note que se você mudar alguma coisa no subrelatório, vc precisará compilar apenas ele. Agora, para finalizar, vamos chamar este relatório a partir do nosso programa em Java, mas para isso ainda temos que fazer algumas pequenas modificações no arquivo de relatório principal.

Para entender o que vamos fazer, abra o arquivo LocacoesPorClientes.jrxml e selecione a caixa do subrelatório. Nas propriedades procure pela propriedade Subreport Expression. Essa expressão que é responsável em fazer o subrelatório ser carregado, ou seja, é ela que define para o relatório principal a localização do subrelatório. Por padrão – na verdade, pq definimos quando usamos o assistente de subrelatórios – ela vai conter o valor $P{SUBREPORT_DIR} + “LocacoesPorClientes_Locacoes.jasper”, ou seja, o caminho do subrelatório é formado pelo valor do parâmetro SUBREPORT_DIR concatenado com o nome do arquivo de Subrelatório. O problema é que o parâmetro SUBREPORT_DIR está configurado com um valor fixo, que corresponde ao caminho absoluto do relatório e isso não é bom… O que vamos fazer então é fazer com que tudo execute corretamente tanto no projeto, quanto no programa compilado e empacotado.

Primeiro, no Report Inspector, procure pelo parâmetro SUBREPORT_DIR. Ao selecioná-lo, verifique a propriedade “Default Value Expression”. Ela vai estar definida como o caminho absoluto do diretório onde está o subrelatório. Vamos mudar esse valor para “/” (com as aspas), ou seja, uma String que contém uma barra, que vai indicar a raiz do projeto. O tipo da classe do parâmetro ainda é String. Ok, aqui está pronto. Agora selecione novamente a caixa do subrelatório. Na propriedade Subreport expression entre com o seguinte código.

getClass().getResource( $P{SUBREPORT_DIR} + "LocacoesPorClientes_Locacoes.jasper" )

Note que o ponto e vírgula da instrução não é colocado. Essa linha de código vai retornar um objeto URL que aponta para o arquivo do subrelatório (“/LocacoesPorClientes_Locacoes.jasper”), sendo então carregado corretamente tanto no projeto, quanto no .jar. Note que poderíamos fixar esse valor sem precisar usar o parâmetro, mas caso haja a necessidade de mudar os diretórios dos subrelatórios, essa abordagem facilita o trabalho, tendo apenas que mudar o valor padrão do parâmetro. Como a expressão vai retornar um objeto URL, ainda nas propriedades da caixa do subrelatório, mude a Expression Class (logo abaixo da Subreport Expression) para java.net.URL.

Feito isso, salve o relatório e dê um preview. Na aba iReport output vai ser acusado um warning, dizendo que não é possível encontrar o relatório, no entando o relatório vai ser renderizado direitinho. Agora não precisamos mais nos preocupar, pois tanto no iReport, quanto no .jar que for ser gerado do projeto, o relatório vai abrir corretamente com o subrelatório.

O código do método abrirRelatorioClientes() da classe Main foi alterado para:

public void abrirRelatorioClientes() {

    InputStream inputStream = getClass().getResourceAsStream( "/LocacoesPorClientes.jasper" );

    Map<String, Object> parametros = new HashMap<String, Object>();
    parametros.put( "nomeCliente", "F%" );

    try {

        ReportUtils.openReport( "Locações por Clientes", inputStream, parametros,
                ConnectionFactory.getSakilaConnection() );

    } catch ( SQLException exc ) {
        exc.printStackTrace();
    } catch ( JRException exc ) {
        exc.printStackTrace();
    }

}

O código das versões finais tanto do arquivo de relatório principal, quanto do subrelatório estão listadas a seguir.

LocacoesPorClientes.jrxml

<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="report name" pageWidth="595" pageHeight="842" columnWidth="535" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20">
    <property name="ireport.zoom" value="1.0"/>
    <property name="ireport.x" value="0"/>
    <property name="ireport.y" value="0"/>
    <parameter name="nomeCliente" class="java.lang.String"/>
    <parameter name="nomeUsuario" class="java.lang.String"/>
    <parameter name="SUBREPORT_DIR" class="java.lang.String" isForPrompting="false">
        <defaultValueExpression><![CDATA["/"]]></defaultValueExpression>
    </parameter>
    <queryString>
        <![CDATA[SELECT
    c.customer_id idCliente,
    c.first_name nome,
    c.last_name sobrenome,
    c.email email,
    a.address endereco,
    a.phone telefone,
    ci.city nomeCidade,
    co.country nomePais

FROM
    customer c,
    address a,
    city ci,
    country co

WHERE
    /* junções */
    c.address_id = a.address_id AND
    a.city_id = ci.city_id AND
    ci.country_id = co.country_id AND

    /* restrições */
    c.first_name LIKE $P{nomeCliente}

ORDER BY c.first_name;]]>
    </queryString>
    <field name="idCliente" class="java.lang.Integer"/>
    <field name="nome" class="java.lang.String"/>
    <field name="sobrenome" class="java.lang.String"/>
    <field name="email" class="java.lang.String"/>
    <field name="endereco" class="java.lang.String"/>
    <field name="telefone" class="java.lang.String"/>
    <field name="nomeCidade" class="java.lang.String"/>
    <field name="nomePais" class="java.lang.String"/>
    <background>
        <band splitType="Stretch"/>
    </background>
    <title>
        <band height="75" splitType="Stretch">
            <staticText>
                <reportElement x="0" y="18" width="555" height="40"/>
                <textElement textAlignment="Center" verticalAlignment="Middle">
                    <font size="25" isBold="true"/>
                </textElement>
                <text><![CDATA[Locações por Clientes]]></text>
            </staticText>
            <line>
                <reportElement x="0" y="1" width="555" height="1"/>
            </line>
            <line>
                <reportElement x="0" y="74" width="555" height="1"/>
            </line>
        </band>
    </title>
    <pageHeader>
        <band height="21" splitType="Stretch">
            <staticText>
                <reportElement x="-1" y="0" width="51" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Usuário:]]></text>
            </staticText>
            <textField>
                <reportElement x="53" y="0" width="150" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$P{nomeUsuario}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="360" y="0" width="68" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Gerado em:]]></text>
            </staticText>
            <textField pattern="dd/MM/yyyy&apos;, às&apos; HH:mm&apos;hs&apos;">
                <reportElement x="433" y="0" width="122" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.util.Date"><![CDATA[new Date()]]></textFieldExpression>
            </textField>
        </band>
    </pageHeader>
    <detail>
        <band height="196" splitType="Stretch">
            <staticText>
                <reportElement x="0" y="3" width="50" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Nome:]]></text>
            </staticText>
            <textField>
                <reportElement x="0" y="23" width="99" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{nome}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="103" y="3" width="70" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Sobrenome:]]></text>
            </staticText>
            <textField>
                <reportElement x="103" y="23" width="136" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{sobrenome}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="242" y="3" width="70" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[E-mail:]]></text>
            </staticText>
            <textField>
                <reportElement x="242" y="23" width="228" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{email}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="0" y="47" width="59" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Endereço:]]></text>
            </staticText>
            <textField>
                <reportElement x="62" y="47" width="177" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{endereco}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="473" y="3" width="82" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Telefone:]]></text>
            </staticText>
            <textField>
                <reportElement x="473" y="23" width="82" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{telefone}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="242" y="47" width="42" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Cidade:]]></text>
            </staticText>
            <textField>
                <reportElement x="287" y="47" width="106" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{nomeCidade}]]></textFieldExpression>
            </textField>
            <staticText>
                <reportElement x="397" y="47" width="31" height="20"/>
                <textElement textAlignment="Left" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[País:]]></text>
            </staticText>
            <textField>
                <reportElement x="433" y="47" width="122" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{nomePais}]]></textFieldExpression>
            </textField>
            <line>
                <reportElement x="0" y="1" width="555" height="1"/>
            </line>
            <subreport>
                <reportElement x="0" y="96" width="555" height="100"/>
                <subreportParameter name="idCliente">
                    <subreportParameterExpression><![CDATA[$F{idCliente}]]></subreportParameterExpression>
                </subreportParameter>
                <connectionExpression><![CDATA[$P{REPORT_CONNECTION}]]></connectionExpression>
                <subreportExpression class="java.net.URL"><![CDATA[getClass().getResource( $P{SUBREPORT_DIR} + "LocacoesPorClientes_Locacoes.jasper" )]]></subreportExpression>
            </subreport>
            <staticText>
                <reportElement x="0" y="72" width="555" height="20"/>
                <textElement textAlignment="Center" verticalAlignment="Middle">
                    <font size="12" isBold="true"/>
                </textElement>
                <text><![CDATA[Filmes alugados]]></text>
            </staticText>
            <line>
                <reportElement x="0" y="71" width="555" height="1"/>
            </line>
            <line>
                <reportElement x="0" y="92" width="555" height="1"/>
            </line>
        </band>
    </detail>
    <pageFooter>
        <band height="22" splitType="Stretch">
            <textField>
                <reportElement x="455" y="2" width="100" height="20"/>
                <textElement textAlignment="Right" verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.Integer"><![CDATA[$V{PAGE_NUMBER}]]></textFieldExpression>
            </textField>
            <line>
                <reportElement x="0" y="1" width="555" height="1"/>
            </line>
        </band>
    </pageFooter>
</jasperReport>

LocacoesPorClientes_Locacoes.jrxml

<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd" name="LocacoesPorClientes_Locacoes" language="groovy" pageWidth="554" pageHeight="802" columnWidth="554" leftMargin="0" rightMargin="0" topMargin="0" bottomMargin="0">
    <property name="ireport.zoom" value="1.0"/>
    <property name="ireport.x" value="0"/>
    <property name="ireport.y" value="0"/>
    <parameter name="idCliente" class="java.lang.Integer"/>
    <queryString>
        <![CDATA[SELECT
    f.title titulo,
    f.release_year anoLancamento,
    r.return_date dataDevolucao

FROM
    customer c,
    rental r,
    inventory i,
    film f

WHERE
    /* junções */
    r.customer_id = c.customer_id AND
    r.inventory_id = i.inventory_id AND
    i.film_id = f.film_id AND

    /* restrições */
    c.customer_id LIKE $P{idCliente} AND
    r.return_date IS NOT NULL

ORDER BY r.return_date, f.title;]]>
    </queryString>
    <field name="titulo" class="java.lang.String"/>
    <field name="anoLancamento" class="java.sql.Date"/>
    <field name="dataDevolucao" class="java.sql.Timestamp"/>
    <background>
        <band splitType="Stretch"/>
    </background>
    <columnHeader>
        <band height="25">
            <staticText>
                <reportElement x="0" y="0" width="100" height="20"/>
                <textElement textAlignment="Center" verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Devolvido em]]></text>
            </staticText>
            <staticText>
                <reportElement x="103" y="0" width="47" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Título]]></text>
            </staticText>
            <staticText>
                <reportElement x="326" y="0" width="117" height="20"/>
                <textElement verticalAlignment="Middle">
                    <font isBold="true"/>
                </textElement>
                <text><![CDATA[Ano de Lançamento]]></text>
            </staticText>
            <line>
                <reportElement x="0" y="24" width="554" height="1"/>
            </line>
        </band>
    </columnHeader>
    <detail>
        <band height="21" splitType="Stretch">
            <textField pattern="dd/MM/yyyy">
                <reportElement x="0" y="1" width="100" height="20"/>
                <textElement textAlignment="Center" verticalAlignment="Middle"/>
                <textFieldExpression class="java.sql.Timestamp"><![CDATA[$F{dataDevolucao}]]></textFieldExpression>
            </textField>
            <textField>
                <reportElement x="103" y="1" width="219" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.lang.String"><![CDATA[$F{titulo}]]></textFieldExpression>
            </textField>
            <textField pattern="yyyy">
                <reportElement x="326" y="1" width="117" height="20"/>
                <textElement verticalAlignment="Middle"/>
                <textFieldExpression class="java.util.Date"><![CDATA[$F{anoLancamento}]]></textFieldExpression>
            </textField>
        </band>
    </detail>
</jasperReport>

O preview da versão final do meu relatório ficou assim:

 

Figura 17

Versão final do relatório

 

Então é isso! Terminamos mais uma parte do tutorial! Espero que tenham gostado e que tenha auxiliado quem ainda tinha dificuldade com os subrelatórios. O projeto finalizado desta parte do tutorial pode ser obtido neste link. Na próxima parte do tutorial iremos aprender a trabalhar com outros tipos de datasources além das conexões que estamos usando até agora.

Grande abraço a todos! Até a próxima parte 😉

Parte 1Parte 2Parte 3Parte 4Parte 5