My favorites | Português | Sign in

Uso do armazenamento de dados com JDO

Armazenar dados em um aplicativo da web escalável pode ser complicado. Um usuário pode estar interagindo com muitos servidores da web em um dado momento, e a próxima solicitação do usuário pode ser para um servidor da web diferente do servidor que tratou a solicitação anterior. Todos os servidores da web precisam interagir com dados que também estão distribuídos em dezenas de máquinas, possivelmente em diferentes locais ao redor do mundo.

Você não precisa se preocupar com nada disso com o Google App Engine. A infraestrutura do Google App Engine cuida de toda a distribuição, replicação e balanceamento de carga de dados por trás de uma simples API — e você obtém um poderoso mecanismo de consultas e transações.

O armazenamento de dados do Google App Engine é um dos vários serviços oferecidos pelo Google App Engine com duas APIs: uma API padrão e uma API de nível inferior. Com as APIs padrão, fica mais fácil transportar o seu aplicativo para outros ambientes de hospedagem e outras tecnologias de banco de dados, se necessário. As APIs padrão "desacoplam" o seu aplicativo dos serviços do Google App Engine. Os serviços do Google App Engine também oferecem APIs de nível inferior que expõem os recursos do serviço diretamente. Você pode usar as APIs de nível inferior para implementar interfaces novas para adaptadores ou apenas usar as APIs diretamente no seu aplicativo.

O Google App Engine inclui suporte para dois padrões diferentes de APIs para o armazenamento de dados: JDO (Objetos de dados Java) e JPA (API persistente Java). Essas interfaces são oferecidas pelo DataNucleus Access Platform, uma implementação de software livre de vários padrões persistentes Java, com um adaptador para o armazenamento de dados do Google App Engine.

Para o livro de visitas, usaremos a interface JDO para recuperar e postar mensagens deixadas pelos usuários.

Como definir o DataNucleus Access Platform

A plataforma de acesso precisa que um arquivo de configuração diga a ela quando usar o armazenamento de dados do Google App Engine como o backend para a implementação JDO. No WAR final, esse arquivo é chamado jdoconfig.xml e se encontra no diretório war/WEB-INF/classes/META-INF/.

Se você estiver usando o Eclipse, o arquivo terá sido criado para você e se chama src/META-INF/jdoconfig.xml. Ele é copiado automaticamente para war/WEB-INF/classes/META-INF/ quando você cria o seu projeto.

Se não estiver usando o Eclipse, você pode criar o diretório war/WEB-INF/classes/META-INF/ diretamente ou fazer com que o seu processo de criação crie o diretório e copie o arquivo de configuração de outro lugar. O script de construção do Ant descrito em Como usar o Apache Ant copia esse arquivo do src/META-INF/.

O arquivo jdoconfig.xml deve ter o seguinte conteúdo:

<?xml version="1.0" encoding="utf-8"?>
<jdoconfig xmlns="http://java.sun.com/xml/ns/jdo/jdoconfig"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:noNamespaceSchemaLocation="http://java.sun.com/xml/ns/jdo/jdoconfig">

    <persistence-manager-factory name="transactions-optional">
        <property name="javax.jdo.PersistenceManagerFactoryClass"
            value="org.datanucleus.store.appengine.jdo.DatastoreJDOPersistenceManagerFactory"/>
        <property name="javax.jdo.option.ConnectionURL" value="appengine"/>
        <property name="javax.jdo.option.NontransactionalRead" value="true"/>
        <property name="javax.jdo.option.NontransactionalWrite" value="true"/>
        <property name="javax.jdo.option.RetainValues" value="true"/>
        <property name="datanucleus.appengine.autoCreateDatastoreTxns" value="true"/>
    </persistence-manager-factory>
</jdoconfig>

Aprimoramento da classe JDO

Para criar as classes JDO, você usa as anotações Java para descrever como as instâncias devem ser armazenadas no armazenamento de dados e como elas devem ser recriadas ao serem recuperadas do armazenamento de dados. A plataforma de acesso conecta as classes de dados à implementação usando uma etapa de processamento pós-compilatório, que o DataNucleus chama de "aprimoramento" das classes.

