Conteúdo


Desenvolvimento em Java 2.0

Armazenamento em nuvem com o SimpleDB da Amazon, Parte 2

Persistência de objetos simples antigos com SimpleJPA

Comments

Conteúdos da série:

Esse conteúdo é a parte # de # na série: Desenvolvimento em Java 2.0

Fique ligado em conteúdos adicionais dessa série.

Esse conteúdo é parte da série:Desenvolvimento em Java 2.0

Fique ligado em conteúdos adicionais dessa série.

Na primeira metade desta introdução ao SimpleDB, foi mostrado como otimizar a própria API da Amazon para modelar um aplicativo de corrida no estilo CRUD. Deixando de lado a exclusividade óbvia para a maioria dos desenvolvedores de Java da abordagem somente de cadeia de caractere da Amazon para tipos de dados, você já deve ter se flagrado olhando para a API do Amazon com um certo ceticismo. Afinal de contas, as APIs para otimização de um banco de dados relacional são no momento bastante padronizadas e bem planejadas — e, talvez o mais importante, elas são familiares.

Nos bastidores, muitas estruturas relacionais hoje implementam o Java Persistence API. Isso torna a modelagem de objetos de domínio para praticamente qualquer tipo de aplicativo Java fácil e familiar por toda a gama de RDBMSs. É natural a resistência a aprender uma nova abordagem para modelagem de domínio, quando você já domina uma que funciona — e a boa notícia é que com o SimpleDB, isso não é preciso.

Nesta segunda metade da minha introdução ao SimpleDB, mostrarei como refatorar o aplicativo de corrida da parte 1 para ficar em conformidade com a especificação da JPA. Depois iremos transportar o aplicativo para SimpleJPA e descobrir algumas das formas como essa plataforma inovadora de software livre pode tornar a adaptação para modelagem de domínio NoSQL, e o armazenamento baseado em nuvem, um pouco mais fácil.

Hibernate e JPA: Uma breve história

Muitos desenvolvedores de Java hoje aproveitam o Hibernate (e o Spring) para persistência de dados. Além de ser um precursor do sucesso do software livre, o Hibernate mudou o campo de ORM para melhor. Antes do Hibernate, os desenvolvedores de Java tinham de lidar com a confusão dos beans de entidade EJB; antes disso, nós basicamente preparávamos nossos próprios ORMs ou comprávamos um de um fornecedor como a IBM®. O Hibernate eliminou toda essa complexidade e custo em nome de uma plataforma de modelagem baseada em POJO que muitos de nós aceitamos hoje.

Uma Java Persistence API foi criada em resposta à popularidade da inovação do Hibernate de aproveitar POJOs para modelagem de dados. Hoje, o EJB 3.0 implementa JPA, assim como o Google App Engine. O próprio Hibernate é uma implementação de JPA, presumindo que você use o Hibernate EntityManager.

Levando-se em conta como foi bom para os desenvolvedores de Java modelar aplicativos centrados em dados usando POJOs, faz sentido que um armazenamento de dados como o SimpleDB nos ofereça uma opção semelhante.Afinal de contas, ele é praticamente um banco de dados, não é?

Modelagem de dados com objetos

Para usar o SimpleJPA, precisamos trabalhar um pouco em nossos objetos Racer e Runner , trazendo-os de volta para acelerar com a especificação de JPA. Felizmente, os conceitos básicos de JPA são bem simples: você decora POJOs normais com anotações e uma implementação EntityManager toma conta do resto — não há necessidade de XML.

Duas das principais anotações usadas pela JPA são @Entity e @Id, que especificam um POJO como persistente e delineiam sua chave de identidade, respectivamente. Com o objetivo de converter nosso aplicativo de corrida em JPA, também precisaremos de duas anotações usadas para gerenciar relacionamentos: @OneToMany e @ManyToOne.

Na primeira metade deste artigo, mostrei como prosseguir com runners e races. Nunca usei nenhum objeto para representar essas entidades, no entanto — acabei de usar a API bruta da Amazon para persistir as propriedades das duas. Se quisesse modelar um relacionamento simples entre um race e seus runners, faria como mostrado na listagem 1:

Listagem 1. Um objeto Race simples
public class Race {
 private String name;
 private String location;
 private double distance;
 private List<Runner> runners;
	
 //setters and getters left out...
}

