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