AutoReport - BIS Wiki
O AutoReport é um serviço do BIS com a finalidade de criar relatórios e gráficos de forma automatizada utilizando a estrutura de dados do BIS.
1 Funcionamento e Objetivos
2 Como utilizar o AutoReport
2.1 AutoReportDefinition
2.1.2 WHERE
2.1.3 ORDER BY
2.1.4 GROUP BY
2.2 AutoReportStructure
A finalidade desta ferramenta é permitir que o desenvolver simplesmente passe uma consulta, similar à um SQL mas utilizando a estrutura do BIS, para que a própria ferramente recupere os dados. E depois disso, a partir de algumas informações de estrutura a própria ferramente estrutura os dados retornados do banco de forma coerente e gera objetos que possam ser traduzidos em diversas interfaces para o usuário. Onde interface neste caso pode ser um Grid/Tabela/Tree na UI, um gráfico na UI com opções de iteração, ou mesmo as informações impressas em PDF, exportar para arquivos como Excel, CSV, etc..
Como utilizar o AutoReport
Para criar um relatório, o primeiro passo é criar um AutoReportDefinition. Esta classe de nome sugestivo, tem as definições do relatório. Nela montamos o que seria similar ao SELECT para ser utilizado no banco de dados e recuperar as informações para serem exibidas.
Criando o AutoReport
final AccountBankStatementVO_ v_ = AccountBankStatementVO_.VO;
AutoReportDefinition<AccountBankStatementVO> def = new AutoReportDefinition<AccountBankStatementVO>(AccountBankStatementVO.class);
// Código configurando o Definition (descrito na sessão do AutoReportDefinition)
Com o definition criado, podemos criar o AutoReport para recuperar os dados:
Instanciando o AutoReport
AutoReport<CFlowCategoryTreeNodeVO> rpt = new AutoReport<CFlowCategoryTreeNodeVO>(def, provider);
AutoReportProvider
O seguindo parâmetro para criar um AutoReport é um objeto do tipo AutoReportProvider.
O provider nada mais é que uma interface que deve fazer a ponte entre o AutoReport e o BISDAO. Isso porque, como o AutoReport pode ser utilizado em qualquer parte do sistema, ele não tem acesso direto ao BISDAO. Por exemplo, se estiver sendo utilizado na UI, deve ter um Provider que repasse as chamadas de consulta passando através da Facade com a devida identificação do login do usuário. Mas se estiver sendo utilizado diretamente do CRUD, deve ter um provider com acesso direto ao BISDAO, pois já está dentro da sessão CMT do container.
A UI do BIS já fornece uma implementação genérica para utilizar o AutoReport nas telas do Vaadin: a AutoReportUIProvider().
Uma observação aqui é a chamada do método .refresh(). Este método faz com que o AutoReport leia a definition e faça a consulta no banco de dados. O AutoReport aplica o "LazyLoad", assim nesse método ele fará a consulta e buscará apenas os IDs dos objetos aplicando todas as demais regras de WHERE, GROUPBY e ORDER BY. Somente a medida em que solicitarmos os blocos de dados é que serão consultados na base de dados.
Para obter os dados do AutoReport, devemos chamar os métodos .fetch(), conforme exemplo:
Recuperando os Dados do AutoReport
for (int i = 0; i < rpt.size(); i += 10) {
List<Object[]> data = rpt.fetch(i, 10);
System.out.print("Coluna " + j + ": " + row[j] + " / ");
O exemplo acima recupera os dados em blocos de 10 registros e os imprime no console. (Código simples mas útil para DEBUG dos dados retornados no relatório.)
O método fetch aceita dois argumentos, indicando um offSet (a partir de qual registro desejamos) e um total (indicando o total de registros desejados). Desta forma conseguimos recuperar em blocos para exibir ou processar as informações do relatório.
Vale lembrar que o AutoReport não tem nenhum tipo de cache dos valores dos registros retornados. Isto significa que uma vez que foi solicitado e retornado, o AutoReport não armazena nada. Assim, a cada vez que os métodos de fetch são chamados uma nova consulta é realizada no banco de dados. Aqui entramos em um dilema. Buscar linha a linha no banco de dados, podemos não ocupar a memória com muitas informações, mas certamente criamos um overhead de processamento no banco de dados, já que a cada requisição feita, o banco precisa separar os dados, organizar e filtrar antes de devolve-los. Além de todo o stack de chamadas entre o método e o acesso ao banco.
Porque não realizar a busca por ID?!
Uma vez que o AutoReport já tem todos os IDs dos objetos (recuperados na chamada do .refresh(), pq não buscar os registros secundários somente pelo ID do objeto? Isso não simplificaria a query e agilizaria para o Banco retornar os objetos?
Reposta: Sim! Mas só funciona se não tivermos tabelas 1:N associadas ao objeto principal e que filtros do WHERE estejam justamente excluindo apenas alguns dos mapeamentos. Ao buscar pelo ID do objeto principal e ignorar os WHERE que faziam o filtro de algumas associações, passaríamos a ter todas as associações como retorno.
AutoReportDefinition
Imagine que desejamos criar um relatório com os dados dos lançamentos financeiros das conta do BIS. Para isso precisamos definir com o AutoReport buscará os dados:
Exemplo de criação do Definition:
Criando o AutoReportDefinition
Note que o definition é instanciado com a classe que servirá de referência (tabela principal do Select) para que o BISDAO faça os joins e buscas no banco de dados.
Uma vez criado o definition, precisamos agora informar que colunas desejamos que sejam recuperadas. Similar ao SELECT do banco de dados, aqui selecionamos os campos dos VOs que queremos para que esses dados estejam disponíveis no relatório.
Note que é possível utilizar a estrutura do BISField para definir funções e outras operações para serem executadas diretamente no banco de dados.
Seleção das Colunas
AutoReportColumn columnID = def.addSelect(BISField.field("id"));
AutoReportColumn columnDate = def.addSelect(BISField.field(v_.date()));
AutoReportColumn columnDisplayline = def.addSelect(BISField.field(v_.displayLine()));
AutoReportColumn columnOperation = def.addSelect(BISField.field(v_.operation()));
AutoReportColumn columnValue = def.addSelect(BISField.field(v_.value()));
AutoReportColumn columnDisplayline = def.addSelect(BISField.field(v_.accountStatementList().displayLine()));
AutoReportColumn columnCatMapID = def.addSelect(BISField.field(v_.accountStatementList().billPaymentVO().categoryMapList().id()));
AutoReportColumn columnCat = def.addSelect(
BISField.coalesce(
BISField.field(v_.accountStatementList().categoryMapList().categoryVO().name()),
BISField.field(v_.accountStatementList().billPaymentVO().categoryMapList().categoryVO().name())));
AutoReportColumn columnCatValue = def.addSelect(
BISField.field(v_.accountStatementList().categoryMapList().value()),
BISField.field(v_.accountStatementList().billPaymentVO().categoryMapList().value())));
Utilizando a estrutura dos MetaObject para selecionar o campo SEMPRE a partir do objeto utilizado como raiz, o AutoReport utilizará o BISDAO para unir as tabelas e trazer os dados coerentemente. Portanto, a parte do "FROM" do SQL é criada automaticamente pelo BISDAO.
Utilização do BISDAO
Note que apesar do AutoReport utilizar a estrutura de objetos do BISDAO o conteúdo da consulta é retornado uma lista de Object[] com os objetos retornados diretamente do banco de dados. Assim, é necessário solicitar cada campo do VO que se deseja no relatório (cada coluna da tabela). Diferentemente das consultas por objeto do BISDAO, em que solicitar um campo do VO já trás todo o objeto populado.
Há alguns motivos para esse funcionamento:
A consulta completa e a montagem dos objetos é muito mais "cara" em questão de processamento e memória. Muitas vezes o relatório manipula muitos dados de uma única vez, assim precisaríamos de máquinas mais potentes e caras para rodar os relatórios.
Outro problema seriam colocar as colunas que foram manipuladas com as funções do BISField, por exemplo: Imagine uma coluna do banco de dados "X" que é do tipo data. Mas recuperamos ela com a função BISField.month("X"). Essa função retornaria um valor inteiro com o número do mês da data, o que já não seria possível fazer um cast para o tipo do campo existente no objeto para a coluna X.
Uma vez familiarizado com a estrutura do BISMO, a parte de "WHERE" (filtro dos dados do relatório) não tem nenhum segredo. Basta criar o BISMO e associa-lo ao AutoReportDefinition para que ele seja utilizado. Exemplo:
Filtrando os dados do AutoReport
BISMO mo = new BISMO();
mo.greaterThanOrEqualTo(v_.date(), LocalDate.of(2019, 11, 1));
mo.lessThan(v_.date(), LocalDate.of(2019, 12, 1));
mo.equal(v_.accountVO().id(), 1);
def.setBISMO(mo);
Assim como a parte do WHERE, a parte do ORDER BY no AutoReport não tem segredos em relação à estrutura já utilizada no BISDAO.
Ordenando os dados do AutoReport
BISOrderBy orderBy = BISOrderBy.createInstance(v_.date());
def.setOrderBy(orderBy);
O GroupBy tem exatamente a mesma funcionalidade do GROUP BY no SQL.
Para configurar o GroupBy no AutoReportDefinition utilizaremos a mesma sintaxe utilizada na parte do SELECT. O GroupBy aceita as mesmas funções e colunas definidas pela estrutura do BISField.
Definindo colunas de GroupBy para a consulta
def.addGroupBy(BISField.field(v_.id()));
AutoReportStructure
Legal, já consegui montar meu definition e recuperar os meus dados. Como transformar essa esse monte de dados em um relatório? A reposta é com o AutoReportStructure! =)
O AutoReportStructure tem a função de carregar a informação de como todos esses dados retornados pelo banco se interagem e podem ser estruturados para dar cara de relatório. Uma vez criada a estrutura as interfaces que exibem os dados conseguem montar o relatório independente da interface de exibição sem saber exatamente que dados ou objetos estão sendo exibidos.