ago

6

Java + FLEX Parte 3 -> Spring BlazeDS Integration + dpHibernate

By Felipe Saab

Neste post iremos ver como obter ainda mais produtividade no desenvolvimento de aplicações Java + FLEX adicionando o framework dpHibernate ao nosso projeto da Agenda, que foi criado na Parte 1 e melhorado na Parte 2 dessa série.

Para utilizarmos o dpHibernate com todo o seu potencial precisaremos melhorar um pouco mais a nossa agenda pois até agora temos apenas uma entidade no projeto: o Contato. Iremos adicionar a entidade Pessoa, sendo assim, Contato passará a ser uma entidade filha de Pessoa. Em outras palavras: uma Pessoa poderá ter nenhum ou muitos Contatos.

Mas vamos a um pouco de teoria antes (caso você já conheça o dpHibernate pode pular para a parte onde a ação começa a acontecer hehehe):

O QUE É dpHibernate?

De acordo com o site do projeto:

dpHibernate is a custom Flex Library and a custom BlazeDS Hibernate adapter that work together to give you support for lazy loading of hibernate objects from inside your flex applications.

dpHibernate é uma biblioteca para projetos Flex e um adaptador customizado do BlazeDS com suporte para o Hibernate que trabalham juntos para proporcionar suporte a lazy loading de objetos hibernate a partir da sua aplicação flex.

Quer dizer, o dpHibernate é composto por dois projetos: uma biblioteca que será referenciada no projeto FLEX (um arquivo .swc) e outra biblioteca que vai ser referenciada no projeto Java (arquivo .jar) que vão proporcionar o lazy loading de objetos do hibernate a partir da sua aplicação FLEX!!

Vamos facilitar ainda mais com um exemplo: :D

Vamos pensar nas entidades que vamos utilizar daqui a pouco: Pessoa e Contato.

Na classe Pessoa nós vamos ter uma lista de Contato:

public class Pessoa {
 
    // Outros atributos
 
    @OneToMany(fetch=FetchType.LAZY, ...)
    private List contatos;
 
    // Getters e setters
 
}

Se antes de enviarmos uma Pessoa do Java para o FLEX não executarmos o método getContatos() do objeto Pessoa a lista não será inicializada pois está marcada para ser carregada preguiçosamente (FetchType.LAZY), e consequentemente não será enviada para o FLEX.

OBS: isso acontece porque estamos utilizando o Spring BlazeDS Integration no nosso projeto, e ele possui essa funcionalidade, somente listas inicializadas são enviadas para o FLEX.

Mas o que acontece caso eu queira a lista de contatos no FLEX? Ou inicializamos a lista antes de enviar o objeto para o FLEX, ou seja, executar o getter da lista, ou então quando precisarmos da lista no FLEX nós fazemos mais uma chamada remota para carregar a lista.

Pensando em performance o correto é carregar a lista somente quando realmente precisarmos dela, quando abrir a tela dos contatos por exemplo.

E é exatamente esta a função do dpHibernate! A primeira vez que uma lista for acessada (na aplicação FLEX) o dpHibernate vai verificar se essa lista já foi carregada ou não e caso ela não tenha sido ele irá se encarregar de fazer uma chamada remota para carregá-la. Tudo isso de uma maneira completamente transparente para o programador! :D

Agora que já sabemos como o dpHibernate funciona vamos preparar a nossa agenda adicionando a entidade Pessoa.

ADICIONANDO A ENTIDADE PESSOA

Na Parte 2 fizemos uma agenda que funcionava em torno de apenas uma entidade: Contato. Ou seja, tinhamos um DAO e um Service para o Contato. Tudo era relacionado com Contato.

Agora vamos adicionar um relacionamento ao contato, vamos seguir o seguinte diagrama de classes:

Com isso a nossa aplicação não vai mais girar em torno de Contato, e sim em torno de Pessoa, ou seja, ao invés de um DAO e um Service para Contato, eles serão para Pessoa. Como Contato passou a ser uma entidade filha, ela vai ser gerenciada através do “seu pai”, a pessoa.

Como essa alteração vai ocupar um bom espaço aqui do post eu sugiro que baixe as classes (e o ddl para o banco) que utilizaremos como base.

