Hibernate Efetivo – erros comuns e soluções

Neste último final de semana, dias 4 e 5 de Agosto, ocorreu a terceira edição do QCon São Paulo, na qual reuniu 1000 participantes em dois dias de evento.

Diferentemente das últimas duas edições, na qual marquei presença como espectador, este ano eu tive a oportunidade de palestrar sobre o tema Hibernate Efetivo – erros comuns e soluções.

A edição deste ano superou as edições anteriores em vários aspectos, como organização, qualidade do wi-fi, nível das palestras e lightning talks, discussão nos corredores e claro, um excelente coffee-break. Devo parabenizar os organizadores pelo evento, principalmente pelo apoio dado a caravana da JavaCE, que este ano marcou presença em peso.

Sendo, segue os slides da minha apresentação, que por sua vez poderá ser baixada no formato PDF:

Durante a apresentação houve uma demo mostrando a diferença de performance na utilização do Hibernate em processamentos batch com e sem o uso da StatelessSession.

Uma das coisas que me deixou hiper-super feliz no evento foi a incrível oportunidade de ser entrevistado pela InfoQ Brasil sobre minha palestra, citando algumas boas e más práticas ao se trabalhar com Hibernate e problemas de performance. Vale a pena conferir!

No blog da Caelum você pode encontrar um excelente resumão do evento, além dos keynotes, e da maioria das palestras e lightning talks já divulgadas até o momento.

Gostaria de parabenizar novamente a Caelum pela organização e sucesso desta edição e, principalmente, pela oportunidade que me foi dada para palestrar neste grande evento.

Utilizando AJAX com JSF de maneira eficiente

Graças a natureza orientada à componentes e eventos do JavaServer Faces podemos implementar aplicações com AJAX de maneira simples e prática. Existem vários frameworks e conjuntos de componentes AJAX disponíveis para JSF, uns mais simples e super fáceis de integrar, outros mais chatinhos, porém bastante eficientes em determinados cenários. Entre eles o mais utilizado e prático sem dúvida nenhuma é o Ajax4jsf que é trazido juntamente com o conjunto de componentes Richfaces da JBoss, com ele é possível inserir recursos AJAX em aplicações JSF de forma transparente e bastante produtiva.

Mesmo com a toda a simplicidade e gama enorme de componentes/frameworks AJAX ainda assim estes recursos não são aproveitados de forma eficiente ao se desenvolver com JSF, muitos desenvolvedores mesmo utilizando-se destes recursos continuam pensando de forma “click’n’wait”, o que no final das contas acaba meio que “anulando” os benefícios oferecidos por tais recursos.

Na onda de aplicações ricas e WEB2.0 não podemos deixar de aproveitar certos recursos essenciais para desenvolver tais aplicações. Utilizar estes recursos em JSF de forma eficiente trará ganhos consideráveis em vários aspectos para o projeto, à equipe de desenvolvimento e principalmente ao cliente, entre eles podemos destacar aspectos como performance, escalabilidade, produtividade e principalmente usabilidade.

Mas fica a pergunta: Como conseguir isso?

Abandonando a velha escola

O que precisamos fazer é deixar de lado o pensamento em interfaces “click’n’wait”, deixar de lado o desenvolvimento baseado em “action-like”, abandonar a navegação dirigida à páginas, precisamos aproveitar melhor os recursos AJAX oferecidos por frameworks e componentes ricos, inserir maior usabilidade nas páginas, aprender um pouco sobre AJAX Patterns e obter mais produtividade ao desenvolver páginas com JSF.

