Mis favoritos | Español | Acceder

Uso del almacén de datos a través de JDO

Almacenar datos en una aplicación web escalable puede ser complicado. Un usuario podría estar interactuando con cualquiera de una docena de servidores web en un momento dado y la siguiente solicitud de ese usuario podría dirigirse a un servidor web distinto del servidor que haya gestionado la solicitud anterior. Todos los servidores web deben interactuar con datos que también están distribuidos por docenas de equipos y que posiblemente se encuentran en distintas partes del mundo.

Google App Engine soluciona todos estos problemas. La infraestructura de App Engine se encarga de todas las tareas de distribución, replicación y balanceo de carga de los datos de un API sencilla, además de ofrecer un potente motor de consulta y transacciones.

El almacén de datos de App Engine es uno de los diversos servicios que ofrece App con dos API: un API estándar y otra de nivel inferior. El uso de las API estándar facilita el traslado de las aplicaciones a otros entornos de alojamiento y a otras tecnologías de bases de datos, en caso necesario. Las API estándar "desconectan" la aplicación de los servicios de App Engine. Los servicios de App Engine también ofrecen API de nivel inferior que muestran directamente las funciones del servicio. Puedes utilizar las API de nivel inferior para implementar nuevas interfaces de adaptadores o utilizar las API directamente en tu aplicación.

App Engine permite el uso de dos estándares de API diferentes para el almacén de datos: Objetos de datos Java (JDO) y API de persistencia Java (JPA). Estas interfaces las proporciona DataNucleus Access Platform, una implementación de software libre de varios estándares de persistencia Java, con un adaptador para el almacén de datos de App Engine.

Para el libro de invitados, utilizaremos la interfaz JDO para la recuperación y la publicación de los mensajes de los usuarios.

Configuración de DataNucleus Access Platform

Access Platform necesita un archivo de configuración que le indique que debe utilizar el almacén de datos de App Engine como servidor para la implementación de JDO. En el WAR final, este archivo se denomina jdoconfig.xml y se encuentra en el directorio war/WEB-INF/classes/META-INF/.

Si utilizas Eclipse, este archivo se habrá creado como src/META-INF/jdoconfig.xml. El archivo se copia automáticamente en war/WEB-INF/classes/META-INF/ al crear el proyecto.

Si no utilizas Eclipse, puedes crear el directorio war/WEB-INF/classes/META-INF/ directamente o dejar que se genere durante el proceso de compilación y copiar el archivo de configuración de otra ubicación. La secuencia de comandos de compilación Ant que se describe en Uso de Apache Ant copia este archivo de src/META-INF/.

El contenido del archivo jdoconfig.xml debe ser el siguiente:

<?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>

Mejora de la clase JDO

Al crear clases JDO, debes utilizar anotaciones Java para describir cómo se deben guardar las instancias en el almacén de datos y cómo se deben volver a crear al recuperarlas de dicho almacén. Access Platform conecta las clases de datos a la implementación mediante un paso de procesamiento posterior a la compilación, que DataNucleus denomina "mejora" de las clases.

Si utilizas Eclipse y el complemento de Google, dicho complemento realizará el paso de mejora de la clase JDO automáticamente durante el proceso de compilación.

Si utilizas la secuencia de comandos de compilación Ant que se describe en Uso de Apache Ant, esa secuencia ya incluirá el paso de mejora necesario.

Para obtener más información sobre la mejora de la clase JDO, consulta Uso de JDO.

Anotaciones de JDO y POJO

JDO permite almacenar objetos Java (a veces denominados "objetos Java antiguos y simples" o POJO) en cualquier almacén de datos con un adaptador compatible con JDO, como DataNucleus Access Platform. El kit de desarrollo de software (SDK) de App Engine incluye un complemento Access Platform para el almacén de datos de App Engine. Esto significa que puedes almacenar instancias de clases definidas en el almacén de datos de App Engine y recuperarlas como objetos mediante el API JDO. Puedes indicar a JDO cómo debe almacenar y reconstruir instancias de tu clase utilizando anotaciones Java.

Vamos a crear una clase Greeting para la representación de mensajes individuales publicados en el libro de invitados.

Crea una nueva clase llamada Greeting en el paquete guestbook. (Aquellos usuarios que no utilicen Eclipse pueden crear el archivo Greeting.java en el directorio src/guestbook/). Asigna al archivo de origen el siguiente contenido:

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;
    }
}

Esta sencilla clase define tres propiedades para un saludo: author, content y date. Estos tres campos privados presentan la anotación @Persistent, que indica a DataNucleus que debe almacenarlos como propiedades de objetos en el almacén de datos de App Engine.

Esta clase define captadores y definidores de las propiedades que sólo utiliza la aplicación y no hacen falta para JDO.

La clase también define un campo llamado id, una clave Long que presenta dos anotaciones: @Persistent y @PrimaryKey. El almacén de datos de App Engine tiene una noción de las claves de entidades y puede representar las claves de varias formas en un objeto. En este caso, el campo de clave es un valor entero largo y se establece automáticamente en una ID numérica única cuando se guarda el objeto.

Para obtener más información sobre las anotaciones de JDO, consulta Definición de las clases de datos.

PersistenceManagerFactory

Cada solicitud que utiliza el almacén de datos crea una nueva instancia de la clase PersistenceManager. Para ello, utiliza una instancia de la clase PersistenceManagerFactory.

Una instancia de PersistenceManagerFactory tarda algún tiempo en inicializarse. Afortunadamente, sólo se necesita una instancia para cada aplicación, y esa instancia se puede almacenar en una variable estática que pueden utilizar varias solicitudes y clases. Una forma sencilla de realizar este procedimiento es crear una clase envoltorio singleton para la instancia estática.

