RSS

Arquivo da categoria: Dicas

Paginação por Demanda com JSF – Parte 3

Note que os métodos são genéricos, mas que isso só funcionária se a query nunca tivesse nenhum tipo de restrição, ou seja, se você quisesse realizar o count apenas de determinadas informações, dessa maneira seria impossível e você teria que reescrever o método count sempre que precisasse restringir alguma informação.
Pensando nisso vamos abstrair mais ainda o Método count. Nele agora será necessário informar às restrições que deverão compor a query.
Vejamos como ficará o método count.

public Integer count(List<Criterion> restricoes){
Integer size = 0;
try {
getHibernate().beginTransaction();
Criteria criteria = getHibernate().getSession().createCriteria(getEntidade().getClass());
if(restricoes != null){
for (Criterion criterion : restricoes) {
criteria.add(criterion);
}
}
criteria.setProjection(Projections.rowCount());
size = (Integer) criteria.uniqueResult();
getHibernate().commit();
} catch (Exception e) {
getHibernate().rollback();
e.printStackTrace();
} finally {
getHibernate().close();
}
return size;
}

Note que agora é solicitado uma Lista de restrições (Criterions) que serão utilizados pela criteria. Isso torna o método genérico a qualquer consulta, sendo necessário passar a restrição que desejar dentro de uma lista, da seguinte maneira.


List lista = new ArrayList();
lista.add(Restrictions.eq("usuario", usuario.GetUsuario()));

Dessa maneira é informado qual restrição quero para o método count.
Pensando dessa forma o método listByCriteriaDemanda deverá permitir restrições, ordenações e até mesmo projeções. Aqui entramos em um problema:
Teremos que prevenir o LIE (Lazy Initialization Exception). Se a sua entidade possuir uma listagem e está será utilizada na tela, a mesma deverá ser Lazy false ou você terá que realizar a prevenção do LIE. No meu caso teremos que inicializar essa dependência.

Mas como? Meu DAO é genérico, não tenho acesso aos atributos da classe e não sei se esta classe possui ou não listagens que poderão causar o LIE.
Vamos utilizar um recurso do Java. Vamos utilizar reflexão, ou seja, vamos descobrir se a classe possui ou não atributos que são listas e iremos inicializar esses atributos.

Vamos ver como ficaria o nosso método após a adição das restrições e inicialização de objetos de um determinado tipo por reflexão.

public List<T> listByCriteriaDemanda
(Integer startPage, Integer maxPage, List<Criterion> restricoes, List<Projection> projecoes, List<Order> ordenacao){

         List<T> list = new ArrayList<T>();

         try {

                   getHibernate().beginTransaction();

                   Criteria criteria = getHibernate().getSession().createCriteria(getEntidade().
getClass());

if(restricoes !=null){

for(Criterion r: restricoes){

criteria.add(r);

}

}

if(projecoes != null){

for (Projection p : projecoes) {

criteria.setProjection(p);

}

}

if(ordenacao != null){

for(Order order: ordenacao){

criteria.addOrder(order);

}

}

criteria.setFirstResult(startPage);

criteria.setMaxResults(maxPage);

list = criteria.list();

for (T t : list) {

for (Method method : t.getClass().getMethods()) {

if(method.getName().indexOf(“get”) > -1){

Object obj = method.invoke(t);

if(obj instanceof java.util.Collection){

for (T lista: (List<T>)obj) {

Hibernate.initialize(lista);

}

}

}

}

}

getHibernate().commit();

} catch (Exception e) {

getHibernate().rollback();

e.printStackTrace();

} finally {

getHibernate.close();

}

return list;

}

Nosso método agora ficou bastante genérico, trouxemos as restrições, as projeções e as ordenações para dentro dele. Na nossa classe agora poderemos criar nossas restrições, projeções e ordenação e manter nosso DAO genérico, sem precisar implementar novamente as funcionalidades da classe.

O trecho abaixo é referente a parte de reflexão que iremos utilizar para descobrir se um atributo é uma coleção ou não, para podermos inicializar este atributo.