Aproveitei e dei uma arrumada no layout da nossa aplicação, agora ela está assim:

Muito bem, agora que a aplicação já está com um relacionamento podemos partir para a parte interessante do post, configurar o dpHibernate! :D

CONFIGURANDO O dpHibernate

A primeira coisa a se fazer é baixar as bibliotecas. Elas podem ser pegas através do site do projeto ou podem ser pegas aqui no Java Simples.

Por que pegar as bibliotecas aqui? Pois eu corrigi um pequeno probleminha no projeto (descrito nesse link). O problema que acontece na última versão disponível do projeto é na deserialização de identificadores (o id da entidade) do tipo java.lang.Long, o deserializador do projeto se confunde e tenta converter o valor vindo do FLEX para o tipo java.lang.Integer. Apliquei um pequeno patch disponibilizado por um dos commiters do projeto e tudo passou a funcionar perfeitamente, portanto caso você – assim como eu – utilize o tipo java.lang.Long para os identificadores sinta-se a vontade para baixar as bibliotecas já corrigidas ou para aplicar o patch no projeto (os códigos fonte podem ser pegos através da página do projeto).

OBS: no pacote de bibliotecas disponíveis aqui no blog também estão algumas bibliotecas adicionais necessárias para o correto funcionamento do projeto.

Com as bibliotecas baixadas basta adicionar os arquivos .jar no classpath do projeto Java e o arquivo .swc ao classpath do projeto FLEX.

Mãos à obra então! :D

Vamos começar alterando nossas entidades (classes do modelo). Para permitir que as classes sejam monitoradas pelo dpHibernate precisamos fazer apenas algumas alterações nelas: as classes Java devem estender HibernateProxy e sobrescrever o método getProxyKey() retornando um identificador único (o id funciona muito bem não? hehe) e as classes AS devem esternder HibernateBean e receber o metadado [Managed].

Pessoa.java

/**
 * Clase que representa uma pessoa na agenda.
 * Cada pessoa poderá ter vários {@link Contato contatos} (email, telefone,
 * site, ...).
 *
 * @author Felipe Saab
 */
@Entity
@Table(name = "PESSOA")
public class Pessoa extends HibernateProxy {
 
	private static final long serialVersionUID = 3815773591498770002L;
 
	@Id
	@GeneratedValue(strategy=GenerationType.AUTO)
	@Column(name = "ID", length=11, nullable=false)
	private Long id;
 
	@Column(name = "NOME", nullable = false, length = 100)
	private String nome;
 
	@OneToMany(fetch = FetchType.LAZY, mappedBy = "pessoa", cascade = CascadeType.ALL, orphanRemoval = true)
	private List contatos;
 
	@Override
	public Object getProxyKey() {
        	return id;
	}
 
	// Getters e Setters e outros métodos
 
}

Contato.java

/**
 * Classe que representa um contato de uma {@link Pessoa pessoa} na agenda.
 *
 * @author Felipe Saab
 */
@Entity
@Table(name = "CONTATO")
public class Contato extends HibernateProxy {
 
	private static final long serialVersionUID = -1549466344815889020L;
 
	@Id
	@GeneratedValue(strategy = GenerationType.AUTO)
	@Column(name = "ID", length = 11, nullable = false)
	private Long id;
 
	@Column(name = "TIPO", length = 50, nullable = false)
	private String tipo;
 
	@Column(name = "CONTATO", length = 100)
	private String contato;
 
	@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "ID_PESSOA", referencedColumnName = "ID")
	private Pessoa pessoa;
 
	@Override
	public Object getProxyKey() {
		return id;
	}
 
	// Getters e Setters e outros métodos
 
}

Pessoa.as

[Managed]
[RemoteClass(alias="br.blog.javasimples.agenda.model.Pessoa")]
public class Pessoa extends HibernateBean
{
	public var id:Number;
	public var nome:String;
	public var contatos:ArrayCollection;
 
	public function Pessoa()
	{
	}
 
	public function addContato(contato:Contato):void
	{
		if (!contatos)
		{
			contatos = new ArrayCollection();
		}
		contatos.addItem(contato);
		contato.pessoa = this;
		contatos.refresh();
	}
 