Na listagem 1, especifiquei um objeto Race com quatro propriedades, sendo que a última é uma Collection de runners. Em seguida, posso criar um objeto Runner simples (mostrado na listagem 2) que contém o nome de cada runner (por enquanto, simplificarei o máximo possível) e SSN junto com a instância Race em que ele está correndo.

Listagem 2. Um Runner simples relacionado a um Race
public class Runner  {
 private String name;
 private String ssn;
 private Race race;

 //setters and getters left out...
}

Como você pode ver nas listagens 1 e 2, modelei de forma lógica um relacionamento de muitos-para-um entre runners e um race. Em uma situação real, provavelmente seria mais apropriado fazer a ligação muitos-para-muitos (os runners normalmente não participam de mais de um race?), mas vou simplificar as coisas. Também deixarei de lado por enquanto os construtores, setters e getters. Irei mostrá-los adiante.

Anotações em JPA

Deixar esses dois objetos prontos para SimpleJPA não é um desafio tão terrível. Primeiro, tenho que indicar minha intenção de torná-los persistíveis, incluindo a anotação @Entity em cada um. Também preciso definir os relacionamentos de forma adequada usando @OneToMany no objeto Race e @ManyToOne no Runner .

A anotação @Entity está anexada no nível de classe e as anotações de relacionamento estão anexadas no nível getter. Tudo isso está demonstrado nas listagens 3 e 4:

Listagem 3. Um Race JPA detalhado
@Entity
public class Race {
 private String name;
 private String location;
 private double distance;
 private List<Runner> runners;

 @OneToMany(mappedBy = "race")
 public List<Runner> getRunners() {
  return runners;
 }

 //other setters and getters left out...
}

Na listagem 3, decorei o método getRunners com umaanotação @OneToMany. Também especifiquei que o relacionamento pode ser encontrado usando a propriedade race na entidade Runner.

Na listagem 4, anotarei o método getRace de forma semelhanteno objeto Runner.

Listagem 4. Um Runner JPA detalhado
@Entity
public class Runner  {
 private String name;
 private String ssn;
 private Race race;

 @ManyToOne
 public Race getRace() {
  return race;
 }

 //other setters and getters left out...
}

A maioria dos armazenamentos de dados (relacionais ou não) precisa de uma forma de descrever exclusividade entre os dados. Assim, se eu quiser tornar esses dois objetos persistentes em um armazenamento de dados, tenho de pelo menos adicionar IDs a eles. Na listagem 5, adicionei uma propriedade id do tipo BigInteger aoobjeto de domínio Race. Farei o mesmo em relação ao objeto Runner.

Listagem 5. Adição de um ID ao Race
@Entity
public class Race {
 private String name;
 private String location;
 private double distance;
 private List<Runner> runners;
 private BigInteger id;

 @Id
 public BigInteger getId() {
  return id;
 }

 @OneToMany(mappedBy = "race")
 public List<Runner> getRunners() {
  return runners;
 }

 //other setters and getters left out...
}

A anotação @Id na listagem 5 não fornece nenhuma informação sobre como o ID é gerenciado. O programa irá presumir que estou fazendo isso de forma manual, em vez de usar uma EntityManager, por exemplo.

Como inserir SimpleJPA

Até agora, não fiz nada específico com o SimpleDB. Os objetos Race e Runner são anotados genericamente com anotações de JPA e podem persistir em qualquer armazenamento de dados que seja suportado por uma implementação de JPA. As opções incluem Oracle, DB2, MySQL e (como você já deve ter adivinhado) SimpleDB.

A SimpleJPA é uma implementação de software livre de JPA para o SimpleDB da Amazon. Embora ela não suporte toda a especificação de JPA (por exemplo, você não pode participar de consultas JPA), ela suporta um subconjunto grande o suficiente para que valha a pena explorá-lo.

Uma grande vantagem de usar SimpleJPA é que ela tenta lidar continuamente com as questões lexicográficas que discuti na primeira metade deste artigo. A SimpleJPA faz a conversão da cadeia de caractere e qualquer preenchimento subsequente para objetos que dependem de tipos numéricos. Para a maior parte, isso significa que você não precisa mudar seu modelo de domínio para refletirtipos de String. (Há uma exceção a essa regra, que explicarei daqui a pouco)

Como a SimpleJPA é uma implementação de JPA, você pode usar com ela objetos de domínio em conformidade com JPA facilmente. A SimpleJPA só exige que você use IDs de String , mostrando que sua propriedade id deve ser java.lang.String. Para tornar as coisas mais fáceis, a SimpleJPA fornece a classe base IdedTimestampedBase, que gerencia a propriedade de ID do objeto do domínio, assim como os atributos de data created e updated. (Por baixo dos panos, a SimpleDB gera um exclusivo Id.)