for (T t : list) {

 for (Method method : t.getClass().getMethods()) {

if(method.getName().indexOf(“get”) > -1){

Object obj = method.invoke(t);

if(obj instanceof java.util.Collection){

for (T lista: (List<T>)obj) {

Hibernate.initialize(lista);

}

}

}

}

}

Aqui nos fazemos uma iteração na lista criada pela criteria. Criamos um objeto do tipo Method, esse objeto é um Array e é carregado a partir do método

t.getClass.getMethods();

Esse método retorna um Methods[].

Feito isso nós testamos se no nome do método existe a palavra “get”, pois será necessário invocar esse get para sabermos qual o tipo de objeto será retornado.
O método indexOf retorna >-1 caso seja verdadeiro, ou seja, se a palavra get existir no nome do método então o retorno será verdadeiro.
Com isso nós invocamos o método através do method.invoke(t);
Como não sabemos qual o tipo de retorno então criamos um objeto do tipo Object que irá receber o retorno. Se esse objeto for filho de collection (List, Set, ArrayList, HashTree, etc) então iremos iterar sobre esse objeto e iremos inicializar item após item da listagem resultante. Por padrão, os relacionamentos OneToMany retornam um objeto do tipo PersistenceBag que é filho de Collection.

Feito isso sua classe está completamente inicializada e não haverá problemas com LazyInitialization e você terá uma paginação em demanda funcional.

Em breve estarei disponibilizando um projeto war contendo esse DAO, SuperDAO e as classes necessárias para realizar a paginação. Um exemplo funcional com acesso a uma base de dados.

 
1 Comment

Publicado por em dezembro 28, 2008 em Dicas, JSF

 

Paginação por Demanda com JSF – Parte 2

O get do DataModel terá que ter a seguinte aparência:

public DataModel getDataModel() {
  int totalListSize = dao.count();
  List<Usuário> pagedList = dao.listByCriteriaDemanda(getDataTable().getFirst(), getDataTable().getRows());
  dataModel = new PagedDataModel(pagedList, tatalListSize);
  return dataModel;
}     

Explicando o que cada um faz:

O primeiro carinha (totalListSize) é responsável por realizar uma consulta no banco que irá retornar a quantidade de linhas existentes para aquela consulta.
O segundo é responsável por realizar a consulta por demanda via hibernate, utilizando criteria para isso.
O terceiro seria a instancia do dataModel de acordo com a classe PagedDataModel que criamos.

Vamos criar os dois métodos, o primeiro para contar as linhas da tabela e o segundo para listar de acordo com a quantidade desejada. Sempre utilizando criteria.

No meu caso possuo uma classe chamada SuperDao que é genérica.
O método getEntidade retorna um objeto do tipo T e o método getHibernate me retorna o objeto Hibernate que está sendo utilizado.

public List<T> listByCriteriaDemanda(Integer startPage, Integer maxPage){

         List<T> list = new ArrayList<T>();

         try {

                  getHibernate().beginTransaction();

                  Criteria criteria = getHibernate().getSession().createCriteria(getEntidade().getClass());

                  criteria.setFirstResult(startPage);

                  criteria.setMaxResults(maxPage);

                  list = criteria.list();

                  getHibernate().commit();

 

         } catch (Exception e) {

                  getHibernate().rollback();

                  e.printStackTrace();

          } finally {

                  getHibernate.close();

         }       

         return list;

}

public Integer count(){

         Integer size = 0;

         try {

                   getHibernate().beginTransaction();

                   Criteria criteria = getHibernate().getSession().createCriteria(getEntidade().getClass());

                   criteria.setProjection(Projections.rowCount());

                   size = (Integer) criteria.uniqueResult();

                   getHibernate().commit();

         } catch (Exception e) {

                   getHibernate().rollback();

                   e.printStackTrace();

         } finally {

                   getHibernate().close();

         }

                   return size;

}

Parte 3

 
3 Comments

