JasperReports com App Web + SpringBoot

Bom dia a todos, estou tentando montar um relatório com JasperReports + iReport 5.6.0, tenho uma aplicação Web desenvolvida com Vaadin/Java, uso tbm Spring Boot e Maven.

Lembrando a todos que é minha primeira experiência com Jasper.

Esta é a minha tela de relatorios:

Aqui o usuario define a data de inicio e fim, e em seguida clica em Gerar Relatório e chegamos nesta tela.

Agora criei um botão chamado Exportar Relatório, neste momento ainda não implementei nenhuma chamado para este botão, está sem lógica alguma. Neste botão quero que, quando o usuario clicar nele, abra um janela ou guia no navegador com o relatório gerado pelo Jasper, de preferencia em formato PDF.

Então pesquisando como trabalhar com Jasper fiz o seguinte:

1º Criei o relatório no iReport:

relatório super simples, pois é meu primeiro.

2º criei uma pasta dentro de src para armazenar este relatório:

3º criei uma classe que terá a responsabilidade de referenciar o arquivo, declarar o objeto, compilar, preencher e exibir:

package br.com.fjsistemas.relatorios;

import java.io.InputStream;
import java.util.List;



import br.com.fjsistemas.backend.Venda;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;
import net.sf.jasperreports.view.JasperViewer;

public class GerarRelatorioVenda {

	public void gerarRelatorioVendas(List<Venda> lista) throws JRException { 
		
		//referencia ao arquivo, carregar no objeto
		InputStream fonte = GerarRelatorioVenda.class.getResourceAsStream("/relatorioVendas/relatorioVenda.jrxml");
		
		//Declarando objeto do tipo JasperReports
		JasperReport report = JasperCompileManager.compileReport(fonte);
		
		//preenchendo o relatorio 1º parametro relatorio compilado, 2º null pois não havera imagens no logo, 3º passando a lista transformando-a em um datasource informando qual fonte de dados
		//o relatorio vai pegar as informações
		JasperPrint print = JasperFillManager.fillReport(report, null, new JRBeanCollectionDataSource(lista));
		
		//exibir
		JasperViewer.viewReport(print, false);
		
	}
}

e por fim, tenho a classe RelatorioVendaView que é a interface grafica: 1ª imagem deste tópico:

package br.com.fjsistemas.relatorios;

import java.text.NumberFormat;
import java.text.ParseException;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Locale;

import org.springframework.beans.factory.annotation.Autowired;

import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
import com.vaadin.flow.component.datepicker.DatePicker;
import com.vaadin.flow.component.grid.Grid;
import com.vaadin.flow.component.html.Label;
import com.vaadin.flow.component.orderedlayout.HorizontalLayout;
import com.vaadin.flow.component.orderedlayout.VerticalLayout;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.data.binder.PropertyId;
import com.vaadin.flow.data.renderer.LocalDateRenderer;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;

import br.com.fjsistemas.backend.Venda;
import br.com.fjsistemas.main.MainView;
import br.com.fjsistemas.repository.VendaRepository;

@Route(value = "relatorio-view", layout = MainView.class)
@PageTitle("Relatório de Vendas")
public class RelatorioVendaView extends VerticalLayout {

	private static final long serialVersionUID = 1L;

	private HorizontalLayout layoutDatas = new HorizontalLayout();

	private Button gerarRelatorio = new Button("Gerar Relatório");
	private Button exportarRelatorio = new Button("Exportar Relatório");

	private Grid<Venda> grid = new Grid<>();

	Label label = new Label("Valor Total no Periodo:");

	@PropertyId("dataInicio")
	private DatePicker dataInicio = new DatePicker("Início");

	@PropertyId("dataFim")
	private DatePicker dataFim = new DatePicker("Final");

	@PropertyId("somaValores")
	private TextField somaValores = new TextField();

	@Autowired
	private VendaRepository vendaRepository;

	private List<Venda> listaVendas;

	public RelatorioVendaView() {
		configuraRelatorio();
	}

	private void populaInformacao() {

		listaVendas = vendaRepository.findAllByDataVendaBetween(dataInicio.getValue(), dataFim.getValue());
		somaValoresRelatorio();
		atualizaGrdVenda();

	}

	private void atualizaGrdVenda() {
		grid.setItems(listaVendas);
	}