Com estes pensamentos em mente já temos uma melhor direção para um desenvolvimento mais eficiente. Para obter isso eu pretendo me utilizar de certas premissas básicas, como:

  • Utilizar um conjunto de componentes ricos e um framework AJAX. (Neste caso eu optei pelo Richfaces/Ajax4jsf por ser bastante simples de usar e integrar, por ser estável e por ser bastante utilizado na comunidade, contudo, qualquer outro conjunto de componentes AJAX poderia ser utilizado, como Trinidad ou mesmo o IceFaces);
  • Desenvolver as regras de navegação entre views orientada à estados e não à páginas;
  • Inserir um maior controle de fluxo de navegação e inteligência nos managed beans;
  • Utilizar algum framework de templating. (Optei pelo Facelets por ser simples, prático, produtivo, estável, ter uma excelente performance e principalmente por ter sido desenvolvido para JSF);
  • Diminuir o número de páginas (xhtml, jspx, jsp etc) no projeto;
  • Evitar reloads/refresh de páginas ao máximo, ou sempre que possível;

Observando a lista acima fica fácil ver que a maioria dos itens se complementam e colaboram entre si, porém o mais importante sem dúvida é o 2º item, “Desenvolver as regras de navegação entre views orientada à estados e não à páginas”, acho que este item resume 90% do que pretendo falar. Baseado nestas premissas básicas vou implementar uma aplicação bem simples (CRUD) e comentar certos pontos nela que facilitarão o entendimento.

Desenvolvendo as regras de navegação orientada à estados

Primeiramente devemos tirar da cabeça a maneira convencional de criar as regras de navegação orientada à páginas, ou seja, ter que desenvolver 3 ou 4 páginas para implementar um CRUD por exemplo, páginas como searchlist.jsp, add.jsp, update.jsp e show.jsp (algumas vezes se resume em até 2 páginas) devem ser descartadas. Ter várias páginas significa neste caso ter várias árvores de componentes, significa que o framework precisa gerar novas árvores de componentes a cada mudança de página, e isso custa memória e processamento no servidor, sem falar consumo de banda e overhead no cliente (browser).

Sendo, substituiremos essa abordagem da velha escola por outra mais eficiente, orientada à estados, em que utilizaremos somente uma única página que terá seu fluxo de navegação gerenciado por um managed bean mais inteligente.

A idéia principal tem por objetivo evitar escrever muitas páginas para representar cada view (tela), diminuindo assim o número de páginas no projeto, o número de regras de navegação no faces-config.xml, diminuindo o overhead no servidor e no cliente a cada refresh de página, obtendo uma melhor performance utilizando sempre a mesma árvore de componentes, inserir maior usabilidade na aplicação para o usuário, e claro, aproveitar eficientemente os recursos AJAX em cada página.

Logo, eu criei um template bem simples com Facelets para nossas páginas de CRUDs, com este template dá para ter uma idéia de como poderíamos escrever páginas bem melhores para nossas aplicações, o template (parte dele) seria algo do tipo:

[myCrudTemplate.xhtml]

<body>
	<a4j:outputPanel ajaxRendered="true" id="mainOutputPanel">
		<a4j:form>
			<!-- bloco de pesquisa -->
			<rich:panel id="searchlistBlock"
						rendered="#{defaultBean.pesquisarState}">
				<f:facet name="header">
					Pesquisar
		        </f:facet>
				<ui:insert name="searchlistBlock">
				</ui:insert>
			</rich:panel>
			<!-- bloco de edição -->
			<rich:panel id="addUpdateBlock"
						rendered="#{defaultBean.adicionarState or defaultBean.editarState}">
				<f:facet name="header">
					Dados do Usuário
		        </f:facet>
				<ui:insert name="addUpdateBlock">
				</ui:insert>
			</rich:panel>
		</a4j:form>
	</a4j:outputPanel>
</body>

Nosso template se resume em dois blocos, um bloco para pesquisa e exibição dos resultados (searchlistBlock):
SearchlistBlock

e outro bloco para o formulário de inclusão e edição de cada registro (addUpdateBlock):
AddUpdateBlock