	public function removeContato(contato:Contato):void
	{
		if (!contatos) return;
		var index:int = contatos.getItemIndex(contato);
		if ( index == -1 ) return;
		contatos.removeItemAt(index);
		contatos.refresh();
	}
}

Contato.as

[Managed]
[RemoteClass(alias="br.blog.javasimples.agenda.model.Contato")]
public class Contato extends HibernateBean
{
	public var id:Number;
	public var tipo:String;
	public var contato:String;
	public var pessoa:Pessoa;
 
	public function Contato()
	{
	}
}

Modelo preparado, podemos partir então para a configuração da aplicação web e do Spring:

A estrutura de pastas e arquivos da aplicação web ficou assim:

Passamos a ter 3 arquivos de configuração do Spring: WEB-INF\spring\applicationContext.xml, WEB-INF\spring\persistence-config.xml e WEB-INF\spring\flex-config.xml. Mas vamos entendê-los mais a fundo nas próximas linhas, vamos começar do começo: :D

web.xml

<!--?xml version="1.0" encoding="UTF-8"?-->
 
	<!-- Listener responsável por iniciar o container do Spring -->
 
		org.springframework.web.context.ContextLoaderListener
 
	<!-- Localização do arquivo de configuração para o listener configurar o 		container -->
 
		contextConfigLocation
		/WEB-INF/spring/applicationContext.xml
 
	<!-- Filtro para manter sempre - e apenas! - uma sessão do Hibernate aberta -->
 
		openSessionInViewFilter
		org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
 
	<!-- DispatcherServlet para o Spring BlazeDS Integration -->
 
		springMessageBroker
		org.springframework.web.servlet.DispatcherServlet
		1
 
			contextConfigLocation
			/WEB-INF/spring/flex-config.xml
 
	<!-- Mappings -->
 
		openSessionInViewFilter
		/*
 
		springMessageBroker
		/messagebroker/*

No deployment descriptor (vulgo web.xml hehehe) definimos que o Spring vai inicializar um container para a aplicação através do listener ContextLoaderListener, que o Hibernate vai ter somente uma sessão aberta durante toda a vida da nossa aplicação (isso é essencial para o lazy loading funcionar corretamente) através do filtro OpenSessionInViewFilter, e por fim definimos o DispatcherServlet do Spring MVC requerido pelo Spring BlazeDS Integration (como vimos na Parte 2).

Com isso já achamos onde são usados 2 arquivos de configuração do Spring: o applicationContext.xml vai conter os beans que poderão ser acessados de qualquer lugar da aplicação pois estarão no contexto da aplicação (ApplicationContext) e o flex-config.xml que é arquivo de configuração para o DispatcherServlet vai conter as configurações do Spring BlazeDS Integration.

OBS: caso não se lembre do servlet DispatcherServlet ou caso não conheça o Spring MVC eu sugiro que dê uma olhada no post: Spring Framework Parte 5 -> Spring Web MVC.

Vamos por partes então, primeiro o applicationContext.xml:

 

E de cara já vimos onde é utilizado o último arquivo de configuração, essa foi rápida né! hehehe

Os maiores motivos para a separação dos arquivos de configuração foi para eles ficarem mais legíveis e coesos, sendo assim o arquivo WEB-INF\spring\persistence-config.xml vai conter somente os beans relacionados à persistência dos dados. Para carregar esses beans no contexto da aplicação utilizamos a tag <import>, com isso todos os beans que estiverem dentro do arquivo persistence-config.xml também serão carregados no contexto da aplicação.

Mas no applicationContext.xml além da inclusão dos beans relacionados à persistência foram definidas também as tags responsáveis por permitir a injeção de dependência e o gerenciamento dos Services e DAOs através de anotações.

persistence-config.xml

<!--?xml version="1.0" encoding="UTF-8"?-->
 
	<!-- DataSource -->
 
			com.mysql.jdbc.Driver
 
			jdbc:mysql://localhost:3306/javasimples_agenda
 
			usuario
 
			senha
 
	<!-- Hibernate SessionFactory -->
 
		<!-- Carrega todos os Beans -->
 
				br.blog.javasimples.agenda.model
 
		<!-- Propriedades do Hibernate -->
 
				org.hibernate.dialect.MySQL5InnoDBDialect
				true
				true
 
	<!-- Habilita as transações nas operações do banco -->

Apesar do arquivo ser um pouco grande não há muita coisa a ser dita sobre esses beans, foram definidos um DriverManagerDataSource para conversar com o banco de dados, a SessionFactory responsável pelo mapeamento do Hibernate e o HibernateTransactionManager responsável pelas transações das operações do Hibernate.

Dando continuidade aos arquivos de configuração vamos ver os beans que serão definidos no escopo do DispatcherServlet do Spring BlazeDS Integration:

flex-config.xml

<!--?xml version="1.0" encoding="UTF-8"?-->
 
	<!-- Injeção de dependência (e outras configurações) através de anotações -->
 
	<!-- MessageBroker vai utilizar o adaptador do dpHibernate -->
 
	<!-- Adaptador remoto que vai interceptar as mensagens do FLEX e vai aplicar a lógica do dpHibernate -->
 
				{
				"dpHibernate" :
				{
				"serializerFactory" : "org.dphibernate.serialization.SpringContextSerializerFactory"
				}
				}
 
	<!-- Serviço que será utilizado para o lazy loading -->
 
	<!-- Cache que evita que um mesmo objeto seja serializado mais de uma vez -->
 
	<!-- Serializador e Deserializador que enviam (serializam) e recebem (deserializam) os objetos do FLEX -->
 
	<!-- Gerencia o CRUD das entidades. -->
 
	<!-- Utilizado nas operações CRUD para converter os proxys novamente para as entidades -->

Aqui é onde a mágica do dpHibernate acontece! hehehe

Começando de cima para baixo temos:

A injeção de dependência nesse contexto (DispatcherServlet) também poderá ser utilizada com anotações graças à tag <context:annotation-config />, definimos o MessageBroker (não lembra dele? dê uma lidinha rápida na Parte 1 então) através da tag <flex:message-broker> e definimos que o adaptador utilizado para processar as requisições será o dpHibernateRemotingAdapter (definido imediatamente abaixo).

Após o adaptador é definido o dataAccessService que vamos utilizar para cuidar das operações CRUD e do lazy loading das nossas entidades, com isso os nossos services vão conter apenas as regras de negócio envolvidas nas entidades, nada mais de CRUD nos services! :D

OBS: a tag <flex:remoting-destination /> é responsável por explicitamente definir o bean como um destination para o FLEX, ou seja, quando criarmos um RemoteObject podemos definir o destination = dataAccessService e essa classe (org.dphibernate.services.SpringDataAccessService) vai receber as requisições.

Logo em seguida é definida uma classe responsável por cachear os objetos serializados para evitar que eles sejam serializados mais de uma vez (assunto meio paralelo, então deixa pra outra hora.. heheh) e o serializador e o deserializador utilizados para converter os objetos Java para AMF e vice versa.

Depois temos o objectChangeUpdater que – de um jeito muito inteligente – cuida de todo o CRUD das entidades gerenciadas pelo dpHibernate. Aconselho fortemente a fazer checkout do código fonte e dar uma olhada. ;)

E por fim temos o hiberanteProxyResolver, classe responsável por manter a integridade dos objetos que ele vai proxiar entre as diversas operações que realizar. Toda classe gerenciada pelo dpHiberante possui uma proxyKey, tanto que sobrescrevemos o getter dessa chave nas entidades para garantir que seja retornado sempre um valor único. O dpHibernate utiliza essa chave para saber qual objeto foi enviado do FLEX para o Java e utilizar o objeto que já estava na sessão do hibernate, mantendo assim a integridade dos dados. Bem simples e bem legal não? :D

Bom, é isso! Com isso terminamos de configurar o dpHibernate do lado servidor da nossa aplicação. Vamos passar então para o lado do cliente, a aplicação FLEX:

AgendaF.mxml

 

Nossa aplicação sofreu uma pequena mudança apenas para ter a interface melhorada, foram adicionados dois ícones na grid: um para mostrar os contatos e outro para remover a pessoa. Ambas funcionalidades serão vistas no próximo tópico, vamos nos atentar primeiro à configuração do dpHibernate.

É na função onApplicationComplete que nós faremos a única configuração no projeto FLEX: vamos criar um Service padrão para o dpHibernate poder utilizar para as operações CRUD. Isso é feito criando um HibernateRemoteObject que vai enviar as requisições para o service que definimos no contexto do DispatcherServlet: o dataAccessService.

E pronto! Java e FLEX configurados!! :D

SALVAR, ATUALIZAR E APAGAR REGISTROS

Vamos então utilizar as vantagens desse mundo de configurações né… hehehehe

Repare no código do arquivo AgendaF.mxml (listagem anterior) que quando o usuário clicar no botão Salvar será chamada a função salvaPessoa():

/**
 * Salva uma pessoa.
 */