Transporte do aplicativo para SimpleJPA

Para fazer com que as classes Race e Runner estejam em conformidade com a SimpleJPA, eu poderia estender a classe base conveniente da SimpleJPA ou alterar cada propriedade do id de BigInteger para String. Optei pela primeira opção, como mostrado na listagem 6:

Listagem 6. Mudança da Race para usar a classe base IdedTimestampedBase da SimpleJPA
@Entity
public class Race extends IdedTimestampedBase{
 private String name;
 private String location;
 private double distance;
 private List<Runner> runners;

 @OneToMany(mappedBy = "race")
 public List<Runner> getRunners() {
  return runners;
 }

 //other setters and getters left out...
}

Não vou mostrar o mesmo código para Runner, mas fique à vontade para passar rapidamente por ele você mesmo: apenas estenda IdedTimestampedBase e remova a propriedade id de Runner.

A atualização dos IDs para Race e Runner é a primeira etapa para fazer com que o aplicativo de corrida fique em conformidade com a SimpleJPA. Em seguida, é preciso trocar tipos de dados primitivos (como double, inte float) para objetos como Integer e BigDecimal.

Começarei com a propriedade distance de Race. Achei o BigDecimal mais confiável que o Double (no release atual de SimpleJPA), então mudei a propriedade distance de Race para BigDecimal, como mostrado na listagem 7:

Listagem 7. Mudança da distância para BigDecimal
@Entity
public class Race extends IdedTimestampedBase{
 private String name;
 private String location;
 private BigDecimal distance;
 private List<Runner> runners;

 @OneToMany(mappedBy = "race")
 public List<Runner> getRunners() {
  return runners;
 }

 //other setters and getters left out...
}

Agora Runner e Race estão prontos para serem persistidos através de uma implementação de SimpleJPA

Uso da SimpleJPA com o SimpleDB

Manipular seus objetos de domínio em relação ao SimpleDB com SimpleJPA não é diferente de ir contra um banco de dados relacional normal com uma implementação de JPA. Se você já fez qualquer desenvolvimento de aplicativo com JPA, então nada irá surpreendê-lo. A única coisa que pode ser nova é configurar EntityManagerFactoryImpl da SimpleJPA, que necessitará das suas credenciais dos Serviços da Web da Amazon e o nome do prefixo para seu domínio SimpleDB. (Outra opção seria fornecer um arquivo de propriedades contendo suas credenciais no caminho de classe.)

Usar o nome do prefixo que você especifica ao criar uma instância EntityManagerFactoryImpl de SimpleJPA resultará em domínios SimpleDB que começam com seu prefixo seguido de um travessão e o nome do seu objeto de domínio. Assim, se eu especificar "b50" como meu prefixo, ao criar um item Race em SimpleDB, o domínio será "b50-Race".

Assim que tiver criado uma instância EntityManagerFactoryImpl de SimpleDB, todo o restante é dirigido pela interface. Você precisará de uma instância EntityManager , que você pode obter de EntityManagerFactoryImpl, como mostrado na listagem 8:

Listagem 8. Obtenção de uma EntityManager
Map<String,String> props = new HashMap<String,String>();
props.put("accessKey","...");
props.put("secretKey","..");

EntityManagerFactoryImpl factory = 
  new EntityManagerFactoryImpl("b50", props);

EntityManager em = factory.createEntityManager();

Manipulação de objetos de domínio

Assim que tiver um identificador para uma EntityManager, é possível manipular objetos de domínio conforme desejar. Como exemplo, posso criar uma instância Race como esta:

Listagem 9. Criação de um Race
Race race = new Race();
race.setName("Charlottesville Marathon");
race.setLocation("Charlottesville, VA");
race.setDistance(new BigDecimal(26.2));
em.persist(race);

Na listagem 9, a SimpleJPA lida com todas as solicitações HTTP para criar Race na nuvem. Usar SimpleJPA significa que eu posso recuperar o Race usando uma consulta JPA, como mostrado na listagem 10. (Lembre-se de que você não pode fazer junções com essas consultas, mas pode procurar com números.)

Listagem 10. Como encontrar um race por distância.
Query query = em.createQuery("select o from Race o where o.distance = :dist");
query.setParameter("dist", new BigDecimal(26.2));
		