Estes blocos substituem as páginas que a velha escola sempre pregou, assim ao se pensar em navegar entre views (telas) nós não mais navegaremos entre páginas (recursos físicos), mas sim entre blocos (recursos lógicos) dentro daquela mesma página. Estes blocos serão gerenciados por estado(s) no managed bean e não mais regras de navegação do faces-config.xml, assim sempre que quisermos “mudar de tela” nós alteramos o estado atual no managed bean e atualizamos parte da página com AJAX escondendo os blocos desnecessários e exibindo os blocos correspondentes à nova view.

Reparem no template que os blocos somente serão renderizados (através do atributo rendered do componente rich:panel) de acordo com o estado atual da view.

Meu managed bean será como qualquer outro, porém eu terei um atributo para representar o estado atual da view (tela) e alguns métodos para expor este estado de maneira legível, logo meu managed bean será algo como:

[UsuarioCrudBean.java]

public class UsuarioCrudBean {

	public static final String PESQUISAR_STATE = "pesquisar";
	public static final String ADICIONAR_STATE = "adicionar";
	public static final String EDITAR_STATE = "editar";

	private String currentState = PESQUISAR_STATE;
	private String nome;
	private Usuario usuario;
	private List<Usuario> usuarios = new ArrayList<Usuario>();
	private UIPanel panelForm;
	// métodos gets e sets

	/**
	 * Pesquisa usuários por nome
	 */
	public void pesquisar() {
		this.setCurrentState(PESQUISAR_STATE);
		this.usuarios = UsuarioRepositorio.findUsuarioByNome(this.nome);
	}

	/**
	 * Prepara view adicionar
	 */
	public void prepareAdicionar() {
		this.clear();
		this.setCurrentState(ADICIONAR_STATE);
	}

	/**
	 * Adiciona usuário
	 */
	public void adicionar() {
		UsuarioRepositorio.add(this.usuario);
		this.pesquisar();
	}

	/**
	 * Prepara view editar
	 */
	public void prepareEditar() {
		this.setCurrentState(EDITAR_STATE);
	}

	/**
	 * Edita usuário
	 */
	public void editar() {
		// já edita diretamente na lista (a lista está como static)
		this.pesquisar();
	}

	/**
	 * Exclui usuário
	 */
	public void excluir() {
		UsuarioRepositorio.delete(this.usuario);
		this.pesquisar();
	}

	/**
	 * Referente ao botão voltar
	 */
	public void voltar() {
		this.clear();
		this.pesquisar();
	}

	/**
	 * Limpa atributos
	 */
	private void clear() {
		this.nome = "";
		this.usuario = new Usuario();
		this.cleanSubmittedValues(this.panelForm);
	}

	/*
	 * Métodos que expõem o estado à página
	 */

	public boolean isPesquisarState() {
		String state = this.getCurrentState();
		return (state == null || PESQUISAR_STATE.equals(state));
	}
	public boolean isAdicionarState() {
		return ADICIONAR_STATE.equals(this.getCurrentState());
	}
	public boolean isEditarState() {
		return EDITAR_STATE.equals(this.getCurrentState());
	}

	/**
	 * Limpa os componentes filhos para que depois eles possam ser recriados
	 * @param component
	 */
	protected void cleanSubmittedValues(UIComponent component) {
		component.getChildren().clear();
	}
}

As regras de navegação serão gerenciadas internamente pelo managed bean, a página (jsp, xhtml etc) deve trabalhar em cima dos métodos expostos pelo managed bean, e não do atributo currentState em si, ou seja, para a página não importa como está implementada as regras de navegação, a página somente precisa conhecer qual o estado atual para que assim ela possa renderizar ou não seus componentes.

Nossa página de CRUD utilizará o template myCrudTemplate.xhtml como base, segue abaixo o código fonte da página somente com o bloco de edição:

[usuarioCrud.xhtml]