private function salvaPessoa():void
{
	var pessoa:Pessoa=new Pessoa();
	pessoa.nome=txtNome.text;
	pessoa.contatos=new ArrayCollection();
 
	var token:AsyncToken=pessoa.save();
	token.addResponder(new mx.rpc.Responder(salvaPessoaSucesso, erro));
}
 
/**
 * Pessoa salva com sucesso.
 */
private function salvaPessoaSucesso(event:ResultEvent):void
{
	Alert.show("Pessoa salva com sucesso!");
 
	// Recupera a agenda com todas as pessoas
	this.getAgenda();
}

Para salvar ou atualizar (save ou update) uma entidade gerenciada pelo dpHiberante devemos apenas chamar a sua função save(). Ela funciona do mesmo modo que uma chamada a um método remoto, ou seja, retorna um AsyncToken referente à chamada para podermos saber se houve sucesso ou erro durante sua execução.

Se repararmos no código do botão da grid referente a apagar a pessoa veremos que ele chama a função apagaPessoa():

/**
 * Apaga a pessoa e seus contatos.
 */
public function apagaPessoa(obj:Object):void
{
	var pessoa:Pessoa=obj as Pessoa;
	if (pessoa)
	{
		var token:AsyncToken=pessoa.deleteRecord();
		token.addResponder(new mx.rpc.Responder(apagaPessoaSucesso, erro));
	}
}
 