Se você estiver usando o Eclipse e o plug-in do Google, o plug-in realizará a etapa de aprimoramento da classe JDO automaticamente como parte do processo de construção.

Se você estiver usando o script de construção do Ant descrito em Como usar o Apache Ant, ele incluirá a etapa de aprimoramento necessária.

Para obter mais informações sobre o aprimoramento da classe JDO, consulte Como usar a JDO.

Anotações POJOs e JDO

A JDO permite armazenar os objetos Java (às vezes chamados de POJOs, ou Plain Old Java Objects) em qualquer armazenamento de dados com um adaptador compatível com JDO, como o DataNucleus Access Platform. O SDK do Google App Engine inclui um plug-in da plataforma de acesso para o armazenamento de dados do Google App Engine. Isso significa que é possível armazenar instâncias de classes definidas no armazenamento de dados do Google App Engine e recuperá-las como objetos usando a API JDO. Você informa à JDO como armazenar e reconstruir as instâncias de sua classe usando anotações Java.

Vamos criar uma classe Greeting para representar mensagens individuais postadas no livro de visitas.

Crie uma classe nova chamada Greeting no pacote guestbook (quem não está usando o Eclipse deve criar o arquivo Greeting.java no diretório src/guestbook/). Forneça os seguintes conteúdos ao arquivo de origem:

package guestbook;

import java.util.Date;
import javax.jdo.annotations.IdGeneratorStrategy;
import javax.jdo.annotations.IdentityType;
import javax.jdo.annotations.PersistenceCapable;
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.PrimaryKey;
import com.google.appengine.api.users.User;

@PersistenceCapable(identityType = IdentityType.APPLICATION)
public class Greeting {
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long id;

    @Persistent
    private User author;

    @Persistent
    private String content;

    @Persistent
    private Date date;

    public Greeting(User author, String content, Date date) {
        this.author = author;
        this.content = content;
        this.date = date;
    }

    public Long getId() {
        return id;
    }

    public User getAuthor() {
        return author;
    }

    public String getContent() {
        return content;
    }

    public Date getDate() {
        return date;
    }

    public void setAuthor(User author) {
        this.author = author;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public void setDate(Date date) {
        this.date = date;
    }
}

Essa classe simples define três propriedades para uma saudação: author, content e date. Esses três campos privados são anotados com @Persistent para informar que o DataNucleus deve armazená-los como propriedades de objetos no armazenamento de dados do Google App Engine.

Essa classe define as funções set e get das propriedades. Elas são usadas apenas pelo aplicativo e não são necessárias para a JDO.

A classe define também um campo chamado id, um Long anotado como @Persistent e @PrimaryKey. O armazenamento de dados do Google App Engine tem uma noção das chaves de entidade e pode representar a chave de diversas maneiras no objeto. Nesse caso, o campo da chave é um inteiro long, e é definido automaticamente como um ID numérico exclusivo quando o objeto é salvo.

Para obter mais informações sobre as anotações JDO, consulte Como definir classes de dados.

PersistenceManagerFactory

Cada solicitação que usa o armazenamento de dados cria uma instância nova da classe PersistenceManager. Para isso, ela usa uma instância da classe PersistenceManagerFactory.

Uma instância da PersistenceManagerFactory demora para ser inicializada. Felizmente, só é preciso uma instância para o seu aplicativo, e essa instância pode ser armazenada em uma variável estática para ser usada em várias solicitações e várias classes. Uma maneira fácil de fazer isso é criando uma única classe empacotadora para a instância estática.

Crie uma classe nova chamada PMF no pacote guestbook (um arquivo chamado PMF.java no diretório src/guestbook/), e forneça a ela o seguinte conteúdo:

package guestbook;

import javax.jdo.JDOHelper;
import javax.jdo.PersistenceManagerFactory;

public final class PMF {
    private static final PersistenceManagerFactory pmfInstance =
        JDOHelper.getPersistenceManagerFactory("transactions-optional");

    private PMF() {}