Publicado por em dezembro 28, 2008 em Dicas, JSF

 

Paginação por Demanda com JSF – Parte 1

Um dos grandes problemas de aplicações grande é o fato da Paginação em memória. Imaginem uma aplicação que trabalha com milhões de dados e que possui milhões de acesso simultâneo como o Ebay ou Mercado Livre, seria muito complicado lidar com milhões de informações na tela ou mesmo realizar uma paginação em memória.

PAGINAÇÃO EM DEMANDA

Há poucos dias comecei a pesquisar sobre esse tipo de paginação, devido aos fatos que expus acima. Nossa aplicação começou pequena, com poucas informações. Tudo corria muito bem até que a aplicação começou realmente a ser utilizada. No nosso caso esta aplicação serviria para controle de documentação acadêmica para um determinado evento que estava sendo organizado. Em principio nosso sistema reagiu bem, com a paginação em memória tudo ficou muito bonito, os filtros funcionavam e as informações eram exibidas com relativa velocidade.

Nossa aplicação era constituída de 4 módulos:
O primeiro era o módulo de cadastro de usuários, onde eles iriam informar dados básicos.
O segundo era um módulo de gerenciamento dos usuários.
O terceiro era um módulo de gerenciamento de atividades do usuário para o evento. (cadastro de material informativo, de resumos para apresentações, etc)
O quarto era um módulo administrativo para gerenciar as atividades de todos os usuários (aprovação cadastral, aprovação de trabalhos para apresentação, lotação de trabalhos, etc).

Os 3 primeiros módulos funcionaram corretamente, sendo que o 4 módulo foi o crucial para queremos “reescrever” a aplicação inteira. Este quarto modulo se tornou um peso enorme para a aplicação e para nossos servidores. Devido a grande massa de dados e a quantidade enorme de pessoas utilizando o sistema simultaneamente, nosso servidor sempre ficava indisponível, devido a paginação em memória.
Imaginem 100 pessoas acessando simultaneamente sua aplicação e solicitando 10 mil registros a cada clique do mouse? Isso daria em torno de 1 milhão de informações sendo carregadas simultaneamente na memória do computador.

Esse foi o motivo que me fez pensar em “paginação por demanda”. Já tinha ouvido falar deste tipo de paginação mas nunca tinha me deparado com uma situação real onde fosse necessário utilizá-la. Bom… deixando de lado esse “histórico” vamos ao que interessa: Paginação em Demanda.

Neste tipo de paginação que irei demonstrar é necessário utilizar-se de alguns componentes.
Vamos lá.

Primeiro é necessário criar uma classe que extenda o DataModel.
Essa classe terá que ter a seguinte aparência:

import java.util.List;
import javax.faces.model.DataModel;

public class PagedDataModel extends DataModel{

  private int rowIndex = -1;
  
  
private int totalNumRows;
  
  
private int pageSize;
  
  
private List list;
  
  
public PagedListDataModel() {
    
super();
  
}
  
  
public PagedListDataModel(List list, int totalNumRows) {
    
super();
    
setWrappedData(list);
    
this.totalNumRows = totalNumRows;
    
this.pageSize = list.size();
  
}
  
  
public boolean isRowAvailable() {
    
if(list == null)
      
return false;
    
    
int rowIndex = getRowIndex();
    
if(rowIndex >=&& rowIndex < list.size())
      
return true;
    
else
      
return false;
  
}

  public int getRowCount() {
    
return totalNumRows;
  
}

  public Object getRowData() {
    
if(list == null)
      
return null;
    
else if(!isRowAvailable())
      
throw new IllegalArgumentException();
    
else {
      
int dataIndex = getRowIndex();
      
return list.get(dataIndex);
    
}
  
}

  public int getRowIndex() {
    
return (rowIndex % pageSize);
  
}

  public void setRowIndex(int rowIndex) {
    
this.rowIndex = rowIndex;
  
}