/**
 * Pessoa apagada com sucesso.
 */
private function apagaPessoaSucesso(event:ResultEvent):void
{
	Alert.show("Pessoa apagada com sucesso!");
 
	// Recupera a agenda com todas as pessoas
	this.getAgenda();
}

Do mesmo modo que a função save() as entidades gerenciadas pelo dpHibernate passam a ter também a função deleteRecord() que é um tanto quanto intuitiva certo? hehehe Apaga do banco a entidade que chamou a função.

E com isso terminamos o cadastro básico de Pessoa, vamos agora colocar os contatos na aplicação:

Contatos.mxml

 

O formulário de contatos segue o mesmo padrão do de pessoa: ao clicar no botão salvar é criado um novo Contato, adicionado na lista de contatos da pessoa e então a pessoa é salva novamente para persistir o novo contato no banco (em cascata); para remover um contato nós apenas o removemos da lista de contatos da pessoa e o apagamos do banco. Simples assim! :D

Para mostrar a tela de contatos, perceba que quando o botão referente a essa função for clicado ele vai chamar a função mostraContatos():

/**
 * Abre uma nova janela com os contatos da pessoa selecionada.
 */
public function mostraContatos(obj:Object):void
{
	var pessoa:Pessoa=obj as Pessoa;
	if (pessoa)
	{
		var formContatos:Contatos=new Contatos();
		formContatos.pessoa=pessoa;
		PopUpManager.addPopUp(formContatos, this, true);
		PopUpManager.centerPopUp(formContatos);
	}
}

Criamos o formulário de contatos, passamos a pessoa que vai ter os contatos exibidos para dentro dele e por fim o adicionamos na tela utilizando o PopUpManager.

LAZY LOADING

Mas no fim das contas, como funciona o lazy loading que nós nem vimos acontecer? hehehehe

Repare na grid que mostra os contatos:

 

O dataProvider da grid é o atributo contatos do objeto pessoa. Quando o formulário está sendo carregado e a grid executa o primeiro get no atributo o dpHibernate já se encarrega de fazer uma chamada remota para carregar os contatos.

Quando utilizamos atributos que irão ser carregados preguiçosamente (lazy loading) no MXML (como na grid) não precisamos nos preocupar com absolutamente nada, o FLEX e o dpHibernate se encarregam de tudo. Mas e quando precisamos utilizar tais atributos no AS (em uma função)?

