Este post faz parte de uma série
que demonstra o uso de Primefaces, Spring, JPA, JSF para criar uma
aplicação que atualiza as DataTables dos clientes automaticamente.
Confira os outros posts da série aqui: Primefaces
Vamos agora introduzir um mecanismo que permite atualizar dinamicamente os p:dataTable em qualquer cliente da aplicação. Para isso vamos criar um segundo managed bean no pacote controle do projeto, chamado aplicacao:
package controle; import delegate.FacadeBD; import java.io.Serializable; import javax.faces.bean.ApplicationScoped; import javax.faces.bean.ManagedBean; import javax.faces.bean.ManagedProperty; @ManagedBean(name = "aplicacao") @ApplicationScoped public class Aplicacao implements Serializable { @ManagedProperty(value = "#{facadeBD}") private FacadeBD facadeBD; public Aplicacao() { } public FacadeBD getFacadeBD() { return facadeBD; } public void setFacadeBD(FacadeBD facadeBD) { this.facadeBD = facadeBD; } }
Por enquanto, a única observação relevante é que o escopo deste bean é @AplicationScoped, ou seja, todos os seus atributos e métodos estão disponíveis a qualquer cliente da aplicação, a partir do momento em que o servidor ativar a aplicação.
No escopo @ViewScoped que utilizamos no bean controlador, os atributos e métodos do bean estão disponíveis separadamente para cada cliente de cada sessão. Isso implica em várias cópias (no servidor) do bean, uma para cada cliente que estiver executando a aplicação, o que causa o efeito de um cliente não ver o que o outro fez sem recorrer a uma nova leitura do banco de dados. E o pior de tudo, se quisermos manter disponível uma lista com grande quantidade de dados, haverá uma cópia da lista para cada cliente, sobrecarregando a memória do servidor.
Então, para continuar com nosso exemplo, devemos retirar do bean controlador a lista de autores e transferi-la para o bean aplicacao:
@ManagedBean(name = "aplicacao") @ApplicationScoped public class Aplicacao implements Serializable { @ManagedProperty(value = "#{facadeBD}") private FacadeBD facadeBD; private List<Autor> autores = new ArrayList<Autor>(); public Aplicacao() { } public List<Autor> getAutores() { if (autores.isEmpty()) { autores = facadeBD.listar(Autor.class); } return autores; } public void setAutores(List<Autor> autores) { this.autores = autores; }
É óbvio que em seguida retiramos o atributo autores e os get e set correspondentes do bean controlador. Em seguida, modificamos o bean controlador, injetando o controlador aplicacao como um atributo:
public class Controlador implements Serializable { @ManagedProperty(value = "#{facadeBD}") private FacadeBD facadeBD; @ManagedProperty(value = "#{aplicacao}") private Aplicacao aplicacao;
E em seguida criamos os get e set para o atributo aplicacao:
public Aplicacao getAplicacao() { return aplicacao; } public void setAplicacao(Aplicacao aplicacao) { this.aplicacao = aplicacao; }
Importante: podemos injetar um bean com escopo mais amplo em um bean com escopo mais restrito, mas não podemos fazer o contrário!
Com essa mudança, os métodos gravar e excluir devem ser modificados para apontar para o novo bean:
public void gravar(Entidade entidade) { facadeBD.salvar(entidade); aplicacao.getAutores().clear(); } public void excluir(Entidade entidade) { entidade.setFlagRemover(true); facadeBD.salvar(entidade); aplicacao.getAutores().clear(); }
E a página autores.xhtml também deve ler os dados usando o novo bean:
<p:dataTable id="Tabela" widgetVar="wTabela" rowIndexVar="rowNumber" rows="10" paginator="true" value="#{aplicacao.autores}" var="registro">
Nessa fase, já eliminamos o problema de ter várias cópias da lista de autores, mantendo uma única cópia durante o tempo em que o servidor estiver disponível. Mas ainda permanece a necessidade de ler o banco de dados todas as vezes em que quisermos obter as alterações mais recentes.
Vamos corrigir isso criando três métodos no bean aplicacao:
public void atualizarLista(Entidade e, Short operacao) { List l = null; if (e instanceof Autor) { l = autores; } if (l != null) { if (e.getId() == null) { l.add(e); } else { Integer indice = 0; Object objeto = null; for (Object o : l) { if (((Entidade) o).getId() == e.getId()) { objeto = o; break; } indice++; } if (indice <= l.size()) { if (operacao == EXCLUIR) { l.remove(objeto); } if (operacao == ATUALIZAR) { l.set(indice, e); } } } } } public void atualizarNaLista(Entidade e) { atualizarLista(e, ATUALIZAR); } public void removerDaLista(Entidade e) { atualizarLista(e, EXCLUIR); }
As constantes utilizadas devem ser declaradas no início da classe:
public class Aplicacao implements Serializable { public static Short ATUALIZAR = 1; public static Short EXCLUIR = 2; @ManagedProperty(value = "#{facadeBD}") private FacadeBD facadeBD;
Esses três métodos recebem um objeto que deve pertencer a uma subclasse de Entidade e decidem se o objeto passado deve ser inserido, atualizado ou excluído da lista correspondente. Com essa mudança, os métodos gravar e excluir do bean controlador devem mudar novamente:
public void gravar(Entidade entidade) { aplicacao.atualizarNaLista(autor); facadeBD.salvar(entidade); } public void excluir(Entidade entidade) { entidade.setFlagRemover(true); aplicacao.removerDaLista(entidade); facadeBD.salvar(entidade); }
Uma observação importante no método gravar: a lista deve ser atualizada antes do banco de dados. Caso contrário, se o registro for novo, o DAO vai mudar o valor da chave primária e o bean aplicacao não vai conseguir decidir se o registro tem que ser inserido ou alterado.
Agora, estamos com a seguinte situação: os dados são atualizados na tela de qualquer cliente, assim que o cliente fizer alguma atualização na tabela (por exemplo, gravar uma edição, ou remover um autor), mas não há mais uma nova leitura de todo o conteúdo a partir do banco de dados, porque após a operação de CRUD, apenas o objeto que foi modificado é atualizado na lista de autores.
Vamos então para o último passo: automatizar a atualização dos clientes! Isso é feito colocando um componente do Primefaces na página autores.xhtml:
</p:dialog> <p:poll interval="3" update="pnlTabela" /> </h:form> </h:body> </html>
O componente p:poll do Primefaces, mostrado acima, executa o update no componente pnlTabela a cada 3 segundos. Como o componente Tabela, que está dentro de pnlTabela, acessa a lista do bean aplicacao, a cada 3 segundos o cliente (seja lá quem for ou em que máquina estiver) terá uma tabela atualizada!
Para ver esse efeito, basta abrir dois navegadores, reduzi-los para metade da tela e alterar um autor em um dos navegadores. Após alguns segundos, o outro navegador atualizará automaticamente seus dados.
Não vamos nos aprofundar a respeito dos locks pessimísticos, otimísticos, concorrência, etc.. Se o leitor verificar todo o mecanismo utilizado na camada DAO e a atualização automática implementada aqui, perceberá que:
- duas ou mais edições ao mesmo tempo: vai prevalecer a que for gravada por último.
- uma edição em aberto e uma exclusão do mesmo registro, antes de gravar a edição: os dados da edição serão descartados, já que o registro foi excluído antes da gravação.
O código deste projeto está disponibilizado aqui: http://code.google.com/p/livraria-jsf/source/browse/Livraria
Veja o vídeo com a demonstração deste aplicativo:
Nenhum comentário:
Postar um comentário