    public static PersistenceManagerFactory get() {
        return pmfInstance;
    }
}

Como criar e salvar objetos

Com o DataNucleus e a classe Greeting no lugar, a lógica de processamento do formulário pode agora armazenar as saudações novas no armazenamento de dados.

Edite src/guestbook/SignGuestbookServlet.java conforme indicado, de forma a ficar semelhante a:

package guestbook;

import java.io.IOException;
import java.util.Date;
import java.util.logging.Logger;
import javax.jdo.PersistenceManager;
import javax.servlet.http.*;
import com.google.appengine.api.users.User;
import com.google.appengine.api.users.UserService;
import com.google.appengine.api.users.UserServiceFactory;

import guestbook.Greeting;
import guestbook.PMF;

public class SignGuestbookServlet extends HttpServlet {
    private static final Logger log = Logger.getLogger(SignGuestbookServlet.class.getName());

    public void doPost(HttpServletRequest req, HttpServletResponse resp)
                throws IOException {
        UserService userService = UserServiceFactory.getUserService();
        User user = userService.getCurrentUser();

        String content = req.getParameter("content");
        Date date = new Date();
        Greeting greeting = new Greeting(user, content, date);

        PersistenceManager pm = PMF.get().getPersistenceManager();
        try {
            pm.makePersistent(greeting);
        } finally {
            pm.close();
        }

        resp.sendRedirect("/intl/pt-BR/guestbook.jsp");
    }
}

Esse código cria uma instância Greeting nova ao chamar o construtor. Para salvar a instância no armazenamento de dados, ele cria um PersistenceManager usando uma PersistenceManagerFactory e, em seguida, passa a instância para o método makePersistent() do PersistenceManager. As anotações e o aprimoramento do bytecode se baseiam nisso. Depois que makePersistent() retorna, o novo objeto é armazenado no armazenamento de dados.

Consultas com JDOQL

O padrão JDO define um mecanismo para consultar objetos persistentes chamado JDOQL. O JDOQL pode ser usado para realizar consultas de entidades no armazenamento de dados do Google App Engine e recuperar resultados como objetos JDO aprimorados.

Para este exemplo, manteremos as coisas simples ao gravarmos o código da consulta diretamente no guestbook.jsp. Para um aplicativo maior, pode ser necessário delegar a lógica da consulta para outra classe.

Edite war/guestbook.jsp e adicione as linhas indicadas para que ele se pareça com:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page import="java.util.List" %>
<%@ page import="javax.jdo.PersistenceManager" %>
<%@ page import="com.google.appengine.api.users.User" %>
<%@ page import="com.google.appengine.api.users.UserService" %>
<%@ page import="com.google.appengine.api.users.UserServiceFactory" %>
<%@ page import="guestbook.Greeting" %>
<%@ page import="guestbook.PMF" %>

<html>
  <body>

<%
    UserService userService = UserServiceFactory.getUserService();
    User user = userService.getCurrentUser();
    if (user != null) {
%>
<p>Hello, <%= user.getNickname() %>! (You can
<a href="<%= userService.createLogoutURL(request.getRequestURI()) %>">sign out</a>.)</p>
<%
    } else {
%>
<p>Hello!
<a href="<%= userService.createLoginURL(request.getRequestURI()) %>">Sign in</a>
to include your name with greetings you post.</p>
<%
    }
%>

<%
    PersistenceManager pm = PMF.get().getPersistenceManager();
    String query = "select from " + Greeting.class.getName();
    List<Greeting> greetings = (List<Greeting>) pm.newQuery(query).execute();
    if (greetings.isEmpty()) {
%>
<p>The guestbook has no messages.</p>
<%
    } else {
        for (Greeting g : greetings) {
            if (g.getAuthor() == null) {
%>
<p>An anonymous person wrote:</p>
<%
            } else {
%>
<p><b><%= g.getAuthor().getNickname() %></b> wrote:</p>
<%
            }
%>
<blockquote><%= g.getContent() %></blockquote>
<%
        }
    }
    pm.close();
%>