Todo objeto que sofre lazy loading dispara o evento LazyLoadEvent. Tal evento possui 3 tipos:

LazyLoadEvent.pending: é disparado antes de fazer a chamada remota que carregará o objeto;

LazyLoadEvent.failed: é disparado quando ocorre uma falha no processo de carregamento do objeto;

LazyLoadEvent.complete: é disparado quando o carregamento foi concluído, ou seja, a chamada remota retornou com os dados e o objeto já foi atualizado com tais dados.

Então vamos fazer uma pequena alteração na função que abre o formulário de contatos para ver se isso verdade:

/**
 * Abre uma nova janela com os contatos da pessoa selecionada.
 */
public function mostraContatos(obj:Object):void
{
	var pessoa:Pessoa=obj as Pessoa;
	if (pessoa)
	{
		var formContatos:Contatos=new Contatos();
		if (pessoa.contatos.length &gt; 0)
			pessoa.contatos.getItemAt(0).addEventListener(LazyLoadEvent.complete, formContatos.contatosCarregados);
		formContatos.pessoa=pessoa;
		PopUpManager.addPopUp(formContatos, this, true);
		PopUpManager.centerPopUp(formContatos);
	}
}

Tudo bem, concordo que não é um código muito bonito… hehehe Mas como vimos a pouco, quem dispara o evento é o objeto que vai ser carregado preguiçosamente, sendo assim precisamos acessar algum desses objetos para poder colocar um listener nele. Devemos sempre ter certeza de que existe pelo menos um contato a ser carregado para poder acessá-lo e colocar um listener nele pois caso não fizéssemos a verificação antes e tentássemos abrir o formulário de contatos para uma pessoa que não tem nenhum contato nós iriamos receber um erro em tempo de execução.

Devemos sempre tomar muito cuidado ao programar as funções em AS, qualquer errinho vai estourar uma mensagem não muito agradável para o usuário final.

Dando continuidade, o listener que adicionamos foi para chamar a função contatosCarregados() do formulário de contatos, vamos a ela então:

/**
 * Mostra uma mensagem avisando que os contatos foram carregados.
 */
public function contatosCarregados(event:LazyLoadEvent):void
{
	Alert.show("A lista de contatos foi carregada com sucesso!");
}

Com isso, a primeira vez que abrirmos o formulário de contatos de uma pessoa que possua pelo menos um contato a ser carregado iremos receber a mensagem:

CARREGAMENTO EM LOTES (BATCH LOADING)

Uma última coisa (e não menos importante) para fecharmos esse post: carregamento em lotes.

Sem muita teoria, vou demonstrar com exemplos pois vai ser muito mais facil de entender né. :)

Eu criei uma pessoa e adicionei 3 contatos a ela, na próxima vez que eu entrar no sistema e abrir o formulário de contatos dela e seus contatos forem carregados reparem na saída impressa no console (ou arquivos de log) do tomcat:

{operation})****************loadBean
{deserialize} 0
Hibernate:
    select
        contato0_.ID as ID0_0_,
        contato0_.CONTATO as CONTATO0_0_,
        contato0_.ID_PESSOA as ID4_0_0_,
        contato0_.TIPO as TIPO0_0_
    from
        CONTATO contato0_
    where
        contato0_.ID=?
{invoke} 10
{serialize} 1
{operation})****************loadBean
{deserialize} 1
Hibernate:
    select
        contato0_.ID as ID0_0_,
        contato0_.CONTATO as CONTATO0_0_,
        contato0_.ID_PESSOA as ID4_0_0_,
        contato0_.TIPO as TIPO0_0_
    from
        CONTATO contato0_
    where
        contato0_.ID=?
{invoke} 15
{serialize} 0
{operation})****************loadBean
{deserialize} 0
Hibernate:
    select
        contato0_.ID as ID0_0_,
        contato0_.CONTATO as CONTATO0_0_,
        contato0_.ID_PESSOA as ID4_0_0_,
        contato0_.TIPO as TIPO0_0_
    from
        CONTATO contato0_
    where
        contato0_.ID=?
{invoke} 1
{serialize} 1

Cada vez que o dpHibernate imprime {operation})*********nome_da_operacao quer dizer que chegou uma chamada remota! Repare que até tem quanto tempo levou para deserializar a chamada e para serializar a resposta.

