Escolar Documentos
Profissional Documentos
Cultura Documentos
Autor:
Gilberto Augusto T. de A. Holms
gibaholms85@gmail.com
http://gibaholms.wordpress.com/
Sumário
1. Ferramentas Utilizadas
1.1. Java JDK
Para desenvolver aplicações Java Enterprise Edition, não precisamos de nenhum
ferramental especial. É necessário apenas algum JDK (Java Development Kit) e um
Servidor de Aplicação (Application Server) compatível com o JDK.
No momento em que foi criada esta apostila, a versão mais recente é a JDK 6 Update
13, disponível no link:
http://java.sun.com/javase/downloads/index.jsp
Note que a Sun também disponibiliza para download um pacote chamado JDK 6
Update 13 with Java EE. Este pacote é apenas um facilitador para iniciantes, pois já
vem com um servidor de aplicação, o GlassFish Enterprise Server, e também alguns
exemplos de mini-aplicativos JEE e tutoriais. O GlassFish é um Application Server
completo, disponibilizado pela Sun como open-source.
No momento em que foi criada esta apostila, a versão mais recente é o Eclipse
Ganymede SR1 – versão 3.4, disponível no link:
http://www.eclipse.org/home/categories/index.php?category=enterprise
No momento em que foi criada esta apostila, a versão mais recente é o JBoss
Application Server 5.0.1.GA, disponível no link:
http://www.jboss.org/jbossas/downloads/
6
Note que dentro de uma mesma versão são disponibilidadas para download diversas
distribuições do JBoss, em diversos formatos, ou com JDK6 incluso. Para o curso nós
utilizaremos a versão “pura”, disponível no formato zip, sem nenhum adicional.
7
1.4. MySQL
Para os exercícios e projetos do curso, utilizaremos a base de dados MySQL (agora
pertencente à Sun Microsystems). Atualmente, ela é disponibilizada em duas versões:
Enterprise e Community.
No momento em que foi criada esta apostila, a versão mais recente é o MySQL
Community Server 5.1, disponível no link:
2. Computação Distribuída
2.1. Por que utilizar sistemas distribuídos ?
o Dados são distribuídos
o Processamento é distribuído
o Usuários são distribuídos
Processamento é distribuído
Uma aplicação pode executar de forma distribuída para ter vantagem de utilizar
múltiplos processadores, em diferentes máquinas ou servidores, ou até mesmo tirar
proveito de recursos específicos de uma determinada máquina ou sistema
operacional. Com isso, são garantidas escalabilidade e alta disponibilidade do sistema.
Cada pedaço da aplicação pode estar presente em uma máquina, e estes pedaços
podem ser reutilizados e orquestrados de forma a compor uma aplicação.
o Camada Física
É a camada que efetivamente transfere os bits, onde podemos pensar
em placas de rede, roteadores e elementos transmissores.
o Enlace de Dados
É a camada que tem diversas características de gerência e controle
sobre os dados trafegados, que realiza multiplexações, detecção,
notificação e recuperação de erros.
o Protocolo IP
A sigla IP significa “Internet Protocol”, por ser o protocolo no qual é
baseada a rede mundial de computadores (Internet). Nada mais é que
um protocolo, único e padronizado mundialmente, que permite que
dados sejam transferidos pela rede através de pacotes de dados com
uma estrutura bem definida, com informações de roteamento,
endereçamento, headers e outras informações.
o TCP / UDP
Aqui começamos a nos atentar. Existem duas maneiras (protocolos) de
se transferir dados em uma rede IP, e cada uma tem suas
características e utilização apropriada:
Protocolo UDP
• Não orientado à conexão
• Não confiável
• Não garante entrega ao destinatário
• Utilizado para comunicação em tempo real (VoIP, Vídeo, etc)
Protocolo TCP
• Orientado à conexão
• Confiável
• Garante entrega ao destinatário
o Camada de Aplicação
É onde residem os aplicativos. Nesta camada temos os protocolos de
software. Diversos aplicativos conhecidos utilizam serviços de rede.
Entre eles, podemos citar: browsers web (protocolo HTTP),
gerenciadores de email (protocolos SMTP e POP3).
Os SOCKETS !
Sockets são um conjunto de funções (API) do sistema operacional que permitem que
os softwares utilizem um canal de comunicação sobre IP. E, logicamente, cada
linguagem de programação fornece o seu conjunto de APIs para manipulação dos
sockets do sistema operacional.
10
2.4. CORBA
Como pudemos observar nos exercícios apresentados em sala de aula, disponibilizar
objetos distribuídos nativamente através de sockets não é uma tarefa simples. Em
muitas linhas de código, onde não há transparência de localização, o cliente precisa
conhecer o servidor. Além de que implementar infra de segurança, alta disponibilidade
e pool de recursos demandaria muito esforço de desenvolvimento. Vimos também que
é muito difícil obter interoperabilidade, pois não há um padrão definido de protocolo de
comunicação, cada desenvolvedor de sistema poderia implementar de sua maneira.
Também não há transparência de linguagem de programação, ou seja, um objeto
distribuído Java seria acessado apenas por clientes Java, pois são utilizados os
recursos de serialização de objetos da própria linguagem para trafegá-los pela rede.
Exemplo de IDL:
module LocadoraObjects {
struct InformacoesFilme {
string nome;
string genero;
double preco;
};
11
exception LocadoraException{};
interface Filme {
InformacoesFilme getInformacoesFilme() raises(LocadoraException);
void setInformacoesFilme(in InformacoesFilme info);
};
};
Exemplo:
idlj –server –client teste.idl
ORB
Camada provedora de serviços (API) para implementação da arquitetura CORBA. Ela
é a intermediadora entre o objeto cliente e o objeto distribuído. Quando um objeto
cliente faz uma requisição a um objeto distribuído, ela localiza o objeto na rede, envia
a requisição ao objeto distribuído (através do protocolo IIOP), aguarda a resposta e a
repassa ao objeto chamador.
Skeletons
São os objetos gerados automaticamente pelo compilador IDL para servirem
requisições no lado do servidor. Eles são responsáveis pelo marshalling e
unmarshalling dos objetos para que o ORB possa enviá-los pela rede. Desta forma, o
desenvolvedor preocupa-se apenas em implementar suas operações de negócio, sem
se preocupar com serviços de infra estrutura.
Stubs
São os objetos gerados automaticamente pelo compilador IDL para enviarem
requisições por parte do cliente. Eles são responsáveis pelo marshalling e
unmarshalling dos objetos para que o ORB possa enviá-los pela rede. Da mesma
forma, o desenvolvedor não precisa se preocupar com serviços de infra estrutura para
acessar os objetos distribuídos.
IIOP
12
Cliente
Código Cliente
Stubs
Compilador
idl2Java
ORB
Arquivo IDL
Servidor ORB
Skeletons
Implementação do Objeto
POA
O “Portable Object Adapter” (POA) é um artefato evolutivo na arquitetura CORBA. Ele
fica entre a implementação do ORB e o objeto distribuído. Desta forma, com o POA
pode-se fazer com que os ORBs de diversos fabricantes rodem objetos distribuídos da
mesma forma, sem nenhuma mudança no código dos objetos. Além disso, o POA
fornece alguns serviços de controle de ciclo-de-vida, segurança, multithreading e
transiência/persistência.
• Binding
13
• Naming Context
Um conjunto único de bindings (nomes), onde não pode haver bindings com nomes
repetidos. O Naming Context é um objeto, portanto, também pode ser “bindado” ao
Naming Service, criando assim uma hierarquia de naming contexts (Naming Graph).
• Naming Graph
É quando criamos Naming Context dentro de outro Naming Context, criando assim
uma árvore de contextos.
Orb.resolve_initial_references("NameService")
Orb.resolve_initial_references("NameService")
Orb.object_to_string(objRef)
corbaloc:iiop:1.2@localhost:1050/Locadora
2.5. RMI
O RMI (“Remote Method Invocation”) é uma API criada como uma iniciativa para que
desenvolvedores pudessem escrever sistemas distribuídos com a mesma semântica
de sistemas comuns, com o intuito de agilizar e facilitar o desenvolvimento.
JRMP
É um protocolo nativo e específico para linguagem Java, ou seja, objetos distribuídos
via RMI-JRMP podem ser consumidos apenas por clientes Java (JVM to JVM).
Exemplo:
rmic br.curso.LocadoraImpl
IIOP
Como já estudamos no tópico sobre CORBA, o IIOP é o protocolo de comunicação da
arquitetura CORBA. Objetos distribuídos sobre RMI-IIOP possuem a vantagem de
executar sobre um ORB, assim sendo independentes de linguagem de programação.
Com o intuito de trazer para o Java a facilidade de programação do RMI fundida à
interoperabilidade do CORBA, a Sun, em conjunto com a IBM, criaram o RMI sobre
IIOP.
Para atender esses requisitos, compilador RMIC foi modificado, adicionando a opção
de geração de Skeletons e Stubs para protocolo IIOP.
Exemplo:
rmic –iiop br.curso.LocadoraImpl
Conceito de “codebase”
Para qualquer chamada remota, vimos que é necessário ter no classpath do cliente o
Stub, tal Stub que referencia a classe de implementação do objeto distribuído. Porém,
nem sempre é desejável ter este Stub localmente, pois a qualquer mudança na
implementação do objeto remoto, teríamos que recompilar todos os clientes com o
novo Stub que seria gerado.
Para evitar esse problema, ao rodar o nosso servidor java podemos setar um atributo
de JVM chamado “codebase”, indicando um local para a JVM do cliente localizar os
Stubs automaticamente em tempo de execução. Quando bindamos um objeto remoto
no RMI Registry, este local é gravado e enviado aos aplicativos que fizerem lookup do
objeto.
Exemplos:
java -Djava.rmi.server.codebase=http://server/public/stubs.jar
java -Djava.rmi.server.codebase=http://server/stubs/
Obs.: este conceito é válido apenas para RMI-JRMP. Não faz sentido um cliente IIOP
fazer download automático de Stubs, pois um cliente IIOP pode ser escrito em
qualquer linguagem de programação, e cada linguagem terá o seu Stub específico.
O JNDI está para os “serviços de nomes e diretórios” assim como o JDBC está
para as “bases de dados”.
Com o JNDI, no que tange aos serviços de nomes e diretórios, temos os mesmos
benefícios que o JDBC fornece no que tange as bases de dados, onde a principal
delas é a independência de implementação.
Context.INITIAL_CONTEXT_FACTORY
Initial Context
É o contexto inicial do serviço de nomes e diretórios. É a “raíz” do sistema, o “root”, o
“ponto de partida” para a navegação entre os nós da árvore de nomes.
A nível de código, ele é representado como uma classe em que seu construtor recebe
um java.util.Properties como parâmetro. É através dele que configuramos a
implementação que desejamos utilizar.
Exemplo:
Properties prop = new Properties();
prop.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
prop.put(Context.PROVIDER_URL, "jnp://127.0.0.1:1099");
LDAP:
Properties prop = new Properties();
prop.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
prop.put(Context.PROVIDER_URL, "ldap://localhost:389");
*incluso no JDK
RMI Registry:
Properties prop = new Properties();
prop.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
prop.put(Context.PROVIDER_URL, "rmi://localhost:1099");
*incluso no JDK
DNS:
Properties prop = new Properties();
prop.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.dns.DnsContextFactory");
prop.put(Context.PROVIDER_URL, "dns://server1.sun.com/java.sun.com");
prop.put(Context.AUTHORITATIVE, "true");
*incluso no JDK
File System:
Properties prop = new Properties();
Prop.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.fscontext.RefFSContextFactory");
*não incluso no JDK
Obs.: para SPIs que requerem usuário e senha, utilizamos os seguintes parâmetros:
prop.put(Context.SECURITY_PRINCIPAL, "usuario");
prop.put(Context.SECURITY_CREDENTIALS, "xxxxxxx");
Context
É qualquer contexto do serviço de nomes e diretórios. Como ilustrado no capítulo
sobre COS Naming Service, um contexto pode conter sub-contextos. A característica
principal de um contexto é que ele não pode possuir nomes repetidos para
associações de recursos.
E isso faz todo o sentido, pois um recurso deve ser identificado de forma única.
Logicamente uma mesma pasta não pode conter dois arquivos com o mesmo nome,
ou um domínio não pode conter dois sub-domínios diretos com o mesmo nome.
Toda e qualquer operação de manuseio de registros de nomes é feita de forma relativa
a um contexto.
Bind
Operação de gravar uma associação nome-objeto em um contexto. Se o nome já
existir, é lançada uma javax.naming.NamingException.
Rebind
Operação de regravar uma associação nome-objeto em um contexto. A diferença para
o Bind é que, se o nome já existir, ele é sobrescrito.
Unbind
Operação de remover uma associação nome-objeto de um contexto.
20
Lookup
Operação de localizar uma associação nome-objeto em um contexto. Lembrando que
será feito sempre relativo a um contexto, pois em JNDI não existem “nomes
absolutos”.
ListBindins
Operação de listar todas as associações nome-objeto de um contexto.
Todas estas especificações são abertas, mantidas pela Sun e pelo Java Community
Process (JCP). O JCP é uma comunidade composta pelos principais fabricantes de
tecnologia em plataforma Java, que definem as especificações (JSRs) para as
principais tecnologias java.
Dentre este plantel de fabricantes encontram-se IBM, Oracle, HP, Lucent, Hed Hat e
todos os demais “barões” da tecnologia atual.
Todas as JSRs e suas revisões são disponibilizadas para download no site do Java
Community Process. As JSRs são documentos fabulosos para se ter como
documentação das principais APIs do JEE:
http://www.jcp.org/
• Container Web
22
• Container EJB
Responsável por disponibilizar e gerenciar os componentes de infra-estrutura
da camada de negócio (“Business Tier”), como as especificações EJB e JPA.
Serviços secundários:
o Mensageria Assíncrona
o Temporização
Para um Container EJB ser completamente compatível com a especificação EJB 3.0,
ele precisa implementar as seguintes APIs:
o EJB 3.0
o JTA 1.1
o JPA 2.0
o JMS 1.1
o JavaMail 1.4 (apenas envio de email)
o JAF 1.1
o JAXP 1.2
o JAXR 1.0
o JAX-RPC 1.1
o JAX-WS 2.0
o JAXB 2.0
o SAAJ 1.3
o JAAS
o Connector 1.5
23
http://www.jboss.org/
5.1. Instalação
Após o download do pacote zip contendo o JBoss Application Server, como indicado
no capítulo 1.3 da apostila, podemos começar o procedimento de instalação.
Por ser baseado em java e assim portável, ele não possui instalador específico para
um sistema operacional. O JBoss é o conteúdo do arquivo zip.
Atenção
Nunca carregue diretamente os templates, pois eles não foram criados para ser
usados. Eles servem como modelo para criarmos a nossa configuration
personalizada.
Para criar uma configuration, basta replicar o template (criar uma cópia da pasta) que
desejarmos e modificar o nome da pasta.
Além desses diretórios padrão, são gerados também em run-time mais quatro pastas:
• data – utilizada para todos os serviços do JBoss que precisam guardar dados
persistentes no disco (ex.: Naming Service) – Hypersonic SQL Database
(HSQLDB)
• log – onde são gerados os arquivos de log do servidor
• tmp – qualquer tipo de dado temporário necessário pelos serviços do JBoss
• work – utilizado pelo Web Container do JBoss (baseado em Tomcat), para
guardar JSPs compiladas e outros arquivos temporários necessários para web
Selecione “JBoss v5.0” e clique em “Next” Escolha o JRE desejado e localize a pasta onde
reside o diretório raiz do JBoss
Obs.: os EJBs 3.0 possuem total interoperabilidade com os EJBs 2.1, ficando quase
que transparente para o desenvolvedor caso precise integrá-los.
Obs.: até a versão EJB 2.1 também haviam os “Entity Beans”, porém na versão 3.0
eles foram substituidos pela JPA, e com bons motivos (veremos com mais detalhes no
capítulo sobre JPA).
Quando pensamos em “sem estado”, podemos imaginar uma classe sem atributos,
composta apenas de métodos. Na prática é exatamente isso. Por exemplo, um
Session Bean de “Login” não possui estado, ele não precisa armazenar informações
entre diferentes requisições, ele não precisa “lembrar do usuário atual”, ele apenas
fornece a tarefa de efetuarLogin, e esta tarefa é auto-suficiente. Por outro lado, um
Session Bean “CarrinhoDeCompras” precisa ter um estado, pois ele não representa
uma tarefa atômica, ele precisa “lembrar do usuário atual” e armazenar seus itens de
compra, até que finalmente o usuário decida efetuar a transação.
Interface Características
• É utilizada quando desejamos acessar o bean
da mesma JVM
• É a interface mais lightweight
• É realizada uma chamada de método local,
javax.ejb.Local
como uma classe normal
• A passagem de parâmetros e retorno de
objetos é feita por referência, como o padrão
do java
• É utilizada quando desejamos acessar o bean
remotamente, via CORBA ou RMI
javax.ejb.Remote • Realiza uma chamada de método remota, por
CORBA, RMI-JRMP ou RMI-IIOP (configurável
de acordo com o container utilizado)
30
• Implementação
@Stateless
public class CalculadoraEJB implements CalculadoraLocal {
public double somar(double a, double b) {
return a + b;
}
public double subtrair(double a, double b) {
return a - b;
}
public double multiplicar(double a, double b) {
return a * b;
}
public double dividir(double a, double b) {
return a / b;
}
}
Como vimos no exemplo acima, para criar um EJB basta definir sua interface de
negócio, anotá-la da maneira desejada e implementar o bean.
• Implementação
@Stateless
public class CalculadoraEJB implements CalculadoraLocal, CalculadoraRemote {
public double somar(double a, double b) {
return a + b;
}
public double subtrair(double a, double b) {
return a - b;
}
public double multiplicar(double a, double b) {
return a * b;
}
public double dividir(double a, double b) {
return a / b;
}
}
No exemplo acima, o nosso bean LocadoraEJB será exposto das duas formas,
localmente e remotamente. É permitido utilizar a interface remota para fazer chamadas
da mesma JVM, porém esta chamada será feita de forma remota (os objetos serão
serializados e passados por valor), desperdiçando assim a melhoria de performance
oferecida na chamada local.
• Implementação
@Stateful
public class CarrinhoDeComprasEJB implements CarrinhoDeComprasLocal {
private double totalCarrinho;
Uma sessão é criada para um determinado cliente quando o mesmo faz o primeiro
request ao bean, e é finalizada quando o cliente chama o método anotado com
@Remove, ou então quando a sessão expira (este tempo de expiração pode ser
configurado no container).
O pooling de instâncias é implementado para os dois tipos de EJB que não guardam
estado: o Stateless e o MDB. O serviço funciona da seguinte forma: para cada
Stateless Session Bean e MDB deployados no servidor, o container cria um pool em
memória contendo um determinado número de instâncias desses beans. Então,
quando um cliente solicita um método de bean, uma instância aleatória é escolhida no
pool para servir à solicitação em particular. Quando a chamada do método finaliza, a
instância volta ao pool para posteriormente servir a outra solicitação.
O container não precisa ter a carga de instanciar uma nova instância do bean a cada
requisição, elas já estão prontas no pool. De acordo com a implementação do
container, ele pode decidir aumentar o pool e criar mais instâncias ou até mesmo
destruir instâncias de acordo com o número de requisições média ou memória
disponível.
Atenção
Ao contrário dos Servlets, os EJBs não executam em multithreading:
Servlets uma instância por aplicação e uma nova thread por requisição
EJBs uma instância por requisição (no caso de Stateless e MDB, a mesma
instância pode ser reutilizada por diversas requisições)
Note que isto é possível apenas para os beans Stateless e MDB, que não guardam
informações de estado e, desta forma, duas requisições subsequentes de um mesmo
33
cliente ao mesmo bean podem ser atendidas por instâncias diferentes do bean, sem
causar efeitos colaterais na aplicação.
Sendo assim, o ciclo de vida dos beans Stateless é muito simples, onde existem
apenas dois estados: “Não Existente” e “Pronto no Pool”
Os session beans do tipo Stateful possuem um ciclo de vida um pouco mais complexo,
mas primeiramente devemos falar sobre um serviço muito importante que o container
EJB oferece: o Mecanismo de Ativação.
Desta forma, o ciclo de vida dos beans Stateful possui um estado a mais que o
Stateless e sem utilização de pool, resultando em três estados: “Não Existente” ,
“Pronto” e “Apassivado”
35
Os beans que implementamos não são objetos remotos, porém o container, através do
design pattern do GOF chamado Proxy, cria um “objeto proxy” que recebe as
requisições e as redireciona para os nossos beans.
Proxy Pattern
que funciona como o Skeleton da arquitetura remota (note que o proxy também
implementa a interface de negócio). Esta classe que é responsável por receber a
invocação remota e “delegar” a chamada à instância do EJB através do container. O
container por sua vez gerencia as questões de pooling, transação e segurança da
chamada.
Vimos também que todo sistema distribuído trabalha em conjunto com um Serviço de
Nomes. Na arquitetura EJB, cada container implementa o serviço de nomes que
preferir. No caso do JBoss, o serviço de nomes padrão é o Java Naming Provider
(JNP), que é uma implementação substituta do RMI Registry. O protocolo de
comunicação padrão de distribuição de EJBs do JBoss é o RMI-JRMP.
• Estrutura do WAR
37
• Estrutura do EJB-JAR
• Estrutura do EAR
Como a definição do EJB Timer Service é relativamente recente (foi incluída a partir da
versão EJB 2.1), ele ainda é um pouco engessado e pobre de funcionalidades. Porém,
para implementações simples, acaba tornando-se a ferramenta mais recomendada.
Para implementações de temporizadores mais robustos, existe um framework de
mercado chamado Quartz, que é muito mais completo e poderoso que o EJB Timer
Service, além do fato de poder ser utilizado em aplicações Java SE simples, sem a
necessidade de um container EJB (o estudo do framework Quartz não é escopo desse
curso).
Os temporizadores fornecidos pelo EJB Timer Service podem ser de dois tipos:
• Ação Única
Os temporizadores de ação única disparam apenas uma vez, depois de um certo
intervalo de tempo ou em uma data específica.
• Intervalo
Os temporizadores de intervalo disparam várias vezes, de acordo com um período
de tempo.
Cuidado
Apenas os beans do tipo Stateless e MDB podem ser timers.
@Resource
private TimerService timerService;
@Timeout
public void temporizador(Timer timer) {
System.out.println("DISPAROU!");
}
Na versão EJB 2.1 existia o conceito de Entity Beans, que também eram componentes
EJB muito parecidos com os Session Beans, com interface de negócio e distribuídos
remotamente, gerando Proxies (ou Skeletons) e Stubs. Ou seja, os Entity Beans eram
pesados, faziam chamadas remotas e dependiam de um container EJB e muita
configuração XML.
Outra grande vantagem da especificação JPA é que ela foi criada para atuar também
em aplicações simples Java SE, não sendo necessário um servidor de aplicação para
gerenciar as entidades, todas as implementações da JPA devem fornecer uma
implementação do Entity Manager independente de container EJB.
Obs.: toda operação realizada com o Entity Manager é transacional, de acordo com os
conceitos de Contexto de Persistência e Entidade Gerenciada que veremos adiante.
persist(Object entity)
• Enfileira a entidade para criação no banco (não representa o momento real do
insert)
• É possível chamar persist fora de uma transação apenas se o contexto for
EXTENDED; nesse caso, a inserção é enfileirada até o contexto ser associado
com uma transação
• Se o parâmetro não for uma entidade, lança IllegalArgumentException
• Se for invocado fora do contexto de transação e for tipo TRANSACTION, lança
uma TransactionRequiredException
41
createQuery
createXXXQuery
• Executam querys EJB-QL e consultas nativas (retornam um objeto Query)
• Entidades retornadas permanegem gerenciadas enquanto o contexto de
persistência estiver ativo
flush()
• Sincroniza as atualizações no banco antes de terminar a transacao
remove(Object entity)
• Remove a entidade da base e a torna desacoplada
refresh(Object entity)
• Atualiza a entidade acoplada com os dados da base
• Se ela não for acoplada ao próprio EntityManager que invoca o método, é
lançada IllegalArgumentException
• Se o objeto não estiver mais no banco devido a outra Thread ou Processo te-
lo removido, será lançado EntityNotFoundException
clear()
• Desacopla todas as entidades atuais gerenciadas pelo EntityManager
• Suas modificações são perdidas, portanto é prudente chamar flush() antes.
• New / Nova
Aqui a entidade ainda não tem um estado persistente, ela acabou de ser instanciada
Ex.:
Pessoa p = new Pessoa();
Aqui a entidade está gerenciada pelo Entity Manager. Isto significa que ela possui um
Id Persistente, ou seja, ela representa ativamente um registro da tabela e qualquer
mudança de seus atributos refletirá automaticamente na tabela após o flush do Entity
Manager ou no final da transação atual.
Ex.:
Pessoa p = entityManager.find(Pessoa.class, 2);
//neste momento, p está “Gerenciada”
p.setNome(“Gilberto Holms”);
p.setIdade(24);
entityManager.flush();
No trecho de código apresentado, após o comando find o Entity Manager irá retornar
uma entidade gerenciada, que representa um registro ativo no banco de dados.
• Detached / Desacoplada
Ocorre quando a entidade perde o sincronismo com o banco. Isso pode acontecer nas
seguintes ocasiões:
o Acabou o Contexto de Persistência
o Foi executado o método clear do Entity Manager
o A entidade foi removida do Contexto de Persistência (método remove)
o A entidade foi enviada ao cliente (retorno do método de negócio de um
EJB)
Ex.:
Pessoa p = entityManager.find(Pessoa.class, 2);
//neste momento, p está “Gerenciada”
p.setNome(“Gilberto Holms”);
p.setIdade(24);
entityManager.flush();
43
entityManager.clear();
//neste momento, p está “Desacoplada”
• Removed / Removida
Uma entidade torna-se removida quando é deletada do banco. Isto é feito através do
método remove do Entity Manager. Após ser removida, ela é automaticamente
desacoplada do Entity Manager.
Ex.:
Pessoa p = entityManager.find(Pessoa.class, 2);
//neste momento, p está “Gerenciada”
p.setNome(“Gilberto Holms”);
p.setIdade(24);
entityManager.flush();
entityManager.remove(p);
//neste momento, p está “Removida”
• Persisted / Persistida
• Transaction
O tipo “Transaction” pode ser utilizado em qualquer tipo de EJB. Significa que o
contexto de persistência irá durar o tempo que durar a transação atual, ou seja, ao
terminar a transação atual, as entidades tornam-se desacopladas.
44
• Extended
O tipo “Extended” foi criado para ser utilizado apenas para os EJBs do tipo Stateful.
Significa que o contexto de persistência irá durar o tempo que durar a sessão do bean
Stateful, ou seja, uma entidade pode-se manter gerenciada entre diferentes
transações e métodos de negócio. Utilizando esse tipo de contexto de persistência,
podemos interagir com uma entidade mesmo fora de uma transação, pois as
alterações realizadas serão enfileiradas e efetivadas quando for iniciada a próxima
transação para aquele entity manager.
45
Exemplo de persistence.xml:
<persistence>
<persistence-unit name="curso">
<jta-data-source>java:/meuDataSource</jta-data-source>
<properties>
<property name="org.hibernate.hbm2ddl"
value="update" />
</properties>
</persistence-unit>
</persistence>
Tags importantes:
<persistence-unit name="curso"> Declara a unidade de persistência, informando um
nome (obrigatório)
<jta-data-source> Declara um Data Source transacional (API JTA,
padrão para sistemas JEE), indicando o
“endereço JNDI” do Data Source dentro do
container
<properties> Declara propriedades específicas do fabricante da
implementação JPA utilizada
Obs.: os mapeamentos via XML não serão abordados nesse curso, utilizaremos
sempre as Annotations pelo fato de simplificarem o desenvolvimento e a manutenção
do sistema, porém isso não descarta a citação de trechos do XML quando for
interessante.
46
@Entity
public class CreditCard {
...
@OneToOne(mappedBy="creditCard")
private Customer customer;
...
}
@Entity
public class Cruise {
...
@OneToMany(mappedBy="cruise")
private Collection<Reservation> reservations = new ArrayList<Reservation>( );
...
}
@Entity
public class Customer {
...
@ManyToMany(mappedBy="customers")
private Collection<Reservation> reservations = new ArrayList<Reservation>( );
...
}
• MERGE
• REMOVE
• REFRESH
Por exemplo:
Endereco e = new Endereco();
...
pessoa.setEndereco(e);
entityManager.persist(pessoa)
No caso mostrado acima, se não tiver sido configurado nenhum tipo de cascata para o
relacionamento, ao persistir a entidade pessoa, o endereço não será persistido, a JPA
irá subentender que ele já existe na base de dados (e caso não exista, lançará
exception).
Se colocássemos um CascadeType.PERSIST, a JPA iria persistir automaticamente o
endereço (caso ele não exista na base) antes de persistir a pessoa.
Note que os nomes dos Cascade Types são idênticos a alguns métodos do Entity
Manager (exceto ALL, que indica “faça todos”). Em resumo, cada tipo de cascata
indica ao Entity Manager para “também realizar a respectiva operação para a
entidade relacionada”.
Por exemplo:
Entidade:
@Entity
public class Customer {
...
@OneToMany(cascade={CascadeType.ALL})
private Collection<Phone> phoneNumbers = new ArrayList<Phone>( );
...
}
FetchType.LAZY
Customer c = entityManager.find(Customer.class, 2);
//neste momento o objeto c não contém a lista de telefones
c.getPhoneNumbers().size();
//apenas nesse momento a JPA carrega a lista de telefones
FetchType.EAGER
Customer c = entityManager.find(Customer.class, 2);
//neste momento o objeto c já contém a lista de telefones
Atenção
49
Atualizar:
p.setNome("Holms");
...
p = entityManager.merge(p);
Excluir:
entityManager.remove(p);
Tendo esse Data Source configurado, qualquer aplicativo pode fazer seu “lookup” e
utilizá-lo para abrir uma sessão com a base de dados. Quando isso ocorre em um
servidor JEE, ele ainda pode gerenciar todo o controle transacional (através de JTA –
Java Transaction API) peculiar da especificação.
<persistence-unit name="curso">
<jta-data-source>java:/meuDataSource</jta-data-source>
</persistence-unit>
</persistence>
Obs.: note que podemos ter outra unidade de persistência com um Data Source
diferente, e utilizar um Entity Manager para cada unidade.
Nos diversos servidores de aplicação disponíveis no mercado, cada um tem sua forma
específica de configurar Data Sources. Em servidores mais poderosos, como por
exemplo o Oracle WebLogic, essa configuração é feita via console administrativo.
Exemplo de –ds.xml:
<datasources>
<local-tx-datasource>
<jndi-name>cursoDS</jndi-name>
<connection-url>jdbc:mysql://localhost:3306/curso</connection-url>
<driver-class>com.mysql.jdbc.Driver</driver-class>
<user-name>root</user-name>
<password>root</password>
<exception-sorter-class-name>
org.jboss.resource.adapter.jdbc.vendor.MySQLExceptionSorter
</exception-sorter-class-name>
<metadata>
<type-mapping>mySQL</type-mapping>
</metadata>
</local-tx-datasource>
</datasources>
Atenção
As configurações de data source são específicas para cada fornecedor de base
de dados, porém o JBoss já vem com um exemplo de configuração para as
principais bases de dados, basta modificá-los e utilizá-los. Estes exemplos
residem na pasta “docs/examples/jca”, a partir do diretório raiz JBoss.
51
Após a publicação deste livro é que se teve o conceito de Design Patterns bem
definido e adotado mundialmente. Design patterns são padrões de desenvolvimento
bem sucedidos e reutilizáveis, criados para resolver os problemas arquiteturais mais
comuns do desenvolvimento orientado a objetos através de uma solução testada e
bem sucedida nos diversos aspectos. O livro da GoF define 23 patterns que resolvem
grande parte dos problemas estruturais de sistemas orientado a objeto.
8.1. Singleton
O Design pattern “Singleton” não é um pattern JEE, mas sim um pattern do GoF. O
estudo dos design patterns da GoF não é escopo deste livro, porém este pattern em
especial é utilizado como parte de diversos J2EE Patterns, portanto é importante o seu
entendimento.
Como o próprio nome do pattern indica, o seu objetivo é manter apenas uma instância
de um objeto na memória, e todas as classes que utilizarem este objeto estarão
utilizando a mesma instância dele.
Ele traz diversos benefícios, como menor consumo de memória garantindo que existe
apenas uma instância do objeto, fornecimento de um único ponto de acesso ao objeto
e maior flexibilidade que métodos estáticos, pois permite polimorfismo.
Exemplo de Implementação:
public class Configuracoes {
//Construtor privado
private Configuracoes(){
// qualquer código de inicialização da instância
}
Para resolver esta questão arquitetural foi desenvolvido o pattern “Service Locator”,
que torna-se uma camada responsável por localizar recursos e serviços, abstraindo a
localização física dos mesmos.
Exemplo de Implementação:
public class ServiceLocator {
//construtor singleton
private ServiceLocator() throws ServiceLocatorException{
Properties properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY,
"org.jnp.interfaces.NamingContextFactory");
properties.put(Context.PROVIDER_URL, "jnp://127.0.0.1:1099");
try {
this.initialContext = new InitialContext(properties);
} catch (NamingException e) {
throw new ServiceLocatorException(e);
}
}
return serviceLocator;
}
//método que retorna um servico do jndi configurado (um EJB, DataSource, etc...)
public Object getService(String jndiName, Class clazz) throws
ServiceLocatorException {
try {
Object ref = initialContext.lookup(jndiName);
return PortableRemoteObject.narrow(ref, clazz);
} catch (NamingException e) {
throw new ServiceLocatorException(e);
}
}
Com isso, garantimos que os recursos que dependem de localização serão criados
através de um único ponto do sistema, deixando o negócio transparente da localização
física dos recursos, abstraindo as chamadas de lookup e particularidades de JNDI.
Exemplo de Implementação:
public class LocadoraDelegate {
Além do problema da rede, temos também uma falta de padronização nas chamadas
de negócio provenientes de diferentes clientes e alto acoplamento entre o cliente e os
objetos distribuídos.
Obs.: existem dois tipos de Session Facade: Stateless e Stateful, que analogamente
são criados a partir dos respectivos Session Beans, para funcionalidades de negócio
sem estado e com estado respectivamente.
56
Para resolver este problema foi criado o pattern Data Transfer Object (conhecido
também por outros nomes como Transfer Object ou Value Object). O pattern consiste
na criação de um POJO contendo os atributos da entidade ou qualquer outro conjunto
de informações, o qual será trafegado para o cliente ao invés do Entity Bean. Desta
forma o cliente recebe um objeto java simples, preenchido com os dados relevantes da
chamada realizada, sem precisar realizar mais chamadas remotas.
A partir da criação da versão EJB 3.0, com a introdução da Java Persistence (JPA),
muitos autores discutem sobre a necessidade do uso desse pattern, pois como agora
as entidades já são POJOs, o servidor pode retorná-las diretamente ao cliente como
entidades desacopladas, ou seja, elas já seriam o DTO.
Porém, ainda assim há cenários em que seria relevante o uso do pattern DTO.
Imagine que você tem uma entidade JPA com oitenta campos, porém sua tela
necessita apenas de três deles, ou ainda que sua tela precise de informações
provenientes de três entidades distintas, ou então você não deseja nenhum
acoplamento entre sua tela e as entidades de negócio. Reflita sobre o uso do DTO
nesses casos.
9.3. JMS
A sigla JMS significa Java Messaging Services. O JMS é uma API da plataforma Java
EE que permite ao desenvolvedor manipular MOMs.
Obs.: texto muito parecido com o do capítulo sobre JNDI não é mesmo!
O JMS está para os “MOMs” assim como o JNDI está para os “serviços de
nomes e diretórios” e assim como o JDBC está para as “bases de dados”.
Principais características:
• Um canal por ter muitos assinantes
• Todos assinantes do canal recebem uma cópia da mensagem
• Um assinante não recebe as mensagens enviadas antes de sua assinatura
• Podem ser implementados ambos os modelos PUSH e PULL.
Principais características:
• Uma fila retém as mensagens até que algum consumidor as retire (ou expirem)
• Apenas um consumidor pode retirar cada mensagem
• Um consumidor pode receber mensagens anteriormente armazenadas na fila
• Podem ser implementados ambos os modelos PUSH e PULL.
Assim como os Data Sources, os Destinos JMS são criados via arquivos de
configuração XML, que tornam-se artefatos deploiáveis no servidor de aplicações.
Exemplo de topic–service.xml:
<server>
<mbean code="org.jboss.jms.server.destination.TopicService"
xmbean-dd="xmdesc/Topic-xmbean.xml"
name="curso.jms:service=Topic,name=TesteTopic">
<depends optional-attribute-name="ServerPeer">
jboss.messaging:service=ServerPeer
</depends>
<depends>
jboss.messaging:service=PostOffice
</depends>
</mbean>
</server>
Exemplo de queue–service.xml:
<server>
<mbean code="org.jboss.jms.server.destination.QueueService"
xmbean-dd="xmdesc/Queue-xmbean.xml"
name="curso.jms:service=Queue,name=TesteQueue">
<depends optional-attribute-name="ServerPeer">
jboss.messaging:service=ServerPeer
</depends>
<depends>
jboss.messaging:service=PostOffice
</depends>
</mbean>
</server>
Atenção
O JBoss já vem com um exemplo de destinos JMS ilustrando diversas
possibilidades de configurações, basta modificá-los e utilizá-los. Este exemplo
reside no caminho “docs/examples/jms/example-destinations-service.xml”, a
partir do diretório raiz JBoss.
61
Realizados estes passos, o Destino JMS torna-se disponível para a aplicação JEE
através do JNDI Global. Por padrão do JBoss versões 5.x, localizamos as filas no
namespace “java:/queue/<name>” e os tópicos no namespace “java:/topic/<name>” –
ex: ctx.lookup(“java:/queue/TesteQueue”).
Outro objeto específico do MOM que precisamos ter em mãos é o “Destino JMS” do
qual iremos consumir ou publicar mensagens, que pode ser um Topic ou Queue.
javax.jms.Destination
|- javax.jms.Topic
|- javax.jms.Queue
Com a fábrica de conexões em mãos, podemos então criar uma conexão com o MOM,
a javax.jms.Connection. A JMS foi desenhada para ser totalmente segura quanto à
integridade dos dados e operações de envio e recebimento de mensagens, portanto,
assim como uma conexão JDBC, ela funciona com os conceitos de “conexões” e
“sessões”, possuindo todo o suporte transacional, até mesmo two-phase-commit
(dependendo do fabricante).
62
Basta ter disponível um MOM e sua implementação da JMS, então qualquer aplicação
poderá consumir e receber mensagens.
producer.close();
session.close();
connection.close();
System.out.println("Mensagem enviada.");
connection.start();
System.out.println("Escutando Topic...");
while(true) Thread.sleep(1000);
@Override
public void onMessage(Message msg) {
try {
ObjectMessage objectMsg = (ObjectMessage)msg;
System.out.println("Mensagem Recebida: " +
objectMsg.getObject().toString());
} catch (JMSException e) {
e.printStackTrace();
}
}
producer.close();
session.close();
connection.close();
System.out.println("Mensagem enviada.");
try {
Context ctx = new InitialContext();
ConnectionFactory connectionFactory =
(ConnectionFactory)ctx.lookup("java:/ConnectionFactory");
Queue queue = (Queue)ctx.lookup("java:/queue/TesteQueue");
connection.start();
System.out.println("Aguardando Mensagens...");
while(true) {
Message msg = consumer.receive();
ObjectMessage objectMsg = (ObjectMessage)msg;
System.out.println("Mensagem Recebida: " +
objectMsg.getObject().toString());
}
Atenção
Na implementação do MOM fornecida pelo JBoss, é disponibilizado no JNDI Global
duas referências ao javax.jms.ConnectionFactory:
65
...
...
@Resource(mappedName="java:/JmsXA")
private ConnectionFactory connectionFactory;
@Resource(mappedName="queue/TesteQueue")
private Destination testeQueue;
producer.close();
session.close();
connection.close();
Atenção
É terminantemente proibido consumir mensagems JMS através de um Session
Bean utilizando os códigos do capítulo 9.7 ! Os métodos consumer.receive()
bloqueam a thread até que uma mensagem apareça, e é proibido interferir na
thread ou criar threads em EJBs, pois o container é quem os controla. Se o
desenvolvedor altera o comportamento da thread de um EJB, o container fica
impossibilitado de controlar o ciclo de vida do bean e garantir o seu correto
funcionamento. Não invente ! ☺
Todo MDB trabalha no modelo PULL, ou seja, ele é executado sempre que chega uma
nova mensagem na fila. Quanto ao ciclo de vida do MDB, da mesma forma que o
Stateless Session Bean, o container trabalha com o “Pool de Instâncias”. Portanto, a
cada nova mensagem que chega na fila, o container elege uma instância do MDB para
consumi-la.
Atenção
A ordem de consumo das mensagens não é garantida! Por exemplo, uma
mensagem enviada antes pode ser consumida posteriormente a uma mensagem
enviada depois dela. Portanto, a lógica implementada nos MDBs não pode
contar com um comportamento seqüencial de mensagens.
Após consumir e processar a mensagem, a instância do MDB retorna ao pool para ser
utilizada posteriormente em uma nova solicitação do container. Sim, o container
também é capaz de solicitar a várias instâncias do MDB o consumo simultâneo de
duas ou mais mensagens que chegaram na fila (exatamente por isso a ordem de
consumo das mensagens não é garantida).
BEGIN TRANSACTION
Operação 1
Operação 2
...
Operação N
[erro] [sucesso]
ROLLBACK COMMIT
Estes níveis de isolamento, são caracterizados por três efeitos colaterias conhecidos
por Dirty Reads, Repeatable Reads e Phantom Reads.
Dirty Reads – traduzidos como “leitura suja”, acontece quando uma
transação lê dados que ainda não foram comitados por outra
transação. Exemplo:
-- usuário 1:
INSERT INTO PUB.state VALUES ('SP', 'São Paulo', 'South');
Conhecidos estes efeitos colaterais, todo dispositivo que suporta transações deve
trabalhar com um determinado nível de isolamento pré-definido ou até permitir que o
desenvolvedor escolha o nível de isolamento desejado. O que determina maior
isolamento é a quantidade e duração dos LOCKS que o dispositivo fará nos seus
dados. Consequentemente, quanto maior a utilização de LOCKS, menos transações
poderão trabalhar nos mesmos dados simultaneamente, fazendo com que a
performance do sistema caia em função disso.
Conhecendo os diversos tipos de LOCKS que um dispositivo pode utilizar para garantir
o isolamento das transações, podemos então estudar os níveis de isolamento
disponíveis nos dispositivos transacionais:
70
NOT_SUPPORTED
A transação do chamador não é propagada e o bean é executado
sempre sem transação.
71
SUPPORTS
Se o chamador tiver transação, ela é propagada. Caso contrário, o bean
é executado sem transação. "Acompanha o chamador".
REQUIRED
Se o chamador tiver transação, ela é propagada. Caso contrário, o bean
cria uma transação própria. "Bean sempre tem uma transação".
REQUIRES_NEW
O bean sempre cria uma transação própria, independentemente do
chamador ter ou não transação.
72
MANDATORY
Bean só executa se o chamador tiver transação. Caso contrário, lança
EJBTransactionRequiredException.
NEVER
Bean só executa se o chamador NÃO tiver transação. Caso contrário,
lança EJBException.
73
Obs.: recomendo agora que o leitor volte ao capítulo 7.3 e leia novamente o tópico
sobre “Contextos de Persistência”, com certeza tudo fará mais sentido agora ☺.
@PersistenceContext()
private EntityManager em;
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public void testar() {
Empresa e = new Empresa();
e.setNome("Holms");
em.persist(e);
}
Importante
Para os MDBs, quando utilizamos transações gerenciadas pelo container (CMT), a
transação é iniciada antes da chamada do método onMessage, o que permite que, em
caso de rollback, o consumo da mensagem também seja revertido, fazendo com que o
MOM tente entregá-la novamente.
74
Porém, quando utilizamos transações gerenciadas pelo bean (BMT), não é possível
iniciá-la antes da chamada onMessage, portanto neste caso o consumo da mensagem
não poderá ser revertido em caso de falha.
Realmente não queremos que um cliente sem saldo consiga transferir o dinheiro que
não tem. Nestes casos utilizamos a anotação @ApplicationException(rollback =
true), que irá dizer ao container:
“Ok, eu sei que é uma exceção de aplicativo, porém eu quero que ela cause
rollback !”.
@PersistenceContext()
private EntityManager em;
@Resource
UserTransaction utx;
try {
Empresa e = new Empresa();
e.setNome("Holms");
em.persist(e);
utx.commit();
} catch (Exception ex) {
utx.setRollbackOnly();
}
}
}
Cuidado
Não confunda, é proibido usar begins e commits de outras APIs (EntityManager,
Connection, etc). Apenas o objeto UserTransaction realiza uma transação distribuída.
http://doc.javanb.com/javasdk-docs-6-0-en/technotes/guides/idl/POA.html
Core J2EE Patterns: Best Practices and Design Strategies, 2nd Ed. – Prentice
Hall
Deepak Alur, Dan Malks, John Crupi