<ui:composition template="/pages/myCrudTemplate.xhtml">

	<!-- passa parâmetro para o template -->
	<ui:param name="defaultBean" value="#{usuarioCrudBean}" />

	<ui:define name="title">
		Cadastro de Usuários
		<t:saveState id="_bean" value="#{usuarioCrudBean}" />
	</ui:define>

	<ui:define name="searchlistBlock">
		<!-- bloco de pesquisa -->
	</ui:define>

	<ui:define name="addUpdateBlock">
		<h:panelGrid columns="2" id="panel2" binding="#{usuarioCrudBean.panelForm}" columnClasses="odd-row,even-row">
			<h:outputLabel value="Login: "/>
			<h:column>
				<h:inputText value="#{usuarioCrudBean.usuario.login}" size="30" required="true" id="login"
							requiredMessage="Campo Login é obrigatório."
							readonly="#{usuarioCrudBean.editarState}"
							style="#{usuarioCrudBean.editarState ? 'color: gray;' : ''}"/>
				<br/>
				<h:message for="login" errorStyle="color: darkred;"/>
			</h:column>
			<h:outputLabel value="Senha: "/>
			<h:column>
				<h:inputSecret value="#{usuarioCrudBean.usuario.senha}" size="30" redisplay="true"/>
				<br/>
			</h:column>
			<h:outputLabel value="Nome: "/>
			<h:column>
				<h:inputText value="#{usuarioCrudBean.usuario.nome}" size="60" required="true" id="nome"
							requiredMessage="Campo Nome é obrigatório." />
				<br/>
				<h:message for="nome" errorStyle="color: darkred;"/>
			</h:column>
			<h:outputLabel value="Blog: "/>
			<h:column>
				<h:inputText value="#{usuarioCrudBean.usuario.blog}" size="90" id="blog"/>
			</h:column>
			<h:outputLabel value="Ativo?"/>
			<h:column>
				<h:selectOneMenu value="#{usuarioCrudBean.usuario.ativo}" id="ativo"
						requiredMessage="Campo Ativo é obrigatório."
						required="true">
					<f:selectItem itemValue="" itemLabel="[Selecione uma opção]" />
					<f:selectItem itemValue="#{true}" itemLabel="Sim" />
					<f:selectItem itemValue="#{false}" itemLabel="Não" />
				</h:selectOneMenu>
				<br/>
				<h:message for="ativo" errorStyle="color: darkred;"/>
			</h:column>
		</h:panelGrid>
		<a4j:commandButton value="Adicionar" action="#{usuarioCrudBean.adicionar}" rendered="#{usuarioCrudBean.adicionarState}"/>
		<a4j:commandButton value="Editar" action="#{usuarioCrudBean.editar}" rendered="#{usuarioCrudBean.editarState}"/>
		<a4j:commandButton value="Voltar" action="#{usuarioCrudBean.voltar}" immediate="true"/>
	</ui:define>
</ui:composition>

Assim como nossos blocos principais (searchlistBlock e addUpdateBlock) do template, muitos componenentes também são renderizados ou mudam de estado dependendo do fluxo de navegação, um deles por exemplo é o botão “Editar” que somente será renderizado quando nossa view estiver no estado de edição:

<a4j:commandButton value="Editar" action="#{usuarioCrudBean.editar}" rendered="#{usuarioCrudBean.editarState}"/>

Na minha página usuarioCrud.xhtml eu estou passando a referência do managed bean atual como parâmetro para o template para que ele consiga gerenciar os blocos corretamente, isso me possibilita ter um template mais genérico para minhas páginas de CRUDs:

<ui:param name="defaultBean" value="#{usuarioCrudBean}" />

Atentem que eu estou utilizando o componente t:saveState do Myfaces Tomahawk para ampliar o escopo de conversação, com ele eu posso manter meus managed beans como escopo de request enquanto eu navego entre os blocos lógicos, depois de mudar literalmente de página (recurso físico) eu terei aqueles dados removidos da memória do servidor.

<t:saveState id="_bean" value="#{usuarioCrudBean}" />