List<Race> races = query.getResultList();
for(Race race : races){
 System.out.println(race);
}

De números a cadeias de caractere

A mágica de transformar número em cadeia de caractere da SimpleJPA é muito bacana; por exemplo, se você habilitar impressão de consulta em SimpleJPA, é possível ver quais consultas ela emite para SimpleDB. A consulta enviada é mostrada na listagem 11. Observe como distance está codificada.

Listagem 11. A SimpleJPA lida com números de uma maneira ótima!
amazonQuery: Domain=b50-Race, query=select * from `b50-Race`
  where `distance` = '0922337203685477583419999999999999928946'

Preenchimento automático e codificação tornam as coisas bem mais fáceis, você não acha?

Relacionamentos em SimpleJPA

Muito embora o SimpleDB não permita junções de domínio em consultas, ainda assim é possível ter itens relacionados nos domínios. Como mostrei na parte 1, é possível simplesmente armazenar a chave de um objeto relacionado em outro e depois recuperar esse objeto quando precisar dele. É isso o que a SimpleJPA faz também. Por exemplo, mostrei anteriormente como vincular Runners a uma Race usando anotações de JPA. Assim, posso criar uma instância de um Runner, adicionar a instância race existente a ela, e depois persistir a instância Runner , como mostrado na listagem 12:

Listagem 12. Relacionamentos com SimpleJPA
Runner runner = new Runner();
runner.setName("Mark Smith");
runner.setSsn("555-55-5555");
runner.setRace(race);
race.addRunner(runner);
		
em.persist(runner);
em.persist(race); //update the race now that it has a runner

Observe também na listagem 12 que eu tive que atualizar a instância Race para refletir o fato de ter adicionado uma instância Runner a ela (observe também que adicionei um método addRunner a Race que simplesmente adiciona um Runner à Collection interna de Runners).

Mais uma vez, se eu procurar um race por sua distância, também posso obter uma listagem de seus runners, como a listagem 13:

Listagem 13. Mais diversão nos relacionamentos!
Query query = em.createQuery("select o from Race o where o.distance = :dist");
query.setParameter("dist", new BigDecimal(26.2));
		
List<Race> races = query.getResultList();
		
for(Race races : race){
 System.out.println(race);
 List<Runner> runners = race.getRunners();
 for(Runner rnr : runners){
  System.out.println(rnr);
 }
}

Usar uma instância EntityManager permite que eu exclua entidades através do método remove , mostrado na listagem 14:

Listagem 14. Remoção de uma instância de classe
Query query = em.createQuery("select o from Race o where o.distance = :dist");
query.setParameter("dist", new BigDecimal(26.2));
		
List<Race> races = query.getResultList();
		
for(Race races : race){
 em.remove(race);
}

Embora eu tenha removido uma instância Race da listagem 14, os Runners relacionados não são removidos. (Eu posso, é claro, lidar com isso usando uma anotação EntityListeners de JPA, o que significa que posso olhar um evento de remoção e usá-lo para remover instâncias Runner .)

Conclusão

Esse rápido tour de SimpleDB mostrou como manipular objetos em armazenamento de dados não relacionais, usando a API dos serviços da Web da Amazon e a SimpleJPA. A SimpleJPA implementa um subconjunto da API de Persistência Java para tornar a persistência de objeto no SimpleDB mais fácil. Uma das conveniências de usar SimpleJPA, como você viu, é que ela converte automaticamente tipos primitivos nos objetos de cadeia de caractere que o SimpleDB reconhece. A SimpleJPA também lida com as regras de não junção do SimpleDB para você automaticamente, tornando mais fácil modelar relacionamentos. As interfaces abrangentes de listener da SimpleJPA também tornam possível implementar regras lógicas de integridade de dados, que você provavelmente espera de um mundo relacional.

O resultado da SimpleJPA é que ela pode ajudá-lo a acessar escalabilidade significativa e acessível financeiramente de forma rápida. Com a SimpleJPA, é possível aproveitar o conhecimento que você já tem de anos de trabalho com estruturas como Hibernate em um ambiente de armazenamento não relacional, baseado em nuvem.


Recursos para download


Temas relacionados


Comentários

Acesse ou registre-se para adicionar e acompanhar os comentários.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=80
Zone=Tecnologia Java, Cloud computing
ArticleID=512620
ArticleTitle=Desenvolvimento em Java 2.0: Armazenamento em nuvem com o SimpleDB da Amazon, Parte 2
publish-date=08202010