	private void somaValoresRelatorio() {

		NumberFormat formatter = NumberFormat.getCurrencyInstance(new Locale("pt", "BR"));

		double soma = 0;

		for (Venda venda : listaVendas) {
			try {
				soma += formatter.parse(venda.getValorTotalVenda()).doubleValue();
			} catch (ParseException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}

		somaValores.setValue(formatter.format(soma));
	}

	private void configuraRelatorio() {

		grid.setWidthFull();
		grid.setHeight("740px");

		grid.addColumn(new LocalDateRenderer<>(Venda::getDataVenda, DateTimeFormatter.ofPattern("dd/MM/yyy")))
				.setHeader("Data Venda").setAutoWidth(true);

		grid.addColumn(venda -> venda.getCliente().getNome()).setHeader("Nome:").setAutoWidth(true)
				.setKey("cliente.nome");

		grid.addColumn(Venda::getValorTotalVenda).setHeader("Valor Total:").setAutoWidth(true)
				.setKey("valorTotalVenda");

		grid.getColumns().forEach(col -> col.setAutoWidth(true).setSortable(true).setResizable(true));

		gerarRelatorio.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
		gerarRelatorio.getStyle().set("margin-top", "37px");
		gerarRelatorio.setWidth("180px");
		gerarRelatorio.addClickListener(event -> {
			populaInformacao();
		});

		exportarRelatorio.addThemeVariants(ButtonVariant.LUMO_PRIMARY);
		exportarRelatorio.getStyle().set("margin-top", "37px");
		exportarRelatorio.setWidth("180px");
		exportarRelatorio.addClickListener(event -> {

		});

		somaValores.setWidth("245px");
		somaValores.getStyle().set("margin-left", "620px");
		somaValores.setLabel("Valor Total no Período");

		dataInicio.setWidth("181px");
		dataFim.setWidth("181px");

		layoutDatas.add(dataInicio, dataFim, gerarRelatorio, exportarRelatorio, somaValores);

		add(layoutDatas, grid);
	}

	public List<Venda> getListaVenda() {
		return listaVendas;
	}

	public void setListaVenda(List<Venda> listaVenda) {
		this.listaVendas = listaVenda;
	}

}

PERGUNTAS:

1ª Até aqui está certo esta lógica? Ou pulei algo?
2º Caso esteja certo, como faço para implementar este relatório do botão exportar relatorio?

Opa, bão?

Apenas mudaria o local do jrxml para src/main/resources. Criaria uma pasta lah, (ex.: relatorios) e colocaria todos os relatorios nela.

E anotaria a classe GerarRelatorioVenda com um @Component também.

A forma como vc está apresentando o relatório que falta ajustar. Em vez de abrir o jasperviewer, vc deve conseguir obter os bytes do relatorio e fazer o download dele.

Como o sistema é web, com o jasperviewer, vc soh vai conseguir visualizar o relatorio se estiver rodando o sistema localmente. Por exemplo, se eu fosse tentar acessar seu sistema e tentar gerar um relatorio, nunca irei conseguir vê-lo, pois ele acabaria sendo gerado no servidor apenas.

1 curtida

@Lucas_Camara

Bom dia man!!

1 curtida

Isso. Só cuidado com o case (diferença de letras maíusculas e minúsculas) ao referenciar o arquivo na string.

1 curtida

Não entendi como fazer isso, entendi que devo substituir isso:

     //exibir
	JasperViewer.viewReport(print, false);

por outra opção, mas qual seria a correta neste caso?

Com o jasper, vc consegue executar o relatorio e obter os bytes dele que foi gerado. Para obter os bytes, tente assim:

byte[] report = JasperExportManager.exportReportToPdf(jasperPrint);

e retorne esses bytes no método gerarRelatorioVendas.

Para fazer o download desses bytes, como vc está usando o vaadin, tem que pesquisar como ele trata isso, mas provavelmente tem um jeito, pois ele é um framework web. Vou pesquisar aqui e posto se achar.

1 curtida

Nesse link explica como fazer download no vaadin: https://vaadin.com/docs/v8/framework/articles/LettingTheUserDownloadAFile

1 curtida

@Lucas_Camara

Minha classe GerarRelatorioVenda ficou assim:

package br.com.fjsistemas.relatorios;

import java.io.InputStream;
import java.util.List;

import org.springframework.stereotype.Component;

import br.com.fjsistemas.backend.Venda;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;

@Component
public class GerarRelatorioVenda {

	public byte[] gerarRelatorioVendas(List<Venda> lista) throws JRException { 
		
		//referencia ao arquivo, carregar no objeto
		InputStream fonte = GerarRelatorioVenda.class.getResourceAsStream("/RelatorioVendas/relatorioVenda.jrxml");
		
		//Declarando objeto do tipo JasperReports
		JasperReport report = JasperCompileManager.compileReport(fonte);
		
		//preenchendo o relatorio 1º parametro relatorio compilado, 2º null pois não havera imagens no logo, 3º passando a lista transformando-a em um datasource informando qual fonte de dados
		//o relatorio vai pegar as informações
		JasperPrint print = JasperFillManager.fillReport(report, null, new JRBeanCollectionDataSource(lista));
		
		//exibir
		byte[] relatorio = JasperExportManager.exportReportToPdf(print);
		
		return relatorio;
		
	}
} 

até aqui está certo?

1 curtida

li e re-li inumeras vezes a documentação, mas não entendi nada…vou ver o que faço, mas obg pelo seu tempo @Lucas_Camara

Pelo que entendi do exemplo no site do vaadin, vc terá que fazer algo mais ou menos assim:

Button downloadButton = new Button("Gerar relatório");

StreamResource myResource = createResource();
FileDownloader fileDownloader = new FileDownloader(myResource);
fileDownloader.extend(downloadButton);
private StreamResource createResource() {
	return new StreamResource(new StreamSource() { // aqui vc pode simplificar com lambda: () -> {}
		@Override
		public InputStream getStream() {
			List<Venda> vendas = vendaRepository.findAll();
			byte[] relatorio = gerarRelatorioVenda.gerarRelatorioVendas(vendas);
			
			try {
				ByteArrayOutputStream baos = new ByteArrayOutputStream(relatorio.length);
				baos.write(relatorio, 0, relatorio.length);
				
				return new ByteArrayInputStream(baos.toByteArray());
			} catch (IOException e) {
				e.printStackTrace();
				return null;
			}
        }, "relatorioVendas.pdf");
    }
}

@Lucas_Camara

uma duvida:

eu mantenho esta classe?

package br.com.fjsistemas.relatorios;

import java.io.InputStream;
import java.util.List;

import org.springframework.stereotype.Component;

import br.com.fjsistemas.backend.Venda;
import net.sf.jasperreports.engine.JRException;
import net.sf.jasperreports.engine.JasperCompileManager;
import net.sf.jasperreports.engine.JasperExportManager;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.data.JRBeanCollectionDataSource;

@Component
public class GerarRelatorioVenda {

	public byte[] gerarRelatorioVendas(List<Venda> lista) throws JRException { 
		
		//referencia ao arquivo, carregar no objeto
		InputStream fonte = GerarRelatorioVenda.class.getResourceAsStream("/RelatorioVendas/relatorioVenda.jrxml");
		
		//Declarando objeto do tipo JasperReports
		JasperReport report = JasperCompileManager.compileReport(fonte);
		
		//preenchendo o relatorio 1º parametro relatorio compilado, 2º null pois não havera imagens no logo, 3º passando a lista transformando-a em um datasource informando qual fonte de dados
		//o relatorio vai pegar as informações
		JasperPrint print = JasperFillManager.fillReport(report, null, new JRBeanCollectionDataSource(lista));
		
		//exibir
		byte[] relatorio = JasperExportManager.exportReportToPdf(print);
		
		return relatorio;
		
	}
} 

e este trecho de código que vc passou e add ele na classe view do relatório?

```
Button downloadButton = new Button("Gerar relatório");

StreamResource myResource = createResource();
FileDownloader fileDownloader = new FileDownloader(myResource);
fileDownloader.extend(downloadButton);
```

```
private StreamResource createResource() {
	return new StreamResource(new StreamSource() { // aqui vc pode simplificar com lambda: () -> {}
		@Override
		public InputStream getStream() {
			List<Venda> vendas = vendaRepository.findAll();
			byte[] relatorio = gerarRelatorioVenda.gerarRelatorioVendas(vendas);
			
			try {
				ByteArrayOutputStream baos = new ByteArrayOutputStream(relatorio.length);
				baos.write(relatorio, 0, relatorio.length);
				
				return new ByteArrayInputStream(baos.toByteArray());
			} catch (IOException e) {
				e.printStackTrace();
				return null;
			}
        }, "relatorioVendas.pdf");
    }
}
```

Pode manter. É até uma boa prática fazer separação de código por responsabilidade, e essa classe está responsável por gerar o relatório.

Sim. Peguei lah do site do vaadin e tentei adaptar de acordo com o pouco que conheço do seu projeto

Provavelmente será preciso ajustar alguma coisa no método createResource, ai vc vê