    <form action="/sign" method="post">
      <div><textarea name="content" rows="3" cols="60"></textarea></div>
      <div><input type="submit" value="Post Greeting" /></div>
    </form>

  </body>
</html>

Para preparar uma consulta, chame o método newQuery() de uma instância PersistenceManager com o texto da consulta como uma string. O método retorna um objeto de consulta. O método execute() do objeto de consulta realiza a consulta e retorna uma List<> de objetos resultantes do tipo apropriado. A string da consulta deve incluir o nome completo da classe a ser consultada, incluindo o nome do pacote.

Recompile o projeto e reinicie o servidor. Visite http://localhost:8080/. Insira uma saudação e envie-a. A saudação aparece acima do formulário. Insira outra saudação e envie-a. As duas saudação serão exibidas. Experimente entrar e sair usando os links e tente enviar mensagens quando estiver conectado e quando não estiver.

Dica: Em um aplicativo do mundo real, é uma boa ideia adicionar código de escape aos caracteres HTML ao exibir o conteúdo enviado pelo usuário, como as saudações nesse aplicativo. A JSTL (Biblioteca padrão de tags da JavaServer Pages) inclui rotinas para isso. Como o Google App Engine inclui a JSTL (e outros JARS de execução relacionados ao JSP), você não precisa incluí-la no seu aplicativo. Procure a função escapeXml na biblioteca de tags http://java.sun.com/jsp/jstl/functions. Consulte o Tutorial do J2EE 1.4 da Sun para obter mais informações.

Apresentação da JDOQL

O nosso livro de visitas exibe atualmente todas as mensagens já postadas no sistema. Ele também as exibe na ordem em que foram criadas. Quando o livro de visitas possuir muitas mensagens, pode ser mais útil exibir apenas as mensagens recentes, deixando a mais recente no topo. Podemos fazer isso ao ajustarmos a consulta do armazenamento de dados.

Use a JDOQL para realizar uma consulta com a interface JDO, uma linguagem de consulta como a SQL para recuperar objetos de dados. Na nossa página JSP, a linha abaixo define a string de consulta JDOQL:

    String query = "select from " + Greeting.class.getName();

Em outras palavras, a string de consulta JDOQL é:

select from guestbook.Greeting

Essa consulta pede para o armazenamento de dados todas as instâncias da classe Greeting salvas até agora.

Uma consulta pode filtrar os resultados ao incluir critérios que devem ser atendidos pelas propriedades de um objeto para ser um resultado. Por exemplo, para recuperar somente os objetos Greeting cuja propriedade author seja alfred@example.com, use a seguinte consulta:

select from guestbook.Greeting where author == 'alfred@example.com'

Uma consulta pode especificar a ordem em que os resultados devem ser retornados em termos de valores de propriedade. Para recuperar todos os objetos Greeting na ordem inversa à que eles foram postados (do mais novo para o mais antigo) use a seguinte consulta:

select from guestbook.Greeting order by date desc

Uma consulta pode limitar os resultados retornados para um intervalo de resultados. Para recuperar apenas as cinco saudações mais recentes, use order by e range juntos, da seguinte maneira:

select from guestbook.Greeting order by date desc range 0,5

Vamos fazer isso no aplicativo do livro de visitas. Em guestbook.jsp, substitua a definição query por:

    String query = "select from " + Greeting.class.getName() + " order by date desc range 0,5";

Poste as saudações no livro de visitas até que haja mais de cinco. Somente as cinco mais recentes são exibidas, em ordem cronológica inversa.

Para obter mais informações sobre consultas e JDOQL, consulte Consultas e índices.

Próximo passo...

Cada aplicativo da web retorna HTML gerado dinamicamente pelo código do aplicativo através de modelos ou de outro mecanismo. A maioria dos aplicativos também precisa servir conteúdo estático como imagens, folhas de estilos CSS ou arquivos JavaScript. Para obter maior eficiência, o Google App Engine trata os arquivos estáticos de maneira diferente dos arquivos de origem e de dados do aplicativo. Vamos criar uma folha de estilos CSS para esse aplicativo, como um arquivo estático.

Vá para Uso de arquivos estáticos.