  public Object getWrappedData() {
    
return list;
  
}
  
  
public void setWrappedData(Object list) {
    
this.list = (List) list;
  
}

}


Em alguns exemplos encontrados o construtor dessa classe possui 3 argumentos, a lista que será utilizada pelo dataModel, o número total de linhas que a tua consulta retornou e o número total de linhas que serão exibidos a cada página.

No meu exemplo o ultimo argumento foi retirado, pois por algum motivo a ultima página nunca era exibida. Digamos, minha paginação será de 10 em 10, possuo no banco 1903 registros, terei 190 paginas com 10 registros e uma página contendo 3 registros, essa página nunca era exibida. Testando percebi que se eu preenchesse os 10 valores com objetos vazios a página 191 era exibida. Para corrigir isso informo ao dataModel que o size da página será o size da lista.

Esse size é o “rows” que o dataTable possui, na paginação em memória setamos que seria 10 rows, na paginação em demanda esse valor se torna dinâmico.

Criado essa classe será necessário trazer alguns objetos para o teu Bean.
O primeiro seria um objeto do tipo DataModel e o segundo seria um HtmlDataTable, com seus get’s e set’s
O import do HtmlDataTable deverá ser igual ao componente que será utilizado na tela, se na tela você utilizar um h:dataTable então o import terá que ser o HtmlDataTable do JSF, se o componente for um rich:dataTable então o import terá que ser o HtmlDataTable do RichFaces.

 

Parte 2

 
1 Comment

Publicado por em dezembro 28, 2008 em Dicas, JSF

 

Java

Erro no Deploy de uma aplicação Seam no JBoss:
JSF1029: The specified InjectionProvider implementation ‘org.jboss.web.jsf.integration.injection.JBossInjectionProvider’ does not implement the InjectionProvider interface.

Correção:
Setar o valor para True no atributo useJBossWebLoader dentro do arquivo 
$JBOSS_HOME\server\default\jboss-web.deployer\META-INF\jboss-service.xml – No Jboss AS 4.2.3GA
No Jboss AS 5.0 o arquivo encontra-se em:
$JBOSS_HOME\server\default\deployers\jbossweb.deployer\META-INF\war-deployers-jboss-beans.xml

Aumentar Tempo de Start do JBoss no Eclipse:
Ganymede
Abrir a aba de server’s, clicar duas vezes em cima do server JBoss. Irá aparecer a tela de configurações do server. No lado direito terá Timeouts, clique e irá aparecer a opção de aumentar o tempo de start do server.

Europa
Windows>Preferences>Server>Server Timeout delay

Erro no deploy de aplicações JSF no JBoss:
Exception sending context initialized event to listener instance of class org.jboss.web.jsf.integration.config.JBossJSFConfigureListener

Correção:
Retire os Jar’s do JSF e JSF-impl de dentro da aplicação, coloque-os na pasta Cliente do JBoss AS.

No JBoss 5.0 a versão do jar do Facelets deverá ser a Facelets 1.1.15B1.jar ou a versão que vem junto com JBoss Seam mais recente. Caso utilize uma versão antiga o JBoss não irá renderizar o Arquivo e lançará uma exception na tela.

Erro
[hibernate] Error executing macro: outputValue
[hibernate] required parameter: property is not specified.
[hibernate] The problematic instruction:
[hibernate] ———-
[hibernate] ==> macro outputValue [on line 38, column 1 in util/TypeInfo.ftl]
[hibernate] in user-directive outputValue [on line 100, column 17 in view/view.xhtml.ftl]

Correção
Editar os arquivos:
view.xhtml.ftl
linha 100
Palavra identifier está escrito como indentifier
edit.xhtml.ftl
linha 127
Palavra identifier está escrito como indentifier
Alterar as palavras para identifier
Estes arquivos estão localizados em:
$SEAM_HOME\seam-gen\view

 
Leave a comment

Publicado por em dezembro 13, 2008 em Dicas

 

Tags:

 
Seguir

Obtenha todo post novo entregue na sua caixa de entrada.