Outra coisa interessante é que por estarmos sempre trabalhando sob a mesma árvore de componentes (viewRoot) é necessário resetar o estado de alguns componentes ou bloco de componentes quando necessário, pois mesmo que estes componentes não sejam renderizados eles ainda assim mantém o estado anterior.

No nosso caso eu estou sempre limpando o estado do formulário de edição ao clicar no botão “Voltar” da aplicação, que por sua vez faz uma chamada ao método cleanSubmittedValues(), é este método que se encarrega de limpar o estado anterior do formulário:

protected void cleanSubmittedValues(UIComponent component) {
	component.getChildren().clear();
}

Existem outras abordagens para resetar o estado dos componentes de um formulário, eu busquei utilizar a maneira mais simples, porém creio que não seja a mais performática -neste caso-. Mesmo não possuindo a melhor performance ainda assim é -várias vezes- mais rápida que gerar todo uma nova árvore de componentes (utilizado pela velha escola), com toda certeza.

E quais seriam as vantagens dessa abordagem?

Existem várias vantagens em relação a abordagem da velha escola, entre elas podemos destacar:

  • Menor consumo de memória e processamento no servidor, pois estaremos sempre trabalhando sob a mesma árvore de componentes (viewRoot);
  • Menor overhead no cliente (browser), pois estaremos evitando reload completo de páginas;
  • Menor tráfego de dados na rede, muito menor, pois estaremos apenas transportando pequenos blocos de página;
  • Maior escalabilidade e performance na aplicação;
  • Diminui drasticamente o número de pontos de manutenção na aplicação (páginas, arquivos de configuração etc);
  • Renderização dos componentes muito mais rápida, sejam parciais ou grandes blocos lógicos;
  • Maior usabilidade e controle de renderização parcial nas páginas;
  • Evita o problema do botão voltar (back button);
  • Evita resubmissão do formulário após um refresh (F5);
  • Managed beans realmente mais inteligentes;
  • Exibição das páginas com um aspecto mais leve, possibilitando ainda a utilização de efeitos através de frameworks javascripts;
  • Facilidade em criar classes de managed beans mais genéricas (um CrudBean por exemplo);
  • Melhor controle/tratamento de respostas entre cliente e o servidor (barra de progresso, timed out etc);

Bem, vantagens é o que não falta em relação a velha escola. Provavelmente possamos discutir mais algumas vantagens e até quem sabe desvantagens na lista de discussão do JavaSF.

Concluindo

Mesmo esta abordagem sendo superior a abordagem da velha escola ela ainda assim não é a “bala de prata”, como toda nova abordagem ela deve ser estudada e utilizada com bom senso, haverá momentos em que será mais prático e até melhor efetuar um reload completo de página, ou dividir os blocos lógicos em pequenas páginas para evitar grandes arquivos físicos, enfim, bom senso é algo que sempre deve ser levado em consideração.

Eu não me prendi a utilização dos frameworks ou conjuntos de componentes utilizados pois este não era o intuito do post, isso é algo simples e acredito que a maioria dos leitores não tenham dificuldades em relação a isso. Além do mais é possível implementar isso não somente com o Richfaces/Ajax4jsf, mas com qualquer outro framework ou conjuntos de componentes AJAX, por exemplo, no último projeto que participei implementamos nossas páginas utilizando-se desse conceito com Myfaces Trinidad, que por sinal está funcionando muito bem.

É bastante notório a velocidade na renderização das páginas, sendo aconselho vocês a fazerem alguns testes com esta abordagem e observarem o quanto mais rápido se torna o desenvolvimento e a renderização das páginas. Eu fiz a aplicação o mais simples possível para demonstrar essa abordagem, com certeza ainda é possível otimizar as requisições AJAX através de configurações do Ajax4jsf e do Richfaces, você pode ver mais sobre isso na documentação do mesmo.

Eu estou disponibilizando esta aplicação exemplo como prova de conceito, é um projeto do Eclipse (MyEclipse para ser mais exacto), sendo basta importa-lo e efetuar o deploy da aplicação.