Quer dizer, 1 chamada remota para carregar cada contato… Vamos e venhamos, isso não é muito eficiente né.

Para suprir esse problema o dpHibernate consegue trabalhar com lotes de requisições (explicados detalhadamente pelo criador neste post), ou seja, ao invés de uma requisição para cada contato, todas são agrupadas em um lote e são enviadas para o servidor em apenas uma chamada remota. :D

Para habilitar isso precisamos de mais uns ajustes nas configurações (coisa rápida, prometo!).

Vamos adicionar um novo bean no contexto do DispatcherServlet do Spring BlazeDS Integration (arquivo WEB-INF\spring\flex-config.xml):

 

E na função onApplicationComplete() onde definimos o service padrão para o CRUD do dpHibernate vamos indicar que ele deve começar a enviar as requisições em lotes:

/**
 * Inicializações gerais.
 */
protected function onApplicationComplete(event:FlexEvent):void
{
	var remoteObject:HibernateRemoteObject=new HibernateRemoteObject("dataAccessService");
	remoteObject.bufferProxyLoadRequests=true;
	HibernateManaged.defaultHibernateService=remoteObject;
	this.getAgenda();
}

E pronto! :D Ao rodar a aplicação novamente, abrir o formulário de contatos da pessoa que possui 3 contatos e observar o console (ou log) do tomcat veremos a seguinte saída:

{operation})****************loadProxyBatch
{deserialize} 6
Hibernate:
    select
        this_.ID as ID0_0_,
        this_.CONTATO as CONTATO0_0_,
        this_.ID_PESSOA as ID4_0_0_,
        this_.TIPO as TIPO0_0_
    from
        CONTATO this_
    where
        this_.ID in (
            ?, ?, ?
        )
{invoke} 219
{serialize} 5

Apenas uma requisição que já carregou os 3 contatos!

Agora sim a nossa aplicação está 100%! Bonita, funcional e eficiente! :D

Os projetos completos (incluindo as biliotecas) podem ser baixados aqui (20MB).

CONCLUSÃO

É isso ai pessoal, o post acabou ficando um pouco longo mas creio que se você leu até aqui não ficou decepcionado com o tempo gasto, certo? hehehe :D

Um ponto que vale a pena ser comentado é em relação ao nosso Service e nosso DAO de Pessoa, depois de implantar o dpHibernate no nosso projeto vamos ver como eles ficaram:

/**
 * Interface que define os métodos de um serviço para {@link Pessoa}.
 *
 * @author Felipe Saab
 */
public interface PessoaService {
 
	/**
	 * Recupera todas as pessoas da agenda.
	 *
	 * @return Todos as pessoas
	 */
	List getAgenda();
}
/**
 * Interface que define os métodos necessários para o DAO de Pessoa.
 *
 * @author Felipe Saab
 */
public interface PessoaDao {
 
	/**
	 * Recupera todas as pessoas da agenda.
	 *
	 * @return Todos as pessoas
	 */
	List getAgenda();
 
}

Como ficou simples! Sem os métodos referentes ao CRUD (tudo bem, ainda tem um select ai.. dá um desconto poxa.. heheh) o Service e o DAO passam a ter somente a regra de negócio referente a entidade pela qual são responsáveis. A aplicação acaba se tornando mais legível! :D

Olhando agora para a nossa aplicação e tudo o que fizemos nela desde a Parte 1 dessa série podemos ver que utilizamos vários frameworks para aumentar a nossa produtividade: Spring, Hibernate, Spring BlazeDS Integration e dpHibernate. Com isso conseguimos construir uma aplicação como esta agenda em poucas horas (chegaria em horas mesmo? hehehe), desde o banco de dados até a interface agradável para o usuário final.

O dpHibernate é um framework poderosíssimo (li em blogs que já foi utilizado para projetos de grande porte) e open source! Caso esteja curioso sobre como ele funciona basta fazer checkout no repositório SVN na página do projeto (o que eu aconselho fortemente) e fuçar… muito legal mesmo!

Espero que tenha gostado deste post, todos os feedbacks são importantes, não deixe de comentar por favor. :D

PS: Caso esteja desenvolvendo um projeto com o dpHibernate sinta-se a vontade para me procurar caso esteja passando por alguns problemas, já passei por vários e você não precisa passar por eles também. =]

[]s e até a próxima,
Saab.

REFERÊNCIAS

[1] Using the ItemRenderer with a DataGrid

[2] Explicitly mapping ActionScript and Java objects

[3] Steps for getting going with dpHibernate

[4] Serialization issue with using primitive type long

[5] Batch loading proxies in dpHibernate

<?xml version=”1.0″ encoding=”UTF-8″?>
<web-app xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xmlns=”http://java.sun.com/xml/ns/javaee” xmlns:web=”http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd”
xsi:schemaLocation=”http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd”
id=”WebApp_ID” version=”2.5″><!– Listener responsável por iniciar o container do Spring –>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<!– Localização do arquivo de configuração para o listener configurar o container –>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/applicationContext.xml</param-value>
</context-param>

<!– Filtro para manter sempre – e apenas! – uma sessão do Hibernate aberta –>
<filter>
<filter-name>openSessionInViewFilter</filter-name>
<filter-class>org.springframework.orm.hibernate3.support.OpenSessionInViewFilter</filter-class>
</filter>

<!– DispatcherServlet para o Spring BlazeDS Integration –>
<servlet>
<servlet-name>springMessageBroker</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>

<!– Mappings –>

<filter-mapping>
<filter-name>openSessionInViewFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>

<servlet-mapping>
<servlet-name>springMessageBroker</servlet-name>
<url-pattern>/messagebroker/*</url-pattern>
</servlet-mapping>
</web-app>

7 Responses so far

Excelente posts (1, 2 e 3)!

Esse dpHibernate é uma mão-na-roda ;)

abs e Sucesso!!!

Parabéns pela iniciativa. Muito, muito bom mesmo os 3 artigos.
Ótima fonte de estudos, e super atual também.

Parabéns, mais uma vez. E muito obrigado pelo conteúdo disponibilizado.

Abraços.

Great writeup!

It’s worth mentioning that when you call save(), dpHibernate efficiently detects only the changed properties, and serializes those, rather than sending the full object graph.

This can lead to dramatically smaller payload sizes when calling save(), espeically with dense or complex object graphs.

Again — great writeup!

Marty

Hey Marty,

Thanks for the reply! Nice to see dpHibernate’s designer reading this post =]

Thanks in advance for the addon about the save() method. Great information!

[],
Saab.

Grande felipe,
tutoriais ótimos, os 3.
estou desenvolvendo 1 aplicação de estudo e tentando integrar o dphibernate na mesma,

falanado genéricamente, em uma aplicação qualquer para integrar o dphibernate,depois de ter adicionado as libs no java, configurar o web.xml, applicationContext.xm,persistence-config.xml,flex-config.xml,
extender as entidades para HibernateProxy,
e as .as para
HibernateBeane anotar [Managed]
fiquei com uma dúvida, na configuração na parte do flex
eu preciso carregar o onApplicationComplete abaixo apenas na view principal do programa??
(ah existem algumas listagens que estão vazias aqui no tutorial)
protected function onApplicationComplete(event:FlexEvent):void
{
var remoteObject:HibernateRemoteObject=new HibernateRemoteObject(“dataAccessService”);
remoteObject.bufferProxyLoadRequests=true;
HibernateManaged.defaultHibernateService=remoteObject;

}

agradeço desde já atenção
douglas

é que voce cria aquele remoteObject
e não entendi onde você usou ele dentro do código ??

Douglas,
Perceba que a última linha do método é:

HibernateManaged.defaultHibernateService=remoteObject;

Setamos o remoteObject em uma propriedade estática da classe HibernateManaged para o dpHibernate utilizar internamente.

Nós não utilizaremos explicitamente esse remoteObject pois ele se comunica com o service dataAccessService no Java, ou seja, é o service que contém os métodos necessário para o dpHibernate trabalhar corretamente.

Como é um atributo estático nós precisamos setar ele apenas uma vez antes de tentar executar os métodos save (e tentar utilizar o lazyLoading) de qualquer entidade. O listener do evento APPLICATION_CREATED é um bom local pois é uma das primeiras funções a serem executadas quando o sistema é carregado.

Leave a comment