Crea una clase nueva PMF en el paquete guestbook (un archivo PMF.java en el directorio src/guestbook/) e incluye el siguiente contenido:

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;
    }
}

Creación y almacenamiento de objetos

Con DataNucleus y la clase Greeting, la lógica de procesamiento de formularios puede almacenar ahora nuevos saludos en el almacén de datos.

Edita src/guestbook/SignGuestbookServlet.java según se indica para que se parezca a lo que se muestra a continuación.

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/es/guestbook.jsp");
    }
}

Este código crea una nueva instancia de Greeting mediante la ejecución del constructor. Para guardar la instancia en el almacén de datos, crea una clase PersistenceManager con una clase PersistenceManagerFactory y, a continuación, transmite la instancia al método makePersistent() de PersistenceManager. La mejora del código de bytes y las anotaciones se toman de ahí. Una vez que se obtiene makePersistent(), el nuevo objeto se guarda en el almacén de datos.

Consultas con JDOQL

El estándar JDO define un mecanismo para consultas de objetos persistentes denominado JDOQL. Puedes utilizar JDOQL para realizar consultas de entidades del almacén de datos de App Engine y recuperar objetos con mejoras JDO.

En este ejemplo, realizaremos un procedimiento sencillo escribiendo el código de consulta directamente en guestbook.jsp. Si se tratara de una aplicación de mayores dimensiones, la lógica de consulta se debería delegar en otra clase.

Edita war/guestbook.jsp y añade las líneas indicadas para obtener un fragmento parecido al siguiente:

<%@ 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 una consulta, debes ejecutar el método newQuery() de una instancia de PersistenceManager con el texto de la consulta como cadena. Al ejecutar el método, se obtiene un objeto de consulta. El método execute() del objeto de consulta realiza la consulta y, a continuación, muestra una lista (List<>) de objetos de resultado del tipo adecuado. La cadena de consulta debe incluir el nombre completo de la clase a la que se dirige la consulta, incluido el nombre del paquete.

Vuelve a crear el proyecto y reinicia el servidor. Visita http://localhost:8080/. Introduce un saludo y envíalo. El saludo aparecerá sobre el formulario. Introduce otro saludo y envíalo. Aparecerán ambos saludos. Prueba a cerrar la sesión y volver a iniciarla utilizando los enlaces e intenta enviar mensajes con la sesión iniciada y con la sesión cerrada.

Sugerencia: en una aplicación real, puede ser una buena idea aplicar formato de escape a los caracteres HTML al mostrar contenido enviado por los usuarios como, por ejemplo, los saludos que se han enviado en esta aplicación. JavaServer Pages Standard Tag Library (JSTL) incluye una serie de rutinas que permiten realizar este procedimiento. App Engine incluye JSTL (y otros JAR de tiempo de ejecución relacionados con JSP), así que no es necesario que los incluya en tu aplicación. Busca la función escapeXml en la biblioteca de etiquetas disponible en http://java.sun.com/jsp/jstl/functions. Para obtener más información, consulta el tutorial de J2EE 1.4 de Sun.

Introducción a JDOQL

El libro de invitados de nuestro ejemplo muestra actualmente todos los mensajes que se han publicado en el sistema. Además, los mensajes aparecen en el orden en que se crearon. Cuando el libro de invitados contenga muchos mensajes, es posible que resulte más útil mostrar sólo los mensajes recientes, empezando por el más actual. Para ello, podemos ajustar la consulta del almacén de datos.

Puedes realizar una consulta con la interfaz JDO mediante JDOQL, un lenguaje de consulta parecido a SQL que permite recuperar objetos de datos. En la página de JSP, la cadena de consulta JDOQL se define con la siguiente línea:

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

Dicho de otro modo, la cadena de consulta JDOQL es la siguiente:

select from guestbook.Greeting

Esta consulta busca en el almacén de datos todas las instancias de la clase Greeting que se han guardado hasta el momento.

Se pueden aplicar filtros en las consultas mediante la inclusión de criterios que deban cumplir las propiedades de un objeto para que ese objeto se muestre como resultado. Por ejemplo, para recuperar sólo los objetos Greeting cuya propiedad author sea alfred@example.com, habría que utilizar la siguiente consulta:

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

Una consulta puede especificar el orden en que se deben mostrar los resultados en lo referente a los valores de las propiedades. Para recuperar todos los objetos Greeting en orden inverso al de su publicación (es decir, del más reciente al más antiguo), se debería utilizar la siguiente consulta:

select from guestbook.Greeting order by date desc

Se pueden realizar consultas que limiten también el número de resultados. Por ejemplo, para obtener solamente los cinco saludos más recientes, se debería utilizar order by y range, tal como se muestra a continuación:

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

Prueba a realizar este procedimiento con la aplicación del libro de invitados. En guestbook.jsp, sustituye la definición de query por lo siguiente:

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

Publica más de cinco saludos en el libro de invitados. Sólo aparecerán los cinco más recientes, en orden cronológico inverso.

Puedes obtener más información sobre las consultas y sobre el lenguaje JDOQL en Consultas e índices.

Siguiente...

Cada aplicación web devuelve HTML generado de forma dinámica a partir del código de la aplicación a través de plantillas o algún otro mecanismo. La mayor parte de las aplicaciones web también necesitan publicar contenido estático, como imágenes, hojas de estilo CSS o archivos JavaScript. Para una mayor eficiencia, App Engine trata los archivos estáticos de forma diferente a los archivos de datos y el código fuente de la aplicación. Vamos a crear ahora una hoja de estilo CSS para esta aplicación como un archivo estático.

Para continuar, consulta la sección Uso de archivos estáticos.