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.
Bem, aqui termina essa série de posts destinada a mostrar a operação básica de CRUD usando Spring, JPA, JSF e Primefaces, com atualização automática independente de estação cliente.
O código deste projeto está disponibilizado aqui:
http://code.google.com/p/livraria-jsf/source/browse/LivrariaVeja o vídeo com a demonstração deste aplicativo: