google app engine

128
Introducción Te damos la bienvenida a Google App Engine. Crear una aplicación de App Engine es fácil y solo tardarás unos minutos. Además, empezar es gratis: sube tu aplicación y compártela con los usuarios inmediatamente, sin ningún tipo de coste ni compromiso. Las aplicaciones de Google App Engine pueden estar escritas en los lenguajes de programación Java o Python. Este tutorial trata el lenguaje de programación Java. Si, por el contrario, utilizas con más frecuencia Python para crear tus aplicaciones, consulta Introducción: Python . En este tutorial aprenderás a: crear una aplicación de App Engine mediante tecnologías web Java estándar, como servlets y páginas JSP, crear un proyecto Java App Engine con y sin Eclipse , utilizar el complemento de Google para Eclipse para el desarrollo de App Engine, utilizar el almacén de datos de App Engine mediante la interfaz estándar de objetos de datos Java (JDO), integrar una aplicación App Engine en el servicio Google Accounts para la autenticación del usuario, subir tu aplicación a App Engine. Al finalizar este tutorial, habrás implementado una aplicación activa: un libro de visitas sencillo que permite a los usuarios publicar mensajes en un tablón de mensajes público. Siguiente... Para empezar a desarrollar las aplicaciones Java Google App Engine, descarga y configura el kit de desarrollo de software Java de App Engine y los componentes relacionados. Para continuar, consulta Instalación del SDK de Java . Instalación del SDK de Java Puedes desarrollar y subir aplicaciones Google App Engine Java con el kit de desarrollo de software (SDK) Java de App Engine. El SDK incluye software para un servidor web que puedes ejecutar en tu propio equipo con el fin de probar las aplicaciones Java. El servidor simula todos los servicios de App Engine, incluida una versión local del almacén de datos, el servicio Google Accounts y la capacidad de extraer direcciones URL y de enviar mensajes de correo electrónico desde tu equipo a través de las API de App Engine. Obtención de JavaE Google App Engine es compatible con Java 5 y con Java 6. Cuando la aplicación Java se ejecuta en App Engine, lo hace a través de la máquina virtual de Java (JVM) 6 y de las bibliotecas estándar. Lo ideal sería que utilizaras Java 6 para compilar y para probar la aplicación, y que te aseguraras de que el servidor local se comporta de forma similar a App Engine. Para los desarrolladores que no puedan acceder fácilmente a Java 6 (como, por ejemplo, los que utilizan Mac OS X), el SDK de App Engine es compatible con Java 5. Puedes subir a App Engine clases compiladas y archivos JAR creados con Java 5. Si es necesario, descarga e instala el kit de desarrollo de Java SE (JDK) para tu plataforma . Los usuarios de Mac pueden consultar el sitio de desarrolladores Java de Apple y descargar e instalar la última versión del kit de desarrollo de Java disponible para Mac OS X. Una vez instalado el JDK, ejecuta los siguientes comandos desde una línea de comandos (en Windows, desde el símbolo del sistema; en Mac OS X, desde Terminal) para comprobar que se pueden ejecutar y para determinar la versión que está instalada. Si tienes instalado Java 6, estos comandos mostrarán un número de versión similar a 1.6.0. Si tienes instalado Java 5, el número de versión será similar a 1.5.0.

Upload: julio-cesar-uribe-catano

Post on 03-Aug-2015

533 views

Category:

Documents


8 download

TRANSCRIPT

Page 1: Google App Engine

IntroducciónTe damos la bienvenida a Google App Engine. Crear una aplicación de App Engine es fácil y solo tardarás unos minutos. Además, empezar es gratis: sube tu aplicación y compártela con los usuarios inmediatamente, sin ningún tipo de coste ni compromiso.Las aplicaciones de Google App Engine pueden estar escritas en los lenguajes de programación Java o Python. Este tutorial trata el lenguaje de programación Java. Si, por el contrario, utilizas con más frecuencia Python para crear tus aplicaciones, consulta Introducción: Python.En este tutorial aprenderás a:

crear una aplicación de App Engine mediante tecnologías web Java estándar, como servlets y páginas JSP, crear un proyecto Java App Engine con y sin Eclipse, utilizar el complemento de Google para Eclipse para el desarrollo de App Engine, utilizar el almacén de datos de App Engine mediante la interfaz estándar de objetos de datos Java (JDO), integrar una aplicación App Engine en el servicio Google Accounts para la autenticación del usuario, subir tu aplicación a App Engine.

Al finalizar este tutorial, habrás implementado una aplicación activa: un libro de visitas sencillo que permite a los usuarios publicar mensajes en un tablón de mensajes público.

Siguiente...Para empezar a desarrollar las aplicaciones Java Google App Engine, descarga y configura el kit de desarrollo de software Java de App Engine y los componentes relacionados.Para continuar, consulta Instalación del SDK de Java.

Instalación del SDK de JavaPuedes desarrollar y subir aplicaciones Google App Engine Java con el kit de desarrollo de software (SDK) Java de App Engine.El SDK incluye software para un servidor web que puedes ejecutar en tu propio equipo con el fin de probar las aplicaciones Java. El servidor simula todos los servicios de App Engine, incluida una versión local del almacén de datos, el servicio Google Accounts y la capacidad de extraer direcciones URL y de enviar mensajes de correo electrónico desde tu equipo a través de las API de App Engine.

Obtención de JavaEGoogle App Engine es compatible con Java 5 y con Java 6. Cuando la aplicación Java se ejecuta en App Engine, lo hace a través de la máquina virtual de Java (JVM) 6 y de las bibliotecas estándar. Lo ideal sería que utilizaras Java 6 para compilar y para probar la aplicación, y que te aseguraras de que el servidor local se comporta de forma similar a App Engine.Para los desarrolladores que no puedan acceder fácilmente a Java 6 (como, por ejemplo, los que utilizan Mac OS X), el SDK de App Engine es compatible con Java 5. Puedes subir a App Engine clases compiladas y archivos JAR creados con Java 5.Si es necesario, descarga e instala el kit de desarrollo de Java SE (JDK) para tu plataforma. Los usuarios de Mac pueden consultar el sitio de desarrolladores Java de Apple y descargar e instalar la última versión del kit de desarrollo de Java disponible para Mac OS X.Una vez instalado el JDK, ejecuta los siguientes comandos desde una línea de comandos (en Windows, desde el símbolo del sistema; en Mac OS X, desde Terminal) para comprobar que se pueden ejecutar y para determinar la versión que está instalada. Si tienes instalado Java 6, estos comandos mostrarán un número de versión similar a 1.6.0. Si tienes instalado Java 5, el número de versión será similar a 1.5.0.java -version

javac -version

Uso de Eclipse y del complemento de Google para EclipseSi usas el entorno de desarrollo Eclipse, la forma más fácil de desarrollar, de probar y de subir aplicaciones de App Engine es mediante el complemento de Google para Eclipse. Este complemento incluye todo lo necesario para crear, probar e implementar la aplicación con Eclipse.El complemento está disponible para las versiones 3.3, 3.4 y 3.5 de Eclipse. Puedes instalarlo a través de la función de actualización de software de Eclipse. Las ubicaciones de la instalación son las siguientes:

Complemento de Google para la versión 3.3 de Eclipse (Europa): http://dl.google.com/eclipse/plugin/3.3

Complemento de Google para la versión 3.4 de Eclipse (Ganymede): http://dl.google.com/eclipse/plugin/3.4

Complemento de Google para la versión 3.5 de Eclipse (Galileo): http://dl.google.com/eclipse/plugin/3.5

Para obtener información detallada sobre cómo utilizar la función de actualización de software para instalar el complemento y cómo crear un proyecto nuevo, consulta Uso del complemento de Google para Eclipse.

Obtención del SDKSi utilizas Eclipse y el complemento de Google, puedes instalar el SDK de App Engine desde Eclipse mediante la función de actualización de software. Si aún no lo tienes, instala el componente SDK de Java de Google App Engine a través de las ubicaciones anteriores.Si no utilizas ni Eclipse ni el complemento de Google, puedes descargar el SDK Java de App Engine como un archivo ZIP.

Page 2: Google App Engine

Descarga el SDK Java de App Engine. Descomprime el archivo en una ubicación adecuada de tu disco duro.Nota: cuando se descomprime el archivo, se crea un directorio cuyo nombre es similar a appengine-java-sdk-X.X.X, donde X.X.X es el número de versión del SDK. En este documento, nos referiremos a este directorio como appengine-java-sdk/. Una vez descomprimido el archivo, puedes cambiar el nombre del directorio.

Aplicaciones de pruebaEl SDK Java de App Engine incluye varias aplicaciones de prueba en el directorio demos/. La versión final de la aplicación de libro de visitas que crearás con este tutorial se incluye en el directorio guestbook/. Esta aplicación de prueba se ha compilado previamente para que puedas utilizarla de inmediato.Si utilizas Eclipse, el SDK se encuentra en el directorio de instalación de Eclipse, en plugins/com.google.appengine.eclipse.sdkbundle_VERSION/, donde VERSION es un identificador de versión del SDK. Desde la línea de comandos, cambia el directorio de trabajo actual por este otro directorio para ejecutar el siguiente comando. Si utilizas Mac OS X o Linux, puede que tengas que asignar permisos de ejecución a los archivos de comandos para poder ejecutarlos (como ocurre con el comando chmod u+x dev_appserver.sh).Si utilizas Windows, inicia la demostración del libro de visitas en el servidor de desarrollo mediante la ejecución del siguiente comando en el símbolo del sistema:appengine-java-sdk\bin\dev_appserver.cmd appengine-java-sdk\demos\guestbook\war

Si utilizas Mac OS X o Linux, ejecuta el siguiente comando:./appengine-java-sdk/bin/dev_appserver.sh appengine-java-sdk/demos/guestbook/war

El servidor de desarrollo se inicia y detecta las solicitudes en el puerto 8080. Accede a la siguiente URL mediante el navegador: http://localhost:8080/

Nota: si inicias el servidor de desarrollo desde Eclipse mediante el complemento de Google para Eclipse (tal como se explica más adelante), el servidor utiliza el puerto 8888 de forma predeterminada: http://localhost:8888/.Para obtener más información sobre la ejecución del servidor web de desarrollo desde la línea de comandos y de cómo cambiar el número de puerto que utiliza, consulta el documento de referencia del servidor web de desarrollo.Para detener el servidor, asegúrate de que la ventana del símbolo del sistema esté activa y, a continuación, pulsa Control-C.

Siguiente...El entorno de desarrollo te permite desarrollar y probar aplicaciones App Engine completas en tu equipo. Comencemos por un proyecto sencillo.Para continuar, consulta Creación de un proyecto.

Creación de un proyectoLas aplicaciones App Engine Java utilizan el estándar Java Servlet para interactuar con el entorno del servidor web. Los archivos de la aplicación, junto con las clases compiladas, los archivos JAR, los archivos estáticos y los archivos de configuración, están organizados en una estructura de directorio que utiliza el formato estándar WAR para aplicaciones web Java. Puedes utilizar el proceso de desarrollo que desees para desarrollar servlets web y para generar un directorio WAR. Los archivos del directorio WAR no son compatibles con el SDK.

El directorio del proyectoEn este tutorial se utilizará un único directorio denominado Guestbook/ para todos los archivos del proyecto. Un subdirectorio llamado src/ contiene el código fuente Java, mientras que otro subdirectorio llamado war/ contiene la aplicación completa organizada en el formato WAR. El proceso de compilación compila los archivos de código fuente Java y coloca las clases compiladas en la ubicación apropiada en war/.El directorio completo del proyecto es similar a:Guestbook/ src/ ...Java source code... META-INF/ ...other configuration... war/ ...JSPs, images, data files... WEB-INF/ ...app configuration... lib/ ...JARs for libraries... classes/ ...compiled classes...

Page 3: Google App Engine

Si utilizas Eclipse, crea un nuevo proyecto haciendo clic en el botón "New Web Application" (Nueva aplicación web) en la barra de

herramientas: Asigna al proyecto un nombre ("Project name"), por ejemplo, Guestbook, y un paquete ("Package"), por ejemplo, guestbook. Asegúrate también de que la casilla "Use Google Web Toolkit" (Utilizar Google Web Toolkit) esté desactivada y de que la casilla "Use Google App Engine" (Utilizar Google App Engine) esté activada. Para obtener más información, consulta Uso del complemento de Google para Eclipse. El asistente crea la estructura de directorios y los archivos tal y como se describe a continuación.

Si no utilizas Eclipse, crea la estructura de directorio descrita anteriormente. Mientras lees los archivos descritos en esta sección, crea los archivos con los nombres y las ubicaciones especificados.Puedes también copiar la nueva plantilla del proyecto, que se incluye en el SDK, en el directorio appengine-java-sdk/demos/new_project_template/.

La clase ServletLas aplicaciones App Engine Java utilizan el API Java Servlet para interactuar con el servidor web. Un servlet HTTP es una clase de aplicación que puede procesar y responder solicitudes web. Esta clase amplía la clase javax.servlet.GenericServlet o la clase javax.servlet.http.HttpServlet.Nuestro proyecto del libro de visitas comienza con una clase servlet sencilla que muestra un mensaje.

Si no utilizas el complemento de Eclipse, crea los directorios de la ruta src/guestbook/ y el archivo de clase servlet descrito a continuación.El directorio src/guestbook/ incluye un archivo denominado GuestbookServlet.java que contiene lo siguiente:

package guestbook;

import java.io.IOException;import javax.servlet.http.*;

public class GuestbookServlet extends HttpServlet {    public void doGet(HttpServletRequest req, HttpServletResponse resp)            throws IOException {        resp.setContentType("text/plain");        resp.getWriter().println("Hello, world");    }}

El archivo web.xmlCuando el servidor web recibe una solicitud, decide qué clase servlet se invoca mediante un archivo de configuración conocido como el descriptor de implementación de la aplicación web. Este archivo se denomina web.xml y se encuentra en el directorio war/WEB-INF/ del WAR. WEB-INF/ y web.xml forman parte de la especificación del servlet.El directorio war/WEB-INF/ incluye un archivo denominado web.xml que contiene lo siguiente:

<?xml version="1.0" encoding="utf-8"?><!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" "http://java.sun.com/dtd/web-app_2_3.dtd">

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">    <servlet>        <servlet-name>guestbook</servlet-name>        <servlet-class>guestbook.GuestbookServlet</servlet-class>    </servlet>    <servlet-mapping>        <servlet-name>guestbook</servlet-name>        <url-pattern>/guestbook</url-pattern>    </servlet-mapping>    <welcome-file-list>        <welcome-file>index.html</welcome-file>    </welcome-file-list></web-app>

Este archivo web.xml declara un servlet denominado guestbook y lo asigna a la ruta de URL /guestbook. Especifica también que siempre que el usuario vaya en busca de una ruta de URL que no esté ya asignada a un servlet y que represente una ruta del directorio dentro del WAR de la aplicación, el servidor debe buscar un archivo denominado index.html en dicho directorio y suministrarlo si lo encuentra.

Page 4: Google App Engine

El archivo appengine-web.xml

App Engine necesita un archivo de configuración adicional para poder desarrollar y ejecutar la aplicación. Este archivo se denomina appengine-web.xml y se encuentra en WEB-INF/ junto con web.xml. Incluye el ID registrado de la aplicación (que Eclipse crea a partir de un ID vacío que rellenas más adelante), el número de versión de la aplicación y las listas de archivos que se deben tratar como archivos estáticos (por ejemplo, las imágenes y las hojas de estilo CSS), así como archivos de recursos (por ejemplo, los archivos JSP y otros datos de aplicación).El directorio war/WEB-INF/ incluye un archivo denominado appengine-web.xml que contiene lo siguiente:

<?xml version="1.0" encoding="utf-8"?><appengine-web-app xmlns="http://appengine.google.com/ns/1.0">    <application></application>    <version>1</version></appengine-web-app>

appengine-web.xml es específico de App Engine y no forma parte del estándar de servlet. En el directorio appengine-java-sdk/docs/, puedes encontrar archivos de esquema XML que describen el formato de este archivo en el SDK. Para obtener más información acerca de este archivo, consulta Configuración de una aplicación.

Ejecución del proyectoEl SDK de App Engine incluye una aplicación de servidor web que puedes utilizar para probar la aplicación. El servidor simula los servicios y el entorno de App Engine, que incluyen restricciones en la zona de pruebas, el almacén de datos y los servicios.Si utilizas Eclipse, puedes iniciar el servidor de desarrollo en el depurador de Eclipse. Asegúrate de seleccionar el proyecto ("Guestbook" o libro de visitas) y, a continuación, en el menú Run (Ejecutar) selecciona Debug As > Web Application (Depurar como > Aplicación web). Para obtener más información sobre cómo crear la configuración de depuración, consulta Uso del complemento de Google para Eclipse.

Si no utilizas Eclipse, consulta Uso de Apache Ant para obtener una secuencia de comandos de compilación que puede compilar el proyecto e iniciar el servidor de desarrollo. Para iniciar el servidor con esta secuencia de comandos de compilación, introduce el siguiente comando: ant runserver. Para detener el servidor, pulsa Control-C.

Realización de pruebas de la aplicaciónInicia el servidor y, a continuación, accede a la URL del servidor mediante el navegador. Si utilizas Eclipse y el complemento de Google para Eclipse, el servidor se ejecuta de forma predeterminada a través del puerto 8888:

http://localhost:8888/guestbook

Si utilizas el comando dev_appserver para iniciar el servidor, el puerto predeterminado es 8080: http://localhost:8080/guestbook

En el resto del tutorial se asume que el servidor utiliza el puerto 8888.El servidor ejecuta el servlet y muestra el mensaje en el navegador.

Siguiente...Enhorabuena, acabas de crear una aplicación de App Engine. Puedes implementar esta sencilla aplicación ahora mismo y compartirla con usuarios de todo el mundo.Esta aplicación muestra un saludo general a todos los usuarios. Añadamos una función para personalizar el saludo para cada usuario que utilice Google Accounts.Para continuar, consulta Uso del servicio de usuarios.

Uso del servicio de usuarios

Google App Engine ofrece varios servicios útiles basados en la infraestructura de Google a los que pueden acceder las aplicaciones mediante una serie de bibliotecas incluidas en el SDK. Uno de ellos es el servicio de usuarios, que te permite integrar la aplicación con las cuentas de usuario de Google. Con el servicio de usuarios, los usuarios pueden utilizar sus cuentas de Google existentes para acceder a la aplicación.Utilizaremos el servicio de usuarios para personalizar el saludo de esta aplicación.

Uso del servicio de usuariosModifica src/guestbook/GuestbookServlet.java tal como se indica para que se parezca a lo que se muestra a continuación:

Page 5: Google App Engine

package guestbook;

import java.io.IOException;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;

public class GuestbookServlet extends HttpServlet {    public void doGet(HttpServletRequest req, HttpServletResponse resp)              throws IOException {        UserService userService = UserServiceFactory.getUserService();        User user = userService.getCurrentUser();

        if (user != null) {            resp.setContentType("text/plain");            resp.getWriter().println("Hello, " + user.getNickname());        } else {            resp.sendRedirect(userService.createLoginURL(req.getRequestURI()));        }    }}

Si utilizas Eclipse y tu servidor de desarrollo se ejecuta en el depurador, cuando guardes los cambios realizados en este archivo, Eclipse compilará el nuevo código de forma automática y, a continuación, intentará insertarlo en el servidor en ejecución. Los cambios realizados en las clases, en las JSP, en los archivos estáticos y en appengine-web.xml se reflejan de inmediato en el servidor en ejecución sin necesidad de reiniciarlo. Si modificas web.xml o cualquier otro archivo de configuración, debes detener e iniciar el servidor para ver los cambios.

Si utilizas Ant, debes detener el servidor y volver a crear el proyecto para ver los cambios realizados en el código fuente. Los cambios realizados en las JSP y en los archivos estáticos no requieren que se reinicie el servidor.Vuelve a crear tu proyecto y reinicia el servidor en caso necesario. Para probar la aplicación, consulta la URL del servlet en el navegador:

http://localhost:8888/guestbook

En lugar de mostrar el mensaje, el servidor te solicita una dirección de correo electrónico. Introduce una dirección de correo electrónico (como, por ejemplo, [email protected]) y, a continuación, haz clic en el botón para acceder. La aplicación muestra un mensaje que ahora contiene la dirección de correo electrónico que has introducido.

El nuevo código de la clase GuestbookServlet utiliza el API de usuarios para comprobar si el usuario ha accedido con una cuenta de Google. Si no es así, se redirecciona al usuario a la pantalla de acceso de Google Accounts. userService.createLoginURL(...) devuelve la URL de la pantalla de acceso. La función de acceso sabe cómo redireccionar al usuario a la aplicación de nuevo gracias a la URL que se transfiere a createLoginURL(...) y que, en este caso, se trata de la URL de la página actual.

El servidor de desarrollo sabe cómo simular la función de acceso a Google Accounts. Si se ejecuta en tu equipo local, se redirecciona a la página en la que puedes introducir una dirección de correo electrónico para simular un acceso a la cuenta. Si se ejecuta en App Engine, se redirecciona a la pantalla real de Google Accounts.Ahora has accedido a tu aplicación de prueba. Si vuelves a cargar la página, el mensaje vuelve a aparecer.Para permitir que el usuario acceda, proporciona un enlace a la pantalla de acceso generado por el método createLogoutURL(). Ten en cuenta que al hacer clic en un enlace de salida, el usuario sale de todos los servicios de Google.

Siguiente...Ahora que sabemos cómo identificar al usuario, podemos invitarlo a publicar mensajes en el libro de visitas. Diseñemos una interfaz de usuario para esta aplicación mediante la tecnología JavaServer Pages (JSP).Para continuar, consulta Uso de JSP.

Uso de JSPAunque podríamos generar el código HTML para la interfaz de usuario directamente a partir del código Java del servlet, sería algo difícil de mantener ya que el código HTML se complica. Es más conveniente utilizar un sistema de plantillas en el que la interfaz de usuario esté diseñada e implementada en archivos independientes con los marcadores y la lógica necesarios para insertar los datos proporcionados por la aplicación. Existen muchos sistemas de plantillas disponibles para Java que podrían ser compatibles con App Engine.

Page 6: Google App Engine

En este tutorial, utilizaremos la tecnología JavaServer Pages (JSP) para implementar la interfaz de usuario del libro de visitas. Las JSP forman parte del estándar de servlet. App Engine compila los archivos JSP en el WAR de la aplicación de forma automática y los asigna a rutas de URL.

¡Hola, JSP!

La aplicación de libro de visitas escribe cadenas en un flujo de salida, aunque esto también podría escribirse como una JSP. Empecemos trasladando la última versión del ejemplo a una JSP.En el directorio war/, crea un archivo llamado guestbook.jsp con el siguiente contenido:

<%@ page contentType="text/html;charset=UTF-8" language="java" %><%@ page import="com.google.appengine.api.users.User" %><%@ page import="com.google.appengine.api.users.UserService" %><%@ page import="com.google.appengine.api.users.UserServiceFactory" %>

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

</body></html>

De forma predeterminada, cualquier archivo en war/ o en un subdirectorio (distinto de WEB-INF/) cuyo nombre termine en .jsp se asigna de forma automática a una ruta de URL. La ruta de URL es la ruta al archivo .jsp, incluido el nombre de archivo. Esta JSP se asigna de forma automática a la URL /guestbook.jsp.En la aplicación de libro de visitas, queremos que esta sea la página principal de la aplicación y que aparezca cuando un usuario acceda a la URL /. Una forma sencilla de hacer esto es declarar en web.xml que guestbook.jsp es el servlet de "bienvenida" (welcome) de esta ruta.

Modifica war/WEB-INF/web.xml y reemplaza el elemento <welcome-file> actual de <welcome-file-list>. Asegúrate de eliminar index.html de la lista, ya que los archivos estáticos tienen preferencia sobre las JSP y sobre los servlets.

    <welcome-file-list>        <welcome-file>guestbook.jsp</welcome-file>    </welcome-file-list>

Consejo: si utilizas Eclipse, el editor puede abrir este archivo en modo de "diseño". Para modificar este archivo como XML, selecciona la pestaña "Source" (Código fuente) en la parte inferior del marco.Detén el servidor de desarrollo y, a continuación, inícialo. Consulta la siguiente URL:

http://localhost:8888/

La aplicación muestra el contenido de guestbook.jsp, incluido el apodo del usuario si este ha accedido.Cuando se carga una JSP por primera vez, el servidor de desarrollo la convierte en código fuente Java y, a continuación, compila este código en código de bytes de Java. El código fuente Java y la clase compilada se guardan en un directorio temporal. El servidor de desarrollo vuelve a generar y a compilar las JSP de forma automática si los archivos JSP originales se modifican.En el momento en que se sube la aplicación a App Engine, el SDK compila todas las JSP en código de bytes y sube únicamente el código de bytes. Cuando la aplicación se ejecuta en App Engine, utiliza las clases JSP compiladas.

Page 7: Google App Engine

El formulario del libro de visitasLa aplicación de libro de invitados necesita un formulario web para que el usuario publique un nuevo saludo, así como una forma de procesar ese formulario. El código HTML del formulario se inserta en la JSP. El destino del formulario es una nueva URL, /sign, que controla una nueva clase de servlet, SignGuestbookServlet. SignGuestbookServlet procesa el formulario y, a continuación, redirecciona el navegador del usuario a /guestbook.jsp otra vez. Por el momento, el nuevo servlet únicamente escribe el mensaje publicado en el registro.

Modifica guestbook.jsp y escribe las líneas que se muestran a continuación justo antes de la etiqueta </body> de cierre:

...

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

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

package guestbook;

import java.io.IOException;import java.util.logging.Logger;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;

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");        if (content == null) {            content = "(No greeting)";        }        if (user != null) {            log.info("Greeting posted by user " + user.getNickname() + ": " + content);        } else {            log.info("Greeting posted anonymously: " + content);        }        resp.sendRedirect("/guestbook.jsp");    }}

Modifica war/WEB-INF/web.xml y añade las siguientes líneas para declarar el servlet SignGuestbookServlet y para asignarlo a la URL /sign:

<web-app xmlns="http://java.sun.com/xml/ns/javaee" version="2.5">    ...

    <servlet>        <servlet-name>sign</servlet-name>        <servlet-class>guestbook.SignGuestbookServlet</servlet-class>    </servlet>    <servlet-mapping>

Page 8: Google App Engine

        <servlet-name>sign</servlet-name>        <url-pattern>/sign</url-pattern>    </servlet-mapping>

    ...</web-app>

Este nuevo servlet utiliza la clase java.util.logging.Logger para escribir mensajes en el registro. Puedes controlar el comportamiento de esta clase a través de un archivo logging.properties y de un conjunto de propiedades del sistema en el archivo appengine-web.xml de la aplicación. Si utilizas Eclipse, tu aplicación se creó con una versión predeterminada de este archivo en el directorio src/ de la aplicación y con la propiedad del sistema adecuada.

Si no utilizas Eclipse, debes configurar el archivo de configuración del registrador de forma manual. Copia el archivo de ejemplo del SDK appengine-java-sdk/config/user/logging.properties en el directorio war/WEB-INF/ de la aplicación. A continuación, modifica el archivo war/WEB-INF/appengine-web.xml de la aplicación tal como se indica:

<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">    ...

    <system-properties>        <property name="java.util.logging.config.file" value="WEB-INF/logging.properties"/>    </system-properties>

</appengine-web-app>

El servlet registra mensajes a través de un nivel de registro INFO (para lo que utiliza log.info()). El nivel de registro predeterminado es WARNING, que suprime los mensajes INFO del resultado. Para cambiar el nivel de registro de todas las clases del paquete guestbook, modifica el archivo logging.properties y añade una entrada para guestbook.level, tal como se muestra a continuación:

.level = WARNINGguestbook.level = INFO

...

Consejo: si tu aplicación registra mensajes a través del API java.util.logging.Logger mientras se ejecuta en App Engine, App Engine registra los mensajes y los pone a tu disposición para que los explores en la consola de administración y para que los descargues mediante la herramienta AppCfg. La consola de administración te permite explorar los mensajes por nivel de registro.Vuelve a compilar, reinicia y, a continuación, prueba http://localhost:8888/. Se muestra el formulario. Introduce texto en el formulario y envíalo. El navegador envía el formulario a la aplicación y, a continuación, se redirecciona al formulario vacío. El servidor registra los datos de saludo que introdujiste en la consola.

Siguiente...Disponemos de una interfaz de usuario en la que se solicita al usuario que introduzca un saludo. Ahora se necesita una forma de recordar los saludos que los usuarios publican para mostrárselos a otros usuarios. Para ello, utilizaremos el almacén de datos de App Engine.Para continuar, consulta Uso del almacén de datos a través de JDO.

Uso del almacén de datos a través de JDOAlmacenar 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 equilibrio 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 Engine con dos API: un API estándar y otra de nivel inferior. El uso de las API estándares 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ándares "desconectan" la aplicación de los servicios de App Engine. Los servicios de App Engine ofrecen también varias 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 utilizarlas directamente en la aplicación.

Page 9: Google App Engine

App Engine es compatible con dos estándares de API diferentes para el almacén de datos: Objetos de datos Java (JDO) y API de persistencia Java (JPA). DataNucleus Access Platform, una implementación de código abierto de varios estándares de persistencia Java que dispone de un adaptador para el almacén de datos de App Engine, proporciona estas interfaces.

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

Configuración de DataNucleus Access PlatformAccess 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 crea como src/META-INF/jdoconfig.xml. Cuando creas el proyecto, el archivo se copia automáticamente en war/WEB-INF/classes/META-INF/.

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 archivo jdoconfig.xml debe contener lo 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 JDOCuando creas 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 realiza 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, dicha secuencia incluye el paso de mejora necesario.

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

POJO y anotaciones de JDOJDO 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 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 las instancias de tu clase a través de anotaciones Java.

Vamos a crear una clase Greeting para representar los mensajes individuales publicados en el libro de visitas.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:

Page 10: Google App Engine

package guestbook;

import com.google.appengine.api.datastore.Key;import com.google.appengine.api.users.User;

import java.util.Date;import javax.jdo.annotations.IdGeneratorStrategy;import javax.jdo.annotations.PersistenceCapable;import javax.jdo.annotations.Persistent;import javax.jdo.annotations.PrimaryKey;

@PersistenceCapablepublic class Greeting {    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    private Key key;

    @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 Key getKey() {        return key;    }

    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 de un saludo author (autor), content (contenido) y date (fecha). Estos tres campos privados presentan la anotación @Persistent, que indica a DataNucleus que debe almacenarlos como propiedades de los objetos en el almacén de datos de App Engine.

Page 11: Google App Engine

Esta clase define captadores y definidores de las propiedades que solo utiliza la aplicación. Con el uso de definidores, puedes garantizar de forma sencilla que la implementación de JDO reconozca las actualizaciones. La modificación de los campos omite directamente la función de JDO que guarda los campos actualizados de forma automática, a menos que realices otros cambios en el código que habiliten esto.

La clase también define un campo key (clave), es decir, una clase Key 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 el objeto. La clase Key representa todos los aspectos de las claves del almacén de datos de App Engine, incluido un ID numérico que se define automáticamente como un valor exclusivo cuando se guarda el objeto.Para obtener más información acerca de las anotaciones de JDO, consulta Definición de las clases de datos.

La clase PersistenceManagerFactoryTodas las solicitudes que utilizan el almacén de datos crean una nueva instancia de la clase PersistenceManager. Para ello, utilizan una instancia de la clase PersistenceManagerFactory.

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

Crea una nueva clase denominada PMF en el paquete guestbook (un archivo denominado PMF.java en el directorio src/guestbook/) que contenga 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 objetosCon DataNucleus y la clase Greeting, la lógica de procesamiento de formularios puede almacenar ahora nuevos saludos en el almacén de datos.

Modifica src/guestbook/SignGuestbookServlet.java tal como 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)

Page 12: Google App Engine

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

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

Consultas con JDOQLEl estándar JDO define un mecanismo para las consultas de objetos persistentes denominado JDOQL. Puedes utilizar JDOQL para realizar consultas de entidades del almacén de datos de App Engine y para recuperar objetos con mejoras de la clase JDO.En este ejemplo, no nos complicaremos y escribiremos 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.Modifica war/guestbook.jsp y añade las líneas indicadas de forma que se parezca a lo que se muestra a continuación:

<%@ 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();

Page 13: Google App Engine

    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 PersistenceManager con el texto de la consulta como cadena. El método devuelve un objeto de consulta. El método execute() del objeto de consulta realiza la consulta y, a continuación, muestra una lista List<> con 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. Consulta http://localhost:8888/. Introduce un saludo y envíalo. El saludo aparecerá encima del formulario. Introduce otro saludo y envíalo. Se mostrarán los dos saludos. Prueba a salir y a volver a acceder utilizando los enlaces; a continuación, intenta enviar mensajes con la sesión iniciada y con la sesión cerrada.

Consejo: en una aplicación real, puede ser una buena idea utilizar caracteres de escape con los caracteres HTML cuando se muestra el contenido enviado por los usuarios como, por ejemplo, los saludos de 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 archivos JAR de tiempo de ejecución relacionados con JSP), así que no es necesario que los incluyas en la 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 JDOQLEl libro de invitados 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 solo los mensajes recientes y que aparezca en primer lugar el mensaje más reciente. 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 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.Una consulta puede especificar el orden en que se deben mostrar los resultados en términos de los valores de las propiedades. Para recuperar todos los objetos Greeting en el orden inverso al de su publicación (es decir, del más reciente al más antiguo), se debería utilizar la siguiente consulta:

Page 14: Google App Engine

select from guestbook.Greeting order by date descSe 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,5Prueba a realizar este procedimiento con la aplicación del libro de visitas. 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 visitas. solo se muestran los cinco más recientes en orden cronológico inverso.Puedes obtener más información acerca de las consultas y del lenguaje JDOQL en Consultas e índices.

Siguiente...Todas las aplicaciones web devuelven código HTML generado de forma dinámica a partir del código de la aplicación mediante 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 no trata igual los archivos estáticos que el código fuente de la aplicación y los archivos de datos. Vamos a crear ahora una hoja de estilo CSS para esta aplicación que sea un archivo estático.Para continuar, consulta Uso de archivos estáticos.

Uso de archivos estáticosHay muchos casos en los que querrás mostrar los archivos estáticos directamente en el navegador web. Las imágenes, las hojas de estilo CSS, el código JavaScript, los vídeos y las animaciones Flash se suelen mostrar directamente en el navegador. Para una mayor eficiencia, App Engine muestra los archivos estáticos desde servidores independientes en lugar de los que invocan servlets.

De forma predeterminada, App Engine pone a tu disposición todos los archivos del WAR como archivos estáticos, salvo las JSP y los archivos de WEB-INF/. Cualquier solicitud de una URL cuya ruta coincida con un archivo estático muestra el archivo directamente en el servidor, incluso si la ruta coincide también con una asignación de filtro o de servlet. Puedes configurar los archivos que quieres que App Engine considere como archivos estáticos a través del archivo appengine-web.xml.

Cambiemos la apariencia de nuestro libro de visitas con una hoja de estilo CSS. En este ejemplo, no modificaremos la configuración de los archivos estáticos. Para obtener más información sobre la configuración de archivos estáticos y de archivos de recursos, consulta Configuración de aplicaciones.

Una hoja de estilo sencillaEn el directorio war/, crea un directorio llamado stylesheets/. En este directorio, crea un archivo llamado main.css con el siguiente contenido:

body { font-family: Verdana, Helvetica, sans-serif; background-color: #FFFFCC;}Modifica war/guestbook.jsp e inserta las siguientes líneas justo después de la línea <html> en la parte superior:<html> <head> <link type="text/css" rel="stylesheet" href="/stylesheets/main.css" /> </head>

<body> ... </body></html>

Consulta http://localhost:8888/. La nueva versión utiliza la hoja de estilo.

Siguiente...Ha llegado la hora de mostrarle al mundo la versión definitiva de tu aplicación.Para continuar, consulta Subida de aplicaciones.

Page 15: Google App Engine

Subida de aplicacionesPuedes crear y administrar aplicaciones en App Engine mediante la consola de administración. Una vez que hayas registrado el ID de la aplicación, súbela a App Engine mediante el complemento de Eclipse o mediante una herramienta de línea de comandos del SDK.

Nota: una vez que hayas registrado el ID de la aplicación, podrás eliminarlo, pero no podrás volver a registrar este mismo ID. Puedes omitir los pasos siguientes si no deseas registrar un ID en estos momentos.

Registro de la aplicaciónPuedes crear y administrar aplicaciones web en App Engine con la consola de administración de App Engine a través de la siguiente URL:

https://appengine.google.com/

Accede a App Engine con tu cuenta de Google. Si no tienes una cuenta de Google, puedes crear una con una dirección de correo electrónico y con una contraseña.Para crear una nueva aplicación, haz clic en el botón "Crear una aplicación". Sigue las instrucciones para registrar el ID de la aplicación, es decir, el nombre exclusivo de esta aplicación. Si decides utilizar el nombre de dominio gratuito appspot.com, la URL completa de la aplicación será http://application-id.appspot.com/. También puedes comprar un nombre de dominio de nivel superior para la aplicación o utilizar un nombre que ya tengas registrado.Modifica el archivo appengine-web.xml y, a continuación, cambia el valor del elemento <application> de forma que sea el ID registrado de la aplicación.

Subida de la aplicaciónPuedes subir la aplicación a través de Eclipse o de un comando del símbolo del sistema.

Subida desde EclipsePuedes subir el código y los archivos de la aplicación desde Eclipse a través del complemento de Google.

Para subir la aplicación desde Eclipse, haz clic en el botón de implementación de App Engine de la barra de herramientas: .Introduce el nombre de usuario (tu dirección de correo electrónico) y la contraseña de tu cuenta de Google cuando el sistema lo solicite y, a continuación, haz clic en el botón Upload (Subir). Eclipse obtiene el ID de la aplicación y la información sobre la versión a partir del archivo appengine-web.xml y sube el contenido del directorio war/.

Subida a través del símbolo del sistemaPuedes subir el código y los archivos de la aplicación a través de un comando del SDK llamado appcfg.cmd (Windows) o appcfg.sh (Mac OS X y Linux).

AppCfg es una herramienta multiuso para interactuar con la aplicación en App Engine. El comando toma el nombre de una acción, la ruta al directorio war/ de la aplicación y otras opciones. Para subir el código y los archivos de la aplicación a App Engine, utiliza la acción update.

Para subir la aplicación en Windows:..\appengine-java-sdk\bin\appcfg.cmd update war

Para subir la aplicación en Mac OS X o en Linux:../appengine-java-sdk/bin/appcfg.sh update warIntroduce el nombre de usuario y la contraseña de tu cuenta de Google cuando el sistema lo solicite.

Acceso a la aplicaciónUna vez hecho esto, podrás ver la aplicación en ejecución en App Engine. Si estableces un nombre de dominio gratuito appspot.com, la URL del sitio web empezará con el ID de la aplicación:http://application-id.appspot.com/

Enhorabuena. Has terminado este tutorial. Para obtener más información acerca de los temas que se han tratado aquí, consulta el resto de la documentación de App Engine.

Page 16: Google App Engine

Aspectos generales de App Engine para Java

Te damos la bienvenida a Google App Engine para Java. Con App Engine, puedes crear aplicaciones web a través de tecnologías estándar de Java y ejecutarlas en la infraestructura escalable de Google. El entorno de Java proporciona un JVM Java 6, una interfaz de servlets Java y la compatibilidad de interfaces estándar con los servicios y el almacén de datos escalable de App Engine como, por ejemplo, JDO, JPA, JavaMail y JCache. La compatibilidad con los estándares permite desarrollar tu aplicación de forma fácil y familiar, así como trasladarla fácilmente hacia tu propio entorno de servlet y desde este.

El complemento de Google para Eclipse añade asistentes para proyectos nuevos y configuraciones de depuración a tu entorno integrado de desarrollo (IDE) de Eclipse para proyectos de App Engine. App Engine para Java facilita especialmente el desarrollo y la implementación de aplicaciones web de gran calidad a través de Google Web Toolkit (GWT). El complemento de Eclipse se suministra junto con los SDK de GWT y de App Engine.

Los complementos de terceros también están disponibles para otros IDE Java. Para NetBeans, consulta la compatibilidad de NetBeans con Google App Engine. Para IntelliJ, consulta la integración de Google App Engine para IntelliJ. (Estos enlaces te llevan a los sitios web de terceros).

Si aún no lo has hecho, consulta la Guía de introducción de Java que ofrece una presentación interactiva del desarrollo de aplicaciones web con tecnologías Java y Google App Engine.

El entorno de tiempo de ejecución JavaApp Engine ejecuta aplicaciones Java que utilizan el equipo virtual Java 6 (JVM). El SDK de App Engine es compatible con Java 5 y versiones posteriores, y JVM Java 6 puede utilizar clases compiladas con cualquier versión del compilador de Java hasta Java 6.

App Engine utiliza el estándar Java Servlet para aplicaciones web. Proporcionas clases de servlets de tu aplicación, páginas del servidor Java (JSP), archivos estáticos y archivos de datos, junto con el descriptor de implementación (el archivo web.xml) y otros archivos de configuración en una estructura de directorio WAR estándar. App Engine muestra solicitudes a través de la invocación de servlets según el descriptor de implementación.

El JVM se ejecuta en un entorno seguro de espacio aislado para aislar tu aplicación por servicio y seguridad. El espacio aislado garantiza que la aplicación solo pueda realizar acciones que no interfieran con el rendimiento ni con la escalabilidad de otras aplicaciones. Por ejemplo, una aplicación no puede generar cadenas, escribir datos en el sistema de archivos local ni establecer conexiones de red arbitrarias. Una aplicación tampoco puede utilizar JNI ni ningún otro código nativo. El JVM puede ejecutar cualquier código de bytes de Java que opere dentro de las restricciones del espacio aislado.

Para obtener más información, consulta Entorno de servlet.

El almacén de datos, los servicios y las interfaces estándarApp Engine proporciona servicios escalables que pueden utilizar las aplicaciones para almacenar datos continuamente, acceder a recursos en la red y llevar a cabo otras tareas como, por ejemplo, administrar datos de imágenes. Siempre que sea posible, las interfaces Java para estos servicios se ajustan a las API estándar establecidas para poder trasladar aplicaciones hacia y desde App Engine. Además, cada uno de los servicios proporciona una interfaz completa de nivel inferior para implementar nuevos adaptadores de interfaz o para acceder directamente.

Las aplicaciones pueden utilizar el almacén de datos de App Engine para un almacenamiento de datos continuo, escalable y fiable. El almacén de datos admite dos interfaces Java estándar: los objetos de datos Java (JDO) 2.3 y el API Java de persistencia (JPA) 1.0. Estas interfaces se implementan a través de DataNucleus Access Platform, la implementación de código abierto de estos estándares.

Memcache de App Engine proporciona un almacenamiento en caché distribuido, transitorio y rápido de los resultados de cálculos y consultas al almacén de datos. La interfaz Java implementa JCache (JSR 107).

Las aplicaciones utilizan el servicio de extracción de URL para acceder a recursos en la Web y para comunicarse con otros hosts que utilicen los protocolos HTTP y HTTPS. Las aplicaciones Java pueden utilizar simplemente java.net.URLConnection y clases relacionadas de la biblioteca estándar de Java para acceder a este servicio.

Una aplicación puede utilizar el servicio de correo para enviar mensajes de correo electrónico en nombre de los administradores de la aplicación o del usuario actual. Las aplicaciones Java utilizan la interfaz JavaMail para enviar mensajes de correo electrónico.

Page 17: Google App Engine

El servicio de imágenes permite a las aplicaciones transformar y administrar datos de imágenes en varios formatos, entre los que se incluyen las funciones de recorte, rotación, cambio de tamaño y mejora del color de fotografía. El servicio puede gestionar tareas de procesamiento de imágenes que utilizan muchos recursos de CPU y deja más recursos disponibles para que el servidor de la aplicación administre solicitudes web. (También puedes utilizar cualquier software de procesamiento de imágenes basado en JVM en el servidor de la aplicación, siempre y cuando funcione dentro de las restricciones del espacio aislado).

Una aplicación puede utilizar Google Accounts para la autenticación de usuarios. Google Accounts gestiona la creación y el acceso de cuentas de usuario. Los usuarios que ya dispongan de una cuenta de Google (p. ej., una cuenta de Gmail) podrán utilizarla con tu aplicación. Una aplicación puede detectar cuándo el usuario actual ha iniciado sesión y puede acceder a su dirección de correo electrónico. Las aplicaciones Java pueden utilizar restricciones de seguridad en el descriptor de implementación para controlar el acceso a través de Google Accounts, así como detectar si el usuario ha accedido y obtener la dirección de correo electrónico a través del método getUserPrincipal() del objeto de solicitud del servlet. Una aplicación puede utilizar el API de nivel inferior de Google Accounts para generar URL de acceso y de salida, así como para obtener un objeto de datos de usuario adecuado para que se guarde en el almacén de datos.

Tareas programadasUna aplicación puede configurar tareas programadas que invocan URL de la aplicación según los intervalos especificados. Para obtener más información, consulta tareas cron.

Herramientas Java

El SDK Java de App Engine incluye herramientas para probar tu aplicación, subir archivos de la aplicación y descargar datos de registro. El SDK también incluye un componente para Apache Ant que permite simplificar tareas comunes en proyectos de App Engine. El complemento de Google para Eclipse añade funciones al IDE de Eclipse para desarrollar, probar e implementar App Engine y, además, incluye el SDK completo de App Engine. El complemento de Eclipse también facilita el desarrollo de las aplicaciones de Google Web Toolkit y las ejecuta en App Engine.

El servidor de desarrollo ejecuta la aplicación en tu equipo local para desarrollarla y probarla. El servidor simula el almacén de datos, los servicios y las restricciones del espacio aislado de App Engine. El servidor de desarrollo también puede generar configuraciones para índices de almacenes de datos basados en las consultas que realiza la aplicación durante la prueba.

Una herramienta multiuso llamada AppCfg gestiona toda la interacción del símbolo del sistema mientras tu aplicación se ejecuta en App Engine. AppCfg puede subir tu aplicación a App Engine o simplemente actualizar la configuración de índices del almacén de datos para que puedas compilar nuevos índices antes de actualizar el código. También puede descargar los datos de registro de la aplicación para que puedas analizar el rendimiento de tu aplicación mediante tus propias herramientas.

El entorno Java Servlet

App Engine ejecuta tu aplicación web Java a través del JVM Java 6 en un entorno seguro de espacio aislado. App Engine invoca las clases de servlet de tu aplicación para administrar solicitudes y preparar respuestas en este entorno.

Selección de la versión del API Java Solicitudes y dominios Solicitudes y servlets Respuestas El periodo de tiempo de las solicitudes La zona de pruebas Accesos permitidos a la clase de JRE Acceso El entorno Cuotas y límites

Selección de la versión del API Java

App Engine sabe que debe utilizar el entorno de tiempo de ejecución Java para tu aplicación cuando utilizas la herramienta AppCfg del SDK Java para subir la aplicación.

Page 18: Google App Engine

En el momento de la publicación de este artículo, solo existía una versión del API Java de App Engine. Esta API se representa mediante appengine-api-*.jar incluido en el SDK (donde * representa la versión del API y del SDK). Selecciona la versión del API que utiliza tu aplicación incluyendo este JAR en el directorio WEB-INF/lib/ de la aplicación. Si se publica una nueva versión del entorno de tiempo de ejecución Java que introduzca cambios no compatibles con las aplicaciones existentes, ese entorno dispondrá de un nuevo número de versión. Tu aplicación seguirá utilizando la versión anterior hasta que sustituyas el JAR por la nueva versión (de un SDK más nuevo) y vuelvas a subir la aplicación.

Solicitudes y dominios

App Engine determina que una solicitud entrante está destinada a tu aplicación a partir del nombre de dominio de la solicitud. Una solicitud cuyo nombre de dominio sea application-id.appspot.com se dirige a la aplicación cuyo ID es application-id. Todas las aplicaciones obtienen un nombre de dominio appspot.com de forma gratuita.

Los dominios appspot.com también son compatibles con subdominios que presentan el formato subdomain.application-id.appspot.com, donde subdomain puede ser cualquier cadena permitida en una parte de un nombre de dominio (no .). Las solicitudes enviadas a un subdominio de esta forma se dirigen a tu aplicación.

Puedes configurar un dominio de nivel superior personalizado mediante Google Apps. Google Apps te permite asignar subdominios del dominio de tu empresa a distintas aplicaciones, como Gmail o Sites. Además, puedes asociar una aplicación App Engine a un subdominio. Para mayor comodidad, puedes configurar un dominio de Google Apps cuando registres tu ID de aplicación o más tarde desde la Consola del administrador. Consulta este artículo para obtener más información.

Las solicitudes de estas URL se dirigen todas a la versión de tu aplicación que has seleccionado como versión predeterminada en la Consola del administrador. Cada versión de tu aplicación también incluye su propia URL, de modo que puedes implementar y probar una nueva versión antes de establecerla como versión predeterminada. La URL específica de la versión utiliza el identificador de versiones del archivo de configuración de tu aplicación además del nombre de dominio appspot.com, siguiendo este patrón: version-id.latest.application-id.appspot.com. También puedes utilizar subdominios con la URL específica de la versión: subdomain.version-id.latest.application-id.appspot.com

El nombre de dominio utilizado para la solicitud se incluye en los datos de la solicitud transmitidos a la aplicación. Si quieres que tu aplicación responda de forma diferente en función del nombre de dominio utilizado para acceder a ella (por ejemplo, restringir el acceso a determinados dominios o redireccionar a un dominio oficial), puedes comprobar los datos de la solicitud (como la cabecera de la solicitud Host) para el dominio desde dentro del código de la aplicación y responder adecuadamente.

Solicitudes y servlets

Cuando App Engine recibe una solicitud web para tu aplicación, invoca el servlet correspondiente de la URL, tal y como se describe en el descriptor de implementación (el archivo web.xml del directorio WEB-INF/). Utiliza el API Java Servlet para proporcionar los datos de la solicitud al servlet y acepta los datos de respuesta.

App Engine utiliza varios servidores web para ejecutar tu aplicación y ajusta automáticamente el número de servidores en uso para procesar las solicitudes de una forma segura. Una determinada solicitud se puede enviar a cualquier servidor, que no tiene que ser el mismo servidor que procesó una solicitud anterior procedente del mismo usuario.

En la siguiente clase de servlet de ejemplo aparece un mensaje sencillo en el navegador del usuario.import java.io.IOException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;

public class MyServlet extends HttpServlet {    public void doGet(HttpServletRequest req, HttpServletResponse resp)            throws IOException {        resp.setContentType("text/plain");        resp.getWriter().println("Hello, world");    }}

Page 19: Google App Engine

Respuestas

App Engine invoca el servlet mediante un objeto de solicitud y un objeto de respuesta. A continuación, espera a que el servlet rellene el objeto de respuesta y devuelve los resultados. Cuando el servlet devuelve los resultados, los datos del objeto de respuesta se envían al usuario.

App Engine no permite enviar datos al cliente, realizar cálculos adicionales en la aplicación y, posteriormente, enviar más datos. En otras palabras, App Engine no admite la transmisión de datos como respuesta a una única solicitud.

Si el cliente envía cabeceras HTTP con la solicitud que indican que puede aceptar contenido comprimido (gzipeed), App Engine comprime los datos de respuesta de forma automática y adjunta las cabeceras de respuesta correspondientes. Utiliza las dos cabeceras de solicitud Accept-Encoding y User-Agent para determinar si el cliente puede recibir respuestas comprimidas de forma segura. Los clientes personalizados fuerzan la compresión del contenido mediante la especificación de las cabeceras Accept-Encoding y User-Agent con un valor "gzip".

Si accedes a tu sitio después de haber iniciado sesión con una cuenta de administrador, App Engine incluye estadísticas por solicitud en las cabeceras de respuesta. La cabecera X-AppEngine-Estimated-CPM-US-Dollars representa una estimación del coste de 1.000 solicitudes similares a esta solicitud en dólares estadounidenses. La cabecera X-AppEngine-Resource-Usage representa los recursos utilizados por la solicitud, incluido el tiempo en el servidor, el tiempo de CPU del servidor de aplicaciones y el tiempo de CPU del API (en milisegundos).

El periodo de tiempo de las solicitudes

Un controlador de solicitudes tiene una cantidad limitada de tiempo para generar y devolver una respuesta a una solicitud, proceso que suele requerir 30 segundos aproximadamente. Transcurrido este tiempo, el controlador de solicitudes se interrumpe.

El entorno de tiempo de ejecución Java interrumpe el servlet con la generación de una excepción com.google.apphosting.api.DeadlineExceededException. Si el controlador de solicitudes no detecta esta excepción, al igual que sucede con todas las excepciones no detectadas, el entorno de tiempo de ejecución devuelve al cliente un error de servidor HTTP 500.

El controlador de solicitudes puede detectar este error para personalizar la respuesta. El entorno de tiempo de ejecución proporciona al controlador de solicitudes un poco más de tiempo (menos de un segundo) tras generar la excepción para preparar una respuesta personalizada.

Aunque una solicitud puede tardar hasta 30 segundos en responder, App Engine se ha optimizado para utilizarse con aplicaciones cuyas solicitudes tengan tiempos de respuesta breves, normalmente cientos de milisegundos. Una aplicación eficiente responde rápidamente a la mayoría de las solicitudes. Una aplicación que no lo es no logrará escalarse con la infraestructura de App Engine.

La zona de pruebas

A fin de permitir a App Engine distribuir solicitudes para aplicaciones a través de varios servidores web y evitar que una aplicación interfiera en otra, la aplicación se ejecuta en un entorno de zona de pruebas restringido. En este entorno, la aplicación puede ejecutar datos de consulta, almacenamiento y código en el almacén de datos de App Engine, utilizar los servicios de correo, de extracción de URL y de usuarios de App Engine, así como examinar las solicitudes web del usuario y preparar la respuesta.

Una aplicación App Engine no puede:

escribir en un sistema de archivos. Las aplicaciones deben utilizar el almacén de datos de App Engine para almacenar los datos permanentes. La lectura desde el sistema de archivos está permitida y todos los archivos de aplicación subidos con la aplicación están disponibles.

abrir un socket o acceder a otro host directamente. Una aplicación puede utilizar el servicio de extracción de URL de App Engine para realizar solicitudes HTTP y HTTPS a otros hosts en los puertos 80 y 443, respectivamente.

generar un proceso secundario o subproceso. El procesamiento de una solicitud web realizada a una aplicación debe ser un único proceso que dure unos cuantos segundos. Los procesos que tardan mucho tiempo en responder se finalizan para evitar la sobrecarga del servidor web.

realizar otro tipo de llamadas al sistema.

Page 20: Google App Engine

Cadenas

Una aplicación Java no puede crear un nuevo java.lang.ThreadGroup ni un nuevo java.lang.Thread. Estas restricciones también son aplicables a las clases JRE que utilizan cadenas. Por ejemplo, una aplicación no puede crear un nuevo java.util.concurrent.ThreadPoolExecutor ni un java.util.Timer. Una aplicación puede realizar operaciones relacionadas con la cadena actual como, por ejemplo, Thread.currentThread().dumpStack().

El sistema de archivos

Una aplicación Java no puede utilizar ninguna clase que se utilice para escribir en el sistema de archivos como, por ejemplo, java.io.FileWriter. Una aplicación puede leer sus propios archivos del sistema de archivos a través de clases como, por ejemplo, java.io.FileReader. Una aplicación también puede acceder a sus propios archivos como "recursos", como en el caso de Class.getResource() o de ServletContext.getResource().

La aplicación solo puede acceder a los archivos que se consideran "archivos de recursos" a través del sistema de archivos. De forma predeterminada, todos los archivos del WAR son "archivos de recursos". Puedes excluir archivos de este conjunto a través del archivo appengine-web.xml.

java.lang.System

Se inhabilitan las funciones de la clase java.lang.System que no son aplicables a App Engine.

Los siguientes métodos System no tienen ninguna función en App Engine: exit(), gc(), runFinalization() y runFinalizersOnExit().

Los siguientes métodos System devuelven null: inheritedChannel() y console().

Una aplicación no puede proporcionar ni invocar de forma directa ningún código JNI nativo. Los siguientes métodos System generan una excepción java.lang.SecurityException: load(), loadLibrary() y setSecurityManager().

Reflexión

Una aplicación tiene acceso completo, ilimitado y reflectivo a sus propias clases. Puede realizar la consulta de cualquier miembro privado, utilizar java.lang.reflect.AccessibleObject.setAccessible() y leer/establecer miembros privados.

Una aplicación también puede reflejarse en las clases JRE y API como, por ejemplo, java.lang.String y javax.servlet.http.HttpServletRequest. Sin embargo, solo puede acceder a miembros públicos de estas clases, no a protegidos ni a privados.

Una aplicación no puede reflejarse en ninguna otra clase que no pertenezca a sí misma y no puede utilizar el método setAccessible() para evitar estas restricciones.

Carga de clases personalizadas

App Engine admite completamente la carga de clases personalizadas. Sin embargo, debes tener en cuenta que App Engine anula todos los ClassLoaders para asignar los mismos permisos a todas las clases cargadas por tu aplicación. Si realizas una carga de clases personalizadas, ten cuidado al cargar código de terceros que no sean de confianza.

Accesos permitidos a la clase de JRE

El acceso a las clases de la biblioteca estándar de Java (el entorno de tiempo de ejecución Java o JRE) se limita a las clases de los accesos permitidos a JRE de App Engine.

Acceso

Tu aplicación puede escribir información en los registros de la aplicación a través de java.util.logging.Logger. Puedes ver y analizar los datos de registro de tu aplicación con la Consola del administrador o descargarlos mediante appcfg.sh request_logs. La Consola del administrador puede reconocer los niveles de registro de la clase Logger y mostrar mensajes de forma interactiva a diferentes niveles.

Page 21: Google App Engine

App Engine detecta y registra en los registros de la aplicación todo lo que el servlet escriba en el flujo de salida estándar (System.out) y en el flujo de errores estándar (System.err). Las líneas que se escriben en el flujo de salida estándar se registran en el nivel INFO (Información) y las que se escriben en el flujo de errores estándar se registran en el nivel WARNING (Advertencia). Puede funcionar cualquier estructura de registro (como, por ejemplo, log4j) que se registre en los flujos de errores o de salida. Sin embargo, para un control más preciso de la visualización del nivel de registro de la Consola del administrador, la estructura de registro debe utilizar un adaptador java.util.logging.import java.util.logging.Logger;// ...

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

    public void doGet(HttpServletRequest req, HttpServletResponse resp)            throws IOException {

        log.info("An informational message.");

        log.warning("A warning message.");

        log.severe("An error message.");    }}

El SDK Java de App Engine incluye un archivo de plantilla logging.properties en el directorio appengine-java-sdk/config/user/. Para utilizarlo, copia el archivo en tu directorio WEB-INF/classes (o en cualquier otra ubicación del WAR). A continuación, copia la propiedad del sistema java.util.logging.config.file en "WEB-INF/classes/logging.properties" (o en cualquier otra ruta asociada al directorio raíz de la aplicación). Puedes establecer las propiedades del sistema en el archivo appengine-web.xml tal y como se muestra a continuación:<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">    ...

    <system-properties>        <property name="java.util.logging.config.file" value="WEB-INF/classes/logging.properties" />    </system-properties>

</appengine-web-app>

El asistente para nuevos proyectos del complemento de Google para Eclipse crea estos archivos de configuración de registro y los copia en WEB-INF/classes/ de forma automática. Para java.util.logging, debes establecer la propiedad del sistema que debe utilizar este archivo.

El entornoTodas las propiedades del sistema y variables de entorno son privadas para tu aplicación. La configuración de una propiedad del sistema solo afecta a la vista de esa propiedad de tu aplicación, y no a la vista del JVM.Puedes establecer propiedades del sistema y variables de entorno para tu aplicación en el descriptor de implementación.

App Engine establece dos propiedades del sistema que identifican el entorno de tiempo de ejecución:

com.google.appengine.runtime.environment es "Production" cuando se ejecuta en App Engine, y "Development" cuando se ejecuta en el servidor de desarrollo.

com.google.appengine.runtime.version es el ID de la versión del entorno de tiempo de ejecución como, por ejemplo, "1.3.0".

Además de utilizar System.getProperty(), puedes acceder a las propiedades del sistema mediante el API de seguridad de tipo. Por ejemplo:if (SystemProperty.environment.value() ==    SystemProperty.Environment.Value.Production) {    // The app is running on App Engine...}

Page 22: Google App Engine

App Engine establece las siguientes propiedades del sistema cuando inicializa el JVM en un servidor de aplicaciones:

file.separator path.separator line.separator java.version java.vendor java.vendor.url java.class.version java.specification.version java.specification.vendor java.specification.name java.vm.vendor java.vm.name java.vm.specification.version java.vm.specification.vendor java.vm.specification.name user.dir

Cuotas y límites

Google App Engine asigna recursos a tu aplicación de forma automática a medida que el tráfico aumenta para que pueda admitir distintas solicitudes al mismo tiempo. No obstante, App Engine se reserva la capacidad de escalado automática para las aplicaciones con baja latencia, que responden a las solicitudes en menos de un segundo. Las aplicaciones con latencia muy alta (más de un segundo por solicitud para muchas solicitudes) están limitadas por el sistema, y requieren una exención especial con el fin de tener una mayor cantidad de solicitudes dinámicas simultáneas. Si es muy necesario que tu aplicación tenga una gran capacidad para procesar solicitudes de ejecución con mayor latencia, puedes solicitar una exención del límite de solicitudes dinámicas simultáneas. La gran mayoría de las aplicaciones no requieren ninguna excepción.

Las aplicaciones que están muy limitadas por la CPU también pueden utilizar latencia adicional para compartir de forma eficaz recursos con otras aplicaciones en los mismos servidores. Las solicitudes de archivos estáticos están exentas de estos límites de latencia.

Cada solicitud que recibe la aplicación se contabiliza en la cuota de solicitudes.

Los datos recibidos como parte de una solicitud se contabilizan en la cuota de ancho de banda de entrada (facturable). Los datos enviados en respuesta a una solicitud se contabilizan en la cuota de ancho de banda de salida (facturable).

Las solicitudes HTTP y HTTPS (seguras) se contabilizan en las cuotas de solicitudes, ancho de banda de entrada (facturable) y ancho de banda de salida (facturable). En la página Detalles de la cuota de la Consola del administrador también aparecen las solicitudes seguras, el ancho de banda de entrada seguro y el ancho de banda de salida seguro como valores individuales para fines informativos. Solo las solicitudes HTTPS se contabilizan en estos valores.

El tiempo de procesamiento de CPU destinado a ejecutar un controlador de solicitudes se contabiliza en la cuota de tiempo de CPU (facturable).

Si quieres obtener más información sobre las cuotas, consulta Cuotas y la sección "Detalles de la cuota" de la Consola del administrador.

Además de las cuotas, los controladores de solicitudes presentan estos límites:Límite Valor

tamaño de la solicitud 10 megabytes

tamaño de la respuesta 10 megabytes

duración de la solicitud 30 segundos

número máximo total de archivos (archivos de aplicación y estáticos) 3.000

tamaño máximo de un archivo de aplicación 10 megabytes

tamaño máximo de un archivo estático 10 megabytes

tamaño máximo total de todos los archivos de aplicación y estáticos 150 megabytes

El API Java del almacén de datos

Page 23: Google App Engine

El almacén de datos de App Engine es un almacén de datos de objetos sin esquema, que dispone de un motor de consultas y transacciones atómicas. El SDK de Java incluye implementaciones de las interfaces de objetos de datos Java (JDO, Java Data Objects) y del API de persistencia Java (JPA, Java Persistence API), así como un API del almacén de datos de nivel inferior.

El almacén de datos de duplicación con alta disponibilidad constituye una solución de almacenamiento todavía más fiable sin periodos de inactividad no programados y ofrece mayor fiabilidad de lectura y escritura, consistencia eventual para todas las consultas, excepto las de ancestro, y consistencia fuerte para las lecturas y las consultas de ancestro.

En la referencia se describen las interfaces Java para el almacén de datos de App Engine, especialmente la de JDO. Presenta la siguiente estructura:

Aspectos generales Entidades Consultas Transacciones Elección de un almacén de datos Uso del almacén de datos de duplicación con alta disponibilidad Consultas de metadatos Estadísticas API de servicio asíncrono JDO

o Aspectos generales o Definición de clases de datos con JDO o Creación, obtención y eliminación de datos en JDO o Relaciones de entidad en JDO o Consultas en JDO

JPA o Aspectos generales

Referencia sobre Java

Aspectos generales del almacén de datos

El almacén de datos de App Engine ofrece un almacenamiento sólido y escalable para las aplicaciones web, con especial atención en el rendimiento de las consultas y de las operaciones de lectura. Una aplicación crea entidades donde los valores de los datos se almacenan como propiedades de una determinada entidad. La aplicación puede realizar consultas de las entidades. Todas las consultas se indexan previamente para así poder proporcionar resultados rápidos a partir de conjuntos de datos de gran volumen.

Introducción al almacén de datos Introducción al API Java del almacén de datos Entidades y propiedades Consultas e índices Transacciones y grupos de entidades Diferencias con SQL Estadísticas del almacén de datos Cuotas y límites

Introducción al almacén de datos

App Engine ofrece dos opciones para almacenar datos que se diferencian por su disponibilidad y por su coherencia:

El almacén de datos principal/secundario se basa en un sistema de replicación principal-secundario que replica datos de forma asíncrona al tiempo que escribes los datos en un centro de datos físico. Dado que solo puede haber un centro de datos principal a la vez para las operaciones de escritura, esta opción ofrece consistencia fuerte para todas las consultas y operaciones de lectura. Como contrapartida, se producen periodos de inactividad temporales por incidencias en el centro de datos o por labores de mantenimiento planificadas. No obstante, la opción ofrece el coste más bajo por almacenar datos y por utilizar la CPU para tal fin.

En el almacén de datos de replicación con alta disponibilidad, los datos se replican en los centros de datos mediante un sistema basado en el algoritmo Paxos. Este tipo de almacén ofrece una gran disponibilidad para las operaciones de lectura y de escritura (como contrapartida, existe una mayor latencia de las operaciones de escritura). La mayoría de las consultas son de

Page 24: Google App Engine

consistencia eventual. El coste de la cuota de almacenamiento y del uso de la CPU es aproximadamente tres veces superior al que supone el almacén de datos principal/secundario.

Si quieres obtener más información al respecto, consulta Elección de un almacén de datos.

El almacén de datos de App Engine guarda objetos de datos, conocidos con el nombre de entidades. Cada entidad incluye una o varias propiedades, es decir, valores específicos de uno de los distintos tipos de datos admitidos. Por ejemplo, una propiedad puede ser una cadena, un número entero o incluso una referencia a otra entidad.

El almacén de datos puede ejecutar varias operaciones en una misma transacción. Por definición, una transacción no se lleva a cabo correctamente si alguna de las operaciones que la componen no se ejecuta. En caso de error en alguna de las operaciones, la transacción se deshace automáticamente. Esto resulta especialmente útil para aplicaciones web distribuidas, donde varios usuarios acceden a los mismos datos o los utilizan simultáneamente.

A diferencia de las bases de datos tradicionales, el almacén de datos emplea una arquitectura distribuida con el fin de administrar automáticamente el escalado para conjuntos de datos de gran volumen. La relación entre los objetos de datos propia de los almacenes de datos es muy distinta a la relación que se da en una base de datos relacional típica. Dos entidades del mismo tipo pueden tener propiedades diferentes. Se admite que varias entidades tengan propiedades con el mismo nombre pero presenten tipos de valor distintos. Si bien la interfaz del almacén de datos incorpora muchas de las funciones que integran las bases de datos tradicionales, el almacén de datos cuenta con características únicas como, por ejemplo, el diseño y la administración de los datos de forma que se pueda aprovechar la capacidad de escalado automático. En esta documentación se explica cómo diseñar la aplicación para sacar el máximo partido a la arquitectura distribuida del almacén de datos.

Introducción al almacén de datos Java

Las entidades de almacén de datos no tienen un esquema: dos entidades del mismo tipo no tienen la obligación de tener las mismas propiedades, ni de utilizar los mismos tipos de valores para las mismas propiedades. La aplicación es responsable de garantizar que las entidades cumplan con un esquema cuando sea necesario.

El almacén de datos proporciona un API de nivel inferior que realiza operaciones simples en entidades, incluidas get, put, delete y query. Puedes usar esta API para implementar otros adaptadores de interfaz, o solo usarla de forma directa en tus aplicaciones.

Marcos para modelo y persistencia de datos

El SDK de Java incluye implementaciones de las interfaces de objetos de datos Java (JDO, Java Data Objects) y del API de persistencia Java (JPA, Java Persistence API) para el modelo y la persistencia de datos. Estas interfaces basadas en estándares incluyen mecanismos para la definición de clases en los objetos de datos y para realizar consultas. Además de los marcos estándar y del API del almacén de datos de nivel inferior, el SDK de Java admite otros marcos diseñados para facilitar el uso del almacén de datos a los desarrolladores de Java. De hecho, son muchos los desarrolladores de Java que usan estos marcos. El equipo de Google App Engine los recomienda encarecidamente y te invita a echarles un vistazo.

Objectify: Objectify es una interfaz muy útil y simple para el almacén de datos de App Engine que elimina algunas de las complejidades de JDO/JPA y del almacén de datos de nivel inferior.

TWiG: TWiG es una interfaz de persistencia de objetos configurable que mejora la integración con las funciones de herencia, de polimorfismo y de tipos genéricos. Al igual que Objectify, TWiG también elimina algunas de las complejidades de JDO y del almacén de datos de nivel inferior.

Slim3: Slim3 es un marco Modelo Vista Controlador completo para usar una amplia gama de funciones de App Engine que no se limitan al almacén de datos (aunque puedes usarlo para tal fin).

Entidades y propiedades

Los objetos de datos del almacén de datos de App Engine se denominan entidades. Las entidades contienen una o varias propiedades, es decir, valores de uno de los distintos tipos de datos, por ejemplo, números enteros, valores de punto flotante, cadenas, fechas, datos binarios, etc.

Por su parte, cada entidad cuenta con una clave que sirve de identificador único. Las claves más simples están compuestas por un tipo y un ID de entidad que proporciona el almacén de datos. El tipo se encarga de clasificar la entidad para poder realizar consultas de esta más fácilmente. El ID de entidad también puede ser una cadena que proporcione la aplicación.

La aplicación puede extraer una entidad del almacén de datos utilizando su clave o bien realizando una consulta que coincida con las propiedades de la entidad. La consulta puede devolver entidades o no devolver ninguna. Además, los resultados pueden aparecer

Page 25: Google App Engine

ordenados por los valores de propiedad. También es posible limitar el número de resultados de una consulta con el fin de reducir el uso de memoria o agilizar el tiempo de ejecución y el uso de la CPU.

A diferencia de las bases de datos relacionales, en el almacén de datos de App Engine no es necesario que todas las entidades de un tipo determinado tengan las mismas propiedades. La aplicación puede especificar y aplicar su modelo de datos mediante las bibliotecas que se incluyen en el SDK o mediante su propio código.

Una propiedad puede incluir uno o varios valores. Si incluye varios de ellos, estos pueden ser de distintos tipos. Las consultas que se realizan a una propiedad que dispone de varios valores comprueban si alguno de estos coincide con los criterios de la consulta. Por consiguiente, estas propiedades resultan útiles para realizar comprobaciones en el caso de miembros de grupos.

Consultas e índices

Las consultas del almacén de datos de App Engine operan en cada una de las entidades de un determinado tipo (una clase de datos). Establecen varios filtros o ninguno para las claves y los valores de propiedad de la entidad y varios criterios de ordenación o ninguno. Si una determinada entidad incluye por lo menos un valor (posiblemente nulo) para cada una de las propiedades de los filtros y de los criterios de ordenación, y todos los valores de propiedad coinciden con los criterios de filtrado, la entidad se devuelve como resultado.

Todas las consultas del almacén de datos se basan en un índice, es decir, una tabla con los resultados de la consulta en el orden deseado. Las aplicaciones App Engine definen los índices en un archivo de configuración (si bien para algunos tipos de consulta, los índices se proporcionan de forma automática). El servidor de desarrollo web añade automáticamente sugerencias a este archivo cuando detecta consultas que todavía no tienen configurado el índice. Puedes ajustar estos índices manualmente modificando el archivo antes de subir la aplicación. A medida que la aplicación modifica las entidades del almacén de datos, este último actualiza los índices con los resultados correctos. Cuando la aplicación ejecuta una consulta, el almacén de datos extrae los resultados directamente del índice en cuestión.

Este mecanismo admite varios tipos de consulta y sirve para la mayoría de las aplicaciones. Sin embargo, es incompatible con algunos tipos de consulta habituales en otras tecnologías de base de datos; concretamente, no se admiten consultas de unión ni de agrupación conjunta.

Transacciones y grupos de entidades

Con el almacén de datos de App Engine, cada uno de los intentos para crear, modificar o eliminar una entidad ocurre en una transacción. La transacción garantiza que cada cambio que se realice en la entidad se guarde en el almacén de datos. En caso de que se produzca un error, la transacción evita que se apliquen los cambios. De esta forma, se garantiza la coherencia de los datos dentro de la entidad.

Dentro de una misma transacción, se pueden realizar varias acciones en una entidad. Para ello, deberás utilizar el API de transacciones. Por ejemplo, supongamos que quieres aumentar el campo de contador en un objeto. Para ello, deberás leer el valor del contador, calcular el valor nuevo y, a continuación, almacenarlo. Si no utilizas una transacción, otro proceso podría aumentar el contador entre el momento de leer el valor y el momento de modificarlo, lo que provocaría que la aplicación sobrescribiera el valor modificado. Reunir las operaciones de lectura, cálculo y escritura en una sola transacción garantiza que ningún otro proceso interfiera en el incremento de valor.

Dentro de la misma transacción, puedes realizar cambios en distintas entidades mediante los grupos de entidades. Al crear una entidad, se especifica el grupo al que esta pertenece. Todas las entidades que se extraigan, creen, modifiquen o eliminen con una misma transacción, deben pertenecer al mismo grupo de entidades.

Los grupos de entidades se configuran a partir de la jerarquía de relaciones que se da entre las entidades. Para crear una entidad dentro de un grupo, debes especificar que esta es una entidad secundaria de otra entidad ya incluida en el grupo. La otra entidad será la entidad principal. Una entidad sin una entidad principal es una entidad raíz. Una entidad raíz sin entidades secundarias se incluye en el grupo de entidades como entidad independiente. Cada entidad tiene asociada una ruta de relaciones entre la entidad principal y la entidad secundaria, desde la entidad raíz hasta la propia entidad. La ruta más corta es cuando no existe entidad principal. Esta ruta es una parte fundamental de la clave completa de la entidad. Una clave completa se puede representar mediante el tipo y el ID (o nombre de clave) de cada una de las entidades de la ruta.

El almacén de datos utiliza el control de concurrencia optimista para administrar las transacciones. Mientras que una instancia de la aplicación introduce los cambios en las entidades en un determinado grupo de entidades, el resto de los intentos por actualizar el grupo (ya sea modificando entidades o creando otras nuevas) resultan fallidos. La aplicación puede intentar ejecutar la transacción de nuevo para los datos actualizados. Ten en cuenta que, dado el funcionamiento del almacén de datos, si se utilizan grupos de entidades, se verá limitado el número de operaciones de escritura concurrentes en cualquiera de las entidades de dicho grupo.

Page 26: Google App Engine

Diferencias con SQL

Existen varias diferencias importantes entre el almacén de datos de App Engine y una base de datos relacional típica.

El almacén de datos de App Engine se puede escalar, con lo que las aplicaciones pueden seguir con un nivel de rendimiento elevado cuando reciben más tráfico. Las operaciones de escritura del almacén de datos se pueden escalar mediante la distribución automática de los datos, según sea necesario. Las operaciones de lectura del almacén de datos se escalan porque las únicas consultas que se admiten son aquellas cuyo rendimiento se escala con el tamaño del conjunto de resultados (a diferencia del conjunto de datos). Esto significa que el rendimiento de una consulta cuyo conjunto de resultados contiene 100 entidades es el mismo tanto si la búsqueda es en 100 entidades como en un millón. Esta propiedad es el principal motivo por el que no se admiten algunos tipos de consulta.

Puesto que todas las consultas que se realizan en App Engine las proporcionan índices creados previamente, los tipos de consulta que se pueden ejecutar son más limitados que los admitidos en una base de datos relacional con SQL. En el almacén de datos no se pueden realizar consultas de unión, como tampoco se admite el filtrado de desigualdad en varias propiedades ni el filtrado de datos basado en los resultados de una subconsulta.

A diferencia de las bases de datos relacionales típicas, el almacén de datos de App Engine no exige que los tipos de datos tengan un conjunto de propiedades coherente (aunque, si quieres, puedes incluir este requisito en el código de la aplicación). Al realizar consultas en la base de datos, en estos momentos no es posible devolver únicamente un subconjunto de propiedades de tipo. Como respuesta a una consulta, el almacén de datos de App Engine puede devolver entidades completas o solo claves de entidad.

Para obtener información más detallada sobre el diseño del almacén de datos, consulta la serie de artículos Mastering the datastore (Información detallada sobre el almacén de datos).

Estadísticas del almacén de datos

El almacén de datos contiene estadísticas sobre los datos almacenados de una aplicación como, por ejemplo, la cantidad de entidades de un determinado tipo o el espacio que utilizan los valores de propiedad de un tipo en concreto. Encontrarás estas estadísticas en "Almacén de datos" > "Estadísticas" de la Consola del administrador.

También puedes acceder a estos valores mediante un programa desde la aplicación. Para ello, basta con realizar una consulta de entidades específicas mediante el API del almacén de datos. Para obtener más información al respecto, consulta Estadísticas del almacén de datos.

Cuotas y límites

Cada una de las invocaciones del API del almacén de datos consume parte de la cuota destinada a las invocaciones del API del almacén de datos. Ten en cuenta que algunas invocaciones de la biblioteca se convierten en varias invocaciones del API, por lo que el consumo de cuota es mayor.

Los datos que la aplicación envía al almacén de datos consumen cuota destinada a los datos enviados al API (Almacén de datos). Los datos que recibe la aplicación del almacén de datos contabilizan en el consumo de cuota de los datos recibidos del API (almacén de datos).

El total de datos de la aplicación que se guarde en el almacén de datos no puede superar la cuota destinada a datos almacenados (facturable). Esto incluye todas las propiedades y claves de entidad, así como los índices necesarios para poder realizar consultas de estas entidades. Consulta How Entities and Indexes are Stored (Cómo se almacenan las entidades y los índices) para obtener una descripción detallada de los metadatos necesarios para almacenar entidades e índices en BigTable.

La cantidad de tiempo de CPU que consumen las operaciones del almacén de datos se distribuye en las cuotas siguientes:

Tiempo de CPU (facturable) Tiempo de CPU del almacén de datos

Si quieres obtener más información sobre cuotas, consulta Cuotas y la sección de información detallada de cuotas de la Consola del administrador.

Además de las cuotas, estos son los límites que atañen al uso del almacén de datos:

Page 27: Google App Engine

Límite Valor

Tamaño máximo de entidad 1 megabyte

Número máximo de valores en todos los índices de una entidad (1) 5.000 valores

1. En todos los índices, cada entidad utiliza un valor de un índice para cada columna y por cada fila que haga referencia a la entidad. El número de valores de índice de una entidad puede aumentar si una propiedad indexada incluye más de un valor, lo que exigirá varias filas con valores repetidos en la tabla.

Entidades, propiedades y claves

Hay que entender el almacén de datos de App Engine como una base de datos de objetos. Cada registro de datos es una entidad, la cual se representa mediante código en forma de objeto. Cada entidad contiene una clave que sirve de identificador para diferenciarla de las demás entidades del almacén de datos. A su vez, cada entidad contiene una o varias propiedades, que se representan como atributos del objeto. Las claves y las propiedades son la base de muchas funciones útiles que ofrece el almacén de datos.

Aspectos generales Tipos, ID y nombres Grupos de entidades y rutas de ancestros Propiedades y tipos de valor Cómo guardar, obtener y eliminar entidades

Aspectos generales

Los objetos que se incluyen en el almacén de datos de App Engine se denominan entidades. Cada entidad contiene una clave que sirve de identificador para diferenciarla de las demás entidades. Las claves están compuestas por varios elementos: una ruta, un elemento opcional que determina la entidad principal de la entidad en cuestión, el tipo de entidad, así como el nombre asignado a la entidad por la aplicación o el ID numérico asignado por el almacén de datos.

Cada entidad incluye una o varias propiedades, es decir, valores de uno de los distintos tipos de datos. Estos son algunos de los tipos de datos admitidos: números enteros, valores de punto flotante, cadenas, fechas, datos binarios, etc. Una propiedad puede incluir uno o varios valores. Si la propiedad contiene más de un valor, estos pueden ser de distintos tipos.

Una aplicación puede extraer una entidad del almacén de datos mediante la clave correspondiente o bien puede realizar una consulta basada en la clave o en los valores de propiedad de la entidad. Una vez creada la entidad, no es posible cambiar la clave asociada. Si quieres obtener más información sobre las consultas, dirígete a la sección Consultas e índices.

Una aplicación solamente puede acceder a las entidades que esta haya creado; no puede acceder a datos que pertenezcan a otras aplicaciones.

El SDK de Java incluye un API Java que admite directamente las funciones del almacén de datos. Esta API se incluye en el paquete com.google.appengine.api.datastore. Puedes usarla directamente en tus aplicaciones y para crear tu propia capa de administración de datos. Utilizaremos esta API para presentar las funciones del almacén de datos.

El SDK de Java también admite dos interfaces estándar para el almacenamiento de datos: la interfaz de objetos de datos Java (JDO, Java Data Objects) y el API de persistencia Java (JPA, Java Persistence API). Estas interfaces te permiten diseñar tus objetos de datos como clases Java y facilitan la transferencia de tu aplicación desde el almacén de datos de App Engine a otras soluciones de almacenamiento de datos. Consulta las secciones Uso de JDO y Uso de JPA para obtener más información sobre estas interfaces.

A continuación, incluimos un breve ejemplo de cómo utilizar el API del almacén de datos para crear una entidad del almacén de datos:

import java.util.Date;import com.google.appengine.api.datastore.DatastoreService;import com.google.appengine.api.datastore.DatastoreServiceFactory;import com.google.appengine.api.datastore.Entity;

// ...DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Page 28: Google App Engine

Entity employee = new Entity("Employee");employee.setProperty("firstName", "Antonio");employee.setProperty("lastName", "Salieri");Date hireDate = new Date();employee.setProperty("hireDate", hireDate);employee.setProperty("attendedHrTraining", true);

datastore.put(employee);

En este ejemplo se prepara una nueva entidad del almacén de datos del tipo "Employee" y se asignan distintos tipos de valor a varias propiedades. A continuación, se solicita al almacén de datos que guarde la nueva entidad.

Nota: el almacén de datos en sí no aplica ninguna restricción a la estructura de las entidades; por ejemplo, que una determinada propiedad tenga un tipo de valor en concreto. La aplicación se encarga de ello. Puedes administrar y aplicar una estructura de entidades mediante las interfaces de JDO y de JPA.

Tipos, ID y nombres

Cada entidad del almacén de datos es de un determinado tipo, un nombre que asigna la aplicación. El tipo de entidad se encarga de clasificar la entidad para las consultas. Por ejemplo, en una aplicación de RR.HH. se podría representar a cada empleado de la organización por medio de una entidad del tipo "Empleado". A diferencia de las filas de una tabla, no es necesario que dos entidades del mismo tipo contengan las mismas propiedades. En caso necesario, se puede configurar esta restricción en el modelo de datos de la aplicación.

Una parte de la clave exclusiva de cada entidad sirve de identificador para la entidad. Una aplicación puede asignar su propio identificador (denominado nombre de clave) para utilizarlo en la clave. Otra posibilidad es que, al almacenar la entidad por primera vez, el almacén de datos le asigne un ID numérico. Puesto que el identificador forma parte de la clave, una vez creada la entidad, ya no se podrá modificar el ID o el nombre asignado.

En el API del almacén de datos, debes especificar el tipo de entidad al crear el objeto Entity, como un argumento del constructor Entity. En el ejemplo anterior, Entity se creó como entidad del tipo "Employee".

Debes especificar si una entidad ha de usar una cadena de nombre de clave asignada por la aplicación o un ID numérico asignado por el sistema como identificador cuando crees el objeto. Para establecer un nombre de clave, inclúyelo como segundo argumento del constructor Entity:Entity employee = new Entity("Employee", "asalieri");

Para especificar que el almacén de datos debe asignar un ID numérico de forma automática, solo tienes que omitir el argumento:Entity employee = new Entity("Employee");

Grupos de entidades y rutas de ancestros

Cuando utilizas el almacén de datos de App Engine, cada uno de los intentos para crear, modificar o eliminar una entidad ocurre en una transacción. La transacción garantiza que cada cambio que se realice en la entidad se guarde en el almacén de datos. En caso de que se produzca un error, la transacción evita que se apliquen los cambios. De esta forma, se garantiza la coherencia de los datos dentro de la entidad. En esta sección se describen las transacciones muy brevemente. Para obtener información más detallada, consulta la sección específica Transacciones.

Puedes realizar una misma transacción con varias entidades siempre y cuando estas pertenezcan al mismo grupo de entidades. Cuando diseñes el modelo de datos, deberías especificar las entidades que quieres procesar en la misma transacción. Luego, al crear las entidades, bastará con indicar que pertenecen al mismo grupo que otra entidad. Con esto, se indica a App Engine que las entidades se modificarán de forma conjunta, por lo que se pueden almacenar de manera que admitan transacciones. Para definir un grupo de entidades, especifica una jerarquía entre las mismas. Todas las entidades que se extraigan, creen, modifiquen o eliminen con una misma transacción, deben pertenecer al mismo grupo de entidades.

En el almacén de datos de replicación con alta disponibilidad, los grupos de entidades también son una unidad coherente. La única forma de garantizar que los resultados de una consulta sean de consistencia fuerte es mediante una consulta de ancestro.

A la hora de crear una entidad nueva en un grupo, debes especificar la entidad principal. Una entidad sin una entidad principal es una entidad raíz. Una entidad raíz sin entidades secundarias es un grupo de entidades por sí misma. Las claves de cada entidad contienen una ruta de entidades empezando por la raíz del grupo de entidades, es decir, la propia entidad, cuando esta no tiene ninguna entidad

Page 29: Google App Engine

principal. Esta ruta es una parte fundamental de la clave completa de la entidad. Una clave completa se puede representar mediante el tipo y el ID (o nombre de la clave) de cada una de las entidades de la ruta.

Una entidad que sea la entidad principal de otra puede tener a su vez otra entidad principal. Una cadena de entidades principales de una entidad hasta la raíz es la ruta de la entidad; los miembros de la ruta son los ancestros de la entidad. La entidad principal de una entidad se define cuando se crea la entidad y no se puede modificar posteriormente.

Para especificar que una entidad se cree en un grupo de entidades existente, asigna el objeto Key de la entidad principal como argumento del constructor Entity de la nueva entidad. Puedes obtener un objeto Key invocando el método getKey() en la entidad (Entity) principal.Entity employee = new Entity("Employee");datastore.put(employee);

Entity address = new Entity("Address", employee.getKey());

Si la entidad con el elemento principal también tiene un nombre de clave, asigna el nombre de clave (String) como segundo argumento, y la clave (Key) de la entidad principal como tercero:Entity address = new Entity("Address", "addr1", employee.getKey());

La clave completa de una entidad es la clave de la entidad principal (si existe) seguida del tipo de entidad y del nombre de la clave o del ID del sistema. La clave de la entidad principal también puede incluir una entidad principal. Por lo tanto, la clave completa representa la ruta completa de los ancestros desde la entidad raíz del grupo hasta la propia entidad. En este ejemplo, si la clave de la entidad Employee es Employee:8261, la clave de la dirección podría ser la siguiente: Employee:8261 / Address:1

Propiedades y tipos de valor

Los datos de cada entidad se almacenan en una o en varias propiedades. Cada propiedad tiene un nombre asignado y al menos un valor. Cada valor es de uno de los distintos tipos de datos admitidos como, por ejemplo, cadena Unicode, número entero, fecha-hora y cadena de bytes.

Una propiedad puede incluir diversos valores, y cada uno de estos puede ser de un tipo distinto. Las propiedades con más de un valor resultan útiles, por ejemplo, para realizar consultas con filtros de igualdad. Cuando se utilizan filtros de igualdad, si alguno de los valores de propiedad coincide con los criterios del filtro, la consulta devuelve la entidad. Para obtener más información sobre las propiedades con más de un valor, así como cuestiones que deberías tener en cuenta, dirígete a la sección Consultas e índices.

En la tabla siguiente se describen los distintos tipos de valor de propiedades que admite el almacén de datos:

Tipo de valor Tipo JavaCriterio de ordenación

Notas

booleano boolean o java.lang.Boolean false < true

cadena de bytes (corta)

com.google.appengine.api.datastore.ShortBlob orden de byteshasta 500 bytes un valor de más de 500 bytes genera una excepción JDOFatalUserException

cadena de bytes (larga)

com.google.appengine.api.datastore.Blob no disponible hasta 1 MB (sin indexar)

categoría com.google.appengine.api.datastore.Category Unicode

fecha y hora java.util.Date cronológico

dirección de correo electrónico

com.google.appengine.api.datastore.Email Unicode

número de punto flotante

float, java.lang.Float, double, java.lang.Double

numérico precisión doble de 64 bits, IEEE 754

punto geográfico

com.google.appengine.api.datastore.GeoPt por latitud y, a continuación, por longitud

Page 30: Google App Engine

usuarios de Google Accounts

com.google.appengine.api.users.User dirección de correo electrónico en orden Unicode

número enteroshort, java.lang.Short, int, java.lang.Integer, long, java.lang.Long

numérico

(almacenado como número entero largo y, a continuación, convertido al tipo de campo); superación del intervalo de valores

clave, almacén de blob

com.google.appengine.api.blobstore.BlobKey orden de bytes

clave, almacén de datos

com.google.appengine.api.datastore.Key o el objeto referenciado (como objeto secundario)

por elementos de ruta (tipo, ID o nombre, tipo, ID o nombre...)

enlace com.google.appengine.api.datastore.Link Unicode

controlador de mensajes

com.google.appengine.api.datastore.IMHandle Unicode

nulo null no disponible

dirección postal

com.google.appengine.api.datastore.PostalAddress Unicode

puntuación com.google.appengine.api.datastore.Rating numérico

número de teléfono

com.google.appengine.api.datastore.PhoneNumber Unicode

cadena de texto (corta)

java.lang.String Unicode hasta 500 caracteres Unicode un valor de más de 500 caracteres genera una excepción JDOFatalUserException

cadena de texto (larga)

com.google.appengine.api.datastore.Text no disponible hasta 1 MB (sin indexar)

Es posible que, para una misma propiedad, dos entidades contengan valores de distinto tipo. El almacén de datos utiliza un sistema determinista para ordenar los valores que son de distinto tipo en función de las representaciones internas:

valores nulos número entero, fecha-hora y puntuación valores booleanos cadena de bytes (corta) cadenas Unicode: cadenas de texto (cortas), categoría, dirección de correo electrónico, controlador de MI, enlace, número de

teléfono, dirección postal números de punto flotante puntos geográficos usuarios de Google Accounts claves del almacén de datos claves del almacén de blob

El almacén de datos no indexa las cadenas de texto largas ni las cadenas de bytes largas y, por lo tanto, no tienen definido ningún orden.

Nota: los valores enteros y los números de punto flotante se consideran tipos distintos en el almacén de datos. Si las entidades utilizan una mezcla de números enteros y de números de punto flotante para la misma propiedad, todos los números enteros se ordenarán antes de los números de punto flotante: 7 < 3.2

Cadenas de texto y cadenas de bytes

El almacén de datos admite dos tipos de valores para almacenar texto Unicode: cadenas de texto cortas de hasta 500 caracteres Unicode y cadenas de texto largas de hasta 1 megabyte. Las cadenas cortas se indexan y se pueden utilizar en filtros de consultas y en criterios de ordenación. Las cadenas largas no se indexan y no se pueden utilizar en filtros de consultas ni en criterios de ordenación.

Page 31: Google App Engine

El almacén de datos admite dos tipos de valores similares para datos binarios sin codificar: cadenas de bytes cortas de hasta 500 bytes y cadenas de bytes largas de hasta 1 megabyte. Al igual que sucede con las cadenas de texto, las cadenas de bytes cortas se indexan y se pueden utilizar en filtros de consultas o en criterios de ordenación; las cadenas de bytes largas no se indexan y no se pueden usar en filtros de consultas ni en criterios de ordenación.

Nota: en el API del almacén de datos, el tipo de cadena de bytes larga se denomina "blob". Este tipo no está relacionado con los blobs según se utilizan en el API del almacén de blob.

Cómo guardar, obtener y eliminar entidades

Las aplicaciones utilizan el API del almacén de datos para crear entidades nuevas, modificar las existentes, así como obtener y eliminar entidades. Si la aplicación conoce la clave completa de una entidad (o la puede deducir de la clave, tipo e ID de la entidad principal), dicha aplicación puede actuar directamente sobre la entidad por medio de la clave. La aplicación también puede realizar una consulta para determinar las claves de aquellas entidades cuyas propiedades cumplan con determinados criterios. Dirígete a la sección Consultas e índices para obtener más información sobre las consultas al almacén de datos.

Con el API Java del almacén de datos, puedes guardar, obtener y eliminar entidades usando métodos de DatastoreService. Este objeto se obtiene invocando el método getDatastoreService() de la clase DatastoreServiceFactory.import com.google.appengine.api.datastore.DatastoreService;import com.google.appengine.api.datastore.DatastoreServiceFactory;

// ...DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

Para crear o actualizar una entidad en el almacén de datos, invoca el método put() con el objeto Entity que desees guardar.Entity employee = new Entity("Employee");// ... set properties ...

datastore.put(employee);

Para obtener una entidad identificada por una clave determinada, invoca el métodoget() con un objeto Key.// Key employeeKey = ...;Entity employee = datastore.get(employeeKey);

Para actualizar una entidad existente, modifica los atributos del objeto Entity y, a continuación, invoca el método put() con el objeto. Los datos del objeto sobrescriben la entidad existente. Todo el objeto se envía al almacén de datos con cada invocación de put().

Nota: el API del almacén de datos no distingue entre la creación de una nueva entidad y la actualización de una entidad existente. Si la clave del objeto representa una entidad existente, al invocar el método put() con el objeto, se sobrescribe la entidad. Puedes usar una transacción para comprobar si una entidad con una clave determinada ya existe antes de crearla.

Para eliminar una entidad identificada por una clave determinada, invoca el método delete() con un objeto Key.// Key employeeKey = ...;datastore.delete(employeeKey);

Creación de claves

La clase KeyFactory puede crear objetos Key a partir de componentes conocidos, como el tipo y el nombre de la clave.

Si la aplicación conoce cada elemento de la clave completa de una entidad, puede crear el objeto Key correspondiente sin el objeto.

Si la entidad asociada a esta clave no tiene un grupo de entidades principal, crea la clave usando el método estático createKey() de la clase KeyFactory. Este método utiliza un tipo (el nombre simple de la clase) junto con un ID de cadena asignado por la aplicación o un ID numérico asignado por el sistema para devolver un objeto Key. En el siguiente código se muestra cómo debe recrearse la clave de una entidad del tipo "Employee" con el nombre de clave "[email protected]" (y sin un grupo de entidades principal):Key k = KeyFactory.createKey(Employee.class.getSimpleName(), "[email protected]");

Para recrear la clave de una entidad del tipo "Employee" con el ID numérico asignado por el sistema 52234 (y sin un grupo de entidades principal):Key k = KeyFactory.createKey(Employee.class.getSimpleName(), 52234);

Page 32: Google App Engine

Si la entidad asociada a una clave tiene un grupo de entidades principal, puedes crear la clave con la clase KeyFactory.Builder:Key k = new KeyFactory.Builder(Employee.class.getSimpleName(), 52234)    .addChild(ExpenseReport.class.getSimpleName(), "A23Z79").getKey();

El método addChild() de la instancia de Builder devuelve el objeto Builder, de modo que puedes encadenar invocaciones para añadir cada elemento de la ruta de la clave. Para obtener el valor de clave completo de un objeto Builder determinado, invoca el método getKey() de Builder.

La clase KeyFactory también incluye los métodos de clase keyToString() y stringToKey() para convertir claves en representaciones de cadena y a partir de estas representaciones. Ten en cuenta que este procedimiento es distinto del método toString() de la clase Key, que devuelve un valor legible por el usuario y apto para la depuración. La cadena de un valor de clave es apta para la Web, es decir, no contiene caracteres considerados especiales ni en el código HTML ni en las URL.String employeeKeyStr = KeyFactory.keyToString(employeeKey);// ...

Key employeeKey = KeyFactory.stringToKey(employeeKeyStr);Entity employee = datastore.get(employeeKey);

Ten en cuenta que Key.toString() no devuelve una representación de cadena (String) de la clave que pueda interpretar una máquina, sino una cadena que cualquier usuario puede interpretar y que resulta útil para fines de depuración y registro. Si necesitas una cadena (String) que pueda convertirse en clave (Key), utiliza KeyFactory.keyToString(key).

Nota: la versión de cadena de una clave no está encriptada. El usuario puede descodificar la cadena de una clave para determinar sus componentes, incluidos los tipos, y los ID de la entidad y sus ancestros. Si esta información debe ocultarse al usuario, pero la aplicación debe aceptar los valores de clave de las solicitudes de los usuarios, debes encriptar la cadena de la clave antes de enviarla al usuario.

Operaciones en lote

Los métodos put(), get() y delete() aceptan objetos java.lang.Iterable Entity (para objetos put()) y objetos Key (para get() y delete()). De este modo, se realiza la acción en varias entidades mediante una sola invocación del almacén de datos. La operación en lote agrupa todas las entidades/claves por grupo de entidades y, a continuación, realiza operaciones en cada grupo de entidades en paralelo.

Una invocación en lote al almacén de datos es más rápida que realizar una invocación para cada entidad porque solo produce la sobrecarga de una invocación del servicio. Además, si hay varios grupos de entidades, la tarea se realiza en el servidor, de forma paralela, para cada grupo de entidades.import java.util.Arrays;import java.util.List;

// ...Entity employee1 = new Entity("Employee");Entity employee2 = new Entity("Employee");Entity employee3 = new Entity("Employee");// ...

List<Entity> employees = Arrays.asList(employee1, employee2, employee3);datastore.put(employees);

Las invocaciones en lote al almacén de datos están sujetas a los mismos límites que las demás invocaciones. Por ejemplo, en una invocación en lote del método put(), el tamaño total de todas las entidades que se guardan no debe superar un megabyte. Del mismo modo, una invocación en lote del método get() no puede devolver más de un megabyte de datos. Una invocación en lote del método delete() solo envía las claves de las entidades que deben eliminarse y no devuelve ningún dato. Por lo tanto, el límite solo afecta al tamaño total de las claves.

Las invocaciones en lote de put() o delete() solo pueden realizarse con determinadas entidades. Si es importante que la invocación consiga resultados para todas las entidades o no consiga para ninguna, debes usar una transacción para que todas las entidades en cuestión se incluyan en el mismo grupo. Una operación en lote dentro de una transacción con entidades o claves que pertenezcan a varios grupos de entidades genera una excepción IllegalArgumentException.

Consultas e índices

Aspectos generales

Page 33: Google App Engine

Restricciones aplicadas a las consultas Extracción de resultados Introducción a los índices Entidades de gran tamaño e índices de ampliación Cursores de consultas Configuración de la política de lectura y el tiempo límite de la llamada al almacén de datos

Aspectos generales

Una consulta recupera entidades del almacén de datos cuando estas reúnen una serie de condiciones, según se hayan especificado. La consulta especifica un tipo de entidad, varias condiciones o ninguna en función de los valores de propiedad de la entidad (en ocasiones denominados "filtros") y varias descripciones de criterios de ordenación o ninguna. Cuando se ejecuta la consulta, esta extrae las entidades de un determinado tipo que reúnen todas las condiciones especificadas, según el criterio de ordenación indicado.

El almacén de datos principal/secundario y el almacén de datos de replicación con alta disponibilidad ofrecen garantías distintas en cuanto a la coherencia de las consultas. De forma predeterminada:

El almacén de datos principal/secundario presenta una consistencia fuerte en todas las consultas. Por su parte, el almacén de datos de replicación con alta disponibilidad ofrece consistencia fuerte de forma predeterminada en

las consultas de un grupo de entidades. En este tipo de almacenes, las consultas que no son de ancestro siempre son de consistencia eventual.

Para obtener más información al respecto, consulta Configuración de la política de lectura y el tiempo límite de la llamada al almacén de datos.

El API de Java de nivel inferior ofrece una clase Query para crear consultas y una clase PreparedQuery para la extracción y devolución de entidades del almacén de datos.import com.google.appengine.api.datastore.DatastoreService;import com.google.appengine.api.datastore.DatastoreServiceFactory;import com.google.appengine.api.datastore.Entity;import com.google.appengine.api.datastore.PreparedQuery;import com.google.appengine.api.datastore.Query;

// ...// Get the Datastore ServiceDatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

// The Query interface assembles a queryQuery q = new Query("Person")q.addFilter("lastName", Query.FilterOperator.EQUAL, lastNameParam);q.addFilter("height", Query.FilterOperator.LESS_THAN, maxHeightParam);

// PreparedQuery contains the methods for fetching query results// from the datastorePreparedQuery pq = datastore.prepare(q);

for (Entity result : pq.asIterable()) {  String firstName = (String) result.getProperty("firstName");  String lastName = (String) result.getProperty("lastName");  Long height = (Long) result.getProperty("height");  System.out.println(lastName + " " + firstName + ", " + height.toString() + " inches tall");}

Un filtro contiene un nombre de propiedad, un operador de comparación y un valor. Una entidad transmite el filtro si dispone de una propiedad del nombre determinado y su valor es comparable con el del valor determinado del filtro que describe el operador. La entidad es el resultado de la consulta si esta transmite todos sus filtros.

El operador de filtro puede ser cualquiera de los siguientes:

Query.FilterOperator.LESS_THAN Query.FilterOperator.LESS_THAN_OR_EQUAL

Page 34: Google App Engine

Query.FilterOperator.EQUAL Query.FilterOperator.GREATER_THAN Query.FilterOperator.GREATER_THAN_OR_EQUAL Query.FilterOperator.NOT_EQUAL Query.FilterOperator.IN (igual a cualquiera de los valores de la lista proporcionada)

En realidad, el operador NOT_EQUAL realiza dos consultas: una donde todos los demás filtros son el mismo y el filtro "no igual a" se sustituye por un filtro "menor que" y otra donde el filtro "no igual a" se sustituye por un filtro "mayor que". Los resultados se fusionan en orden. Como se describe a continuación en la sección sobre los filtros de desigualdad, una consulta solo puede disponer de un filtro "no igual a" y no puede presentar ningún otro filtro de desigualdad.

El operador IN también realiza varias consultas, una por cada elemento del valor de la lista proporcionada, donde los demás filtros son iguales y el filtro IN se sustituye por un filtro "igual a". Los resultados se fusionan en el orden de los elementos de la lista. Si una consulta dispone de más de un filtro IN, esta se realiza como si se tratara de varias consultas, una por cada combinación de valores de los filtros IN.

Una sola consulta que contenga los operadores NOT_EQUAL o IN tiene un límite de 30 subconsultas.

Para obtener más información sobre cómo las consultas NOT_EQUAL y IN se traducen en varias consultas en un marco JDO/JPA, consulta Queries with NOT_EQUAL and IN filters (Consultas con filtros "!=" e "IN").

También es posible que una consulta devuelva solamente las claves de las entidades en lugar de las propias entidades.Query q = new Query("Person").setKeysOnly();

Consultas sin tipo

Una consulta a la que no se aplican filtros de tipo, ancestro o clave devuelve todas las entidades que se encuentran en el almacén de datos de la aplicación. Ello incluye las entidades que crean y administran otras funciones de App Engine, como las entidades con estadísticas (que existen para todas las aplicaciones) y las entidades de metadatos del almacén de blob (si existen). Las consultas sin tipo no pueden incluir filtros de propiedades, pero si pueden filtrar por clave de entidad utilizando Entity.KEY_RESERVED_PROPERTY como nombre de propiedad para el filtro. También funciona el orden ascendente en Entity.KEY_RESERVED_PROPERTY.

Nota: las consultas sin tipo no funcionan actualmente en el servidor de desarrollo de la aplicación.

Consultas de ancestro

Puedes filtrar las consultas del almacén de datos por un ancestro determinado para que los resultados solo incluyan las entidades que contengan dicho ancestro. En otras palabras, todos los resultados incluirán el ancestro como elemento principal o como elemento principal del elemento principal, etc.

Si asignas "null" como parámetro a Query.setAncestor(String ancestor), no se realizará ninguna consulta de entidades sin ancestros (en estos momentos no se admite este tipo de consulta).DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();Entity person = new Entity("Person", "tom");

Entity weddingPhoto = new Entity("Photo", person.getKey());weddingPhoto.setProperty("imageUrl",    "http://domain.com/some/path/to/wedding_photo.jpg");

Entity babyPhoto = new Entity("Photo", person.getKey());babyPhoto.setProperty("imageUrl",    "http://domain.com/some/path/to/baby_photo.jpg");

Entity dancePhoto = new Entity("Photo", person.getKey());dancePhoto.setProperty("imageUrl",    "http://domain.com/some/path/to/dance_photo.jpg");

Entity campingPhoto = new Entity("Photo");dancePhoto.setProperty("imageUrl",    "http://domain.com/some/path/to/camping_photo.jpg");

datastore.put(Arrays.asList(person, weddingPhoto, babyPhoto, dancePhoto,

Page 35: Google App Engine

    campingPhoto));

Query userPhotosQuery = new Query("Photo");userPhotosQuery.setAncestor(person.getKey());

// This returns weddingPhoto, babyPhoto and dancePhoto, but// not campingPhoto because tom is not an ancestor.List<Entity> results = datastore.prepare(userPhotosQuery).asList(    FetchOptions.Builder.withDefaults());

Consultas de ancestro sin tipo

También puedes realizar consultas de entidades con un ancestro determinado independientemente del tipo. Estas consultas también pueden incluir filtros, de igualdad o de desigualdad, en Entity.KEY_RESERVED_PROPERTY. Las consultas sin tipo no pueden incluir filtros de propiedades. Sin embargo, pueden filtrar por clave de entidad asignando Entity.KEY_RESERVED_PROPERTY como nombre de propiedad del filtro. También funciona el orden ascendente en Entity.KEY_RESERVED_PROPERTY.

En concreto, una consulta de ancestro sin tipo, y sin filtros adicionales, siempre devuelve la entidad identificada de forma única por el ancestro (siempre que exista la entidad). El término "ancestro" parece indicar que las consultas devuelven solo elementos secundarios de una entidad, pero, de hecho, las consultas de ancestro pueden devolver la propia entidad. Ello se demuestra en el siguiente código de ejemplo.

Las consultas de ancestro sin tipo no requieren índices personalizados.

Para realizar una consulta de ancestro sin tipo usando la clase Query, invoca el constructor sin incluir el tipo:Query q =  new Query();

q.setAncestor(ancestorKey);q.addFilter(Entity.KEY_RESERVED_PROPERTY, Query.FilterOperator.GREATER_THAN, lastKeyParam)

En el siguiente código de ejemplo se muestra cómo procesar una iteración sobre todas las entidades pertenecientes a un usuario determinado: DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();Entity person = new Entity("Person", "tom");

Entity weddingPhoto = new Entity("Photo", person.getKey());weddingPhoto.setProperty("imageUrl",    "http://domain.com/some/path/to/wedding_photo.jpg");

Entity weddingVideo = new Entity("Video", person.getKey());weddingVideo.setProperty("videoUrl",    "http://domain.com/some/path/to/wedding.avi");

datastore.put(Arrays.asList(person, weddingPhoto, weddingVideo));

Query userMediaQuery = new Query();userMediaQuery.setAncestor(person.getKey());    // Ancestor queries return ancestors by default. This filter// excludes the ancestor from query results.userMediaQuery.addFilter(Entity.KEY_RESERVED_PROPERTY, Query.FilterOperator.GREATER_THAN,    person.getKey());

// Returns both weddingPhoto and weddingVideo even though they are// different entity kinds.List<Entity> results = datastore.prepare(userMediaQuery).asList(    FetchOptions.Builder.withDefaults());

Restricciones aplicadas a las consultas

La naturaleza del mecanismo de consulta de índices impone ciertas restricciones en el funcionamiento de una consulta. Dichas limitaciones se describen en esta sección.

Page 36: Google App Engine

Para poder filtrar u ordenar los criterios de una propiedad, esta debe existir

Si una propiedad presenta una condición de filtro o un criterio de ordenación de consulta, esta última devuelve solo las entidades del almacén de datos cuya propiedad tenga un valor (incluido un valor nulo).

No es necesario que las entidades de un mismo tipo tengan asociadas las mismas propiedades. El filtro de una propiedad solo puede coincidir con una entidad cuya propiedad presente un valor. Si en una entidad, la propiedad que se utiliza en el filtro o en los criterios de ordenación no tiene ningún valor asociado, dicha entidad se omite del índice que se crea a partir de la consulta.

Los filtros de desigualdad solo se admiten en una propiedad

Las consultas solo pueden emplear filtros de desigualdad (<, <=, >=, > y !=) en una propiedad en todos sus filtros.

Por ejemplo, se admitiría la siguiente consulta:import com.google.appengine.api.datastore.Query;import com.google.appengine.api.datastore.Query.FilterOperator;import com.google.appengine.api.datastore.Query.SortDirection;

Query q = new Query("Person");q.addFilter("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYearParam);q.addFilter("birthYear", FilterOperator.LESS_THAN_OR_EQUAL, maxBirthYearParam);

Sin embargo, la consulta siguiente no se permitiría porque emplea filtros de desigualdad en dos propiedades distintas de la misma consulta:Query q = new Query("Person");q.addFilter("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYearParam);// Errorq.addFilter("height", FilterOperator.GREATER_THAN_OR_EQUAL, minHeightParam);

Los filtros pueden combinar comparaciones equivalentes (==) para propiedades distintas en una misma consulta, incluidas las consultas con una o varias condiciones de desigualdad en una propiedad. La consulta siguiente se admitiría:Query q = new Query("Person");q.addFilter("lastName", FilterOperator.EQUAL, lastNameParam);q.addFilter("city", FilterOperator.EQUAL, cityParam);q.addFilter("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYearParam);

Para que dos consultas sean adyacentes en la tabla de índice, el mecanismo de consulta se basa en todos los resultados. Así se evita tener que revisar cada uno de los resultados de la tabla. Una sola tabla de índice no puede representar varios filtros de desigualdad en distintas propiedades y al mismo tiempo conservar el orden consecutivo de los resultados en la tabla.

Las propiedades en filtros de desigualdad deben ordenarse antes que otros criterios de ordenación

Si una consulta incluye un filtro con una comparación de desigualdad y uno o varios criterios de ordenación, esta deberá contener un criterio de ordenación para la propiedad que se utilice en la desigualdad. Por otro lado, el criterio de ordenación deberá aparecer antes que los criterios de ordenación de otras propiedades.

Esta consulta no es válida porque emplea un filtro de desigualdad y no ordena por la propiedad filtrada:Query q = new Query("Person");q.addFilter("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYearParam);q.addSort("lastName", SortDirection.ASCENDING); // ERROR

La consulta siguiente tampoco es válida porque no ordena por la propiedad filtrada antes de ordenar por otras propiedades:Query q = new Query("Person");q.addFilter("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYearParam);q.addSort("lastName", SortDirection.ASCENDING); // ERRORq.addSort("birthYear", SortDirection.ASCENDING);

La consulta siguiente es válida:Query q = new Query("Person");q.addFilter("birthYear", FilterOperator.GREATER_THAN_OR_EQUAL, minBirthYearParam);q.addSort("birthYear", SortDirection.ASCENDING);q.addSort("lastName", SortDirection.ASCENDING);

Page 37: Google App Engine

Para obtener todos los resultados que coinciden con un filtro de desigualdad, la consulta analiza la tabla de índice y localiza la primera fila coincidente. A continuación, devuelve todos los resultados consecutivos hasta la siguiente fila que no coincide con los criterios especificados. Para que las filas consecutivas representen el conjunto de resultados completo, el filtro de desigualdad debe ordenar las filas antes que otros criterios de ordenación.

Las propiedades con más de un valor pueden tener un comportamiento inesperado

Debido a la forma en que están indexadas las propiedades con más de un valor, estas interactúan con los filtros y los criterios de ordenación de la consulta de una forma concreta y, en ocasiones, inesperada.

En primer lugar, si una consulta incluye varios filtros de desigualdad en una determinada propiedad, la entidad coincidirá con la consulta solo si esa propiedad tiene asociado un valor que cumpla con todos los filtros de desigualdad. Por ejemplo, si una entidad presenta los valores [1, 2] para la propiedad x, no coincidirá con la consulta WHERE x > 1 AND x < 2. Cada filtro coincide con uno de los valores de x, pero ninguno de los valores coincide con ambos filtros.

Ten en cuenta que esto no sucede con los filtros =. Por ejemplo, la consulta WHERE x = 1 AND x = 2 devolverá la entidad anterior.

El operador != funciona como la fórmula de comprobación "valor distinto de". Así, por ejemplo, el filtro x != 1 coincide con una entidad con un valor mayor o menor que 1. Solo en Java se puede utilizar un filtro del tipo x != 1 AND x != 2, el cual funciona de la siguiente manera: "a < 1 OR (a > 1 y a < 2) OR a > 2". Por lo tanto, [1, 2] coincide, pero [1, 2, 3], no.

Del mismo modo, no son habituales los criterios de ordenación para propiedades con distintos valores:

Si las entidades se ordenan por una propiedad con más de un valor en orden ascendente, el valor más pequeño es el que se utiliza para la ordenación.

Si las entidades se ordenan por una propiedad con más de un valor en orden descendente, el valor más grande es el que se utiliza para la ordenación.

Los otros valores no afectan a los criterios de ordenación, ni tampoco la cantidad de valores.

Esta ordenación ocurre porque aparecen en el índice propiedades con distintos valores para cada uno de los valores que contienen. Sin embargo, el almacén de datos elimina los valores duplicados del índice, por lo que el primero que se detecta en el índice determina el criterio de ordenación.

Este presenta una consecuencia inusual: [1, 9] se incluye antes que [4, 5, 6, 7] en la ordenación ascendente y también en la descendente.

Los criterios de ordenación se ignoran en propiedades con filtros de igualdad

Es importante tener en cuenta las consultas con un filtro de igualdad y un criterio de ordenación en una propiedad con más de un valor. En estas consultas, el criterio de ordenación no se toma en consideración. En el caso de las propiedades con un solo valor, se trata de una simple optimización. Todos los resultados tendrían asignado el mismo valor para la propiedad, por lo que estos no deberán ordenarse más.

Sin embargo, las propiedades con distintos valores podrían presentar valores adicionales. Puesto que el criterio de ordenación no se tiene en cuenta, los resultados de la consulta se podrían mostrar en un orden diferente que si se aplicara el criterio de ordenación. Restaurar el criterio de ordenación ignorado resultaría caro y se necesitarían índices adicionales. Esta situación es inusual, por lo que el planificador de consultas la omite.

Ordenación de los resultados de la consulta cuando no se especifica ningún criterio de ordenación

Cuando una consulta no especifica el orden, el almacén de datos devuelve los resultados en el mismo orden en que los ha recuperado. A medida que introducimos cambios en la implementación del almacén de datos, el orden de los resultados en una consulta sin orden especificado también puede cambiar. Por lo tanto, si quieres que los resultados de la consulta presenten un orden determinado, no olvides indicarlo en la consulta. De lo contrario, los resultados se mostrarán en un orden indefinido que puede variar con el tiempo.

Las consultas incluidas en transacciones deben contener filtros de ancestro

Las consultas también se permiten dentro de una transacción, siempre y cuando incluyan un filtro de ancestro. Este debe pertenecer al mismo grupo de entidades que las demás operaciones de la transacción. Con ello, se establece la limitación por la que una transacción solo puede operar en entidades de un mismo grupo de entidades.

Page 38: Google App Engine

Extracción de resultados

Después de crear la consulta, puedes especificar varias opciones de extracción con el fin de controlar los resultados que se devuelven.

Si solo quieres que se muestre una entidad que coincida con la consulta, puedes utilizar el método de consulta método PreparedQuery.asSingleEntity(). Esto devuelve el primer resultado que coincide con la consulta. Si existen varios resultados que coincidan con la consulta, el sistema genera una excepción TooManyResultsException.

Las consultas basadas únicamente en claves devuelven las claves de las entidades del almacén de datos que coinciden con tu consulta. Este tipo de consultas se ejecutan más rápidamente y consumen menos CPU que las que devuelven entidades completas. Para que una consulta solo devuelva las claves de una entidad, utiliza el método Query.setKeysOnly().

Puedes especificar un límite y una desviación para la consulta con el fin de controlar la cantidad y el intervalo de resultados que se devuelven en un lote. Si especificas un límite representado por un número entero, se devolverá esa cantidad máxima de resultados. Si indicas una desviación mediante un número entero, se omitirá esa cantidad de resultados y se devolverá el resto, hasta el límite especificado. Por ejemplo, para extraer las cinco personas más altas del almacén de datos, la consulta debería crearse de la forma siguiente: // Construct then prepare your queryList<Entity> get5TallestPeople() {    DatastoreService ds = DatastoreServiceFactory.getDatastoreService();    Query q = new Query("Person");    q.addSort("height", SortDirection.DESCENDING);    PreparedQuery pq = ds.prepare(q);    return pq.asList(FetchOptions.Builder.withLimit(5));}

Si, en lugar de ello, quieres extraer las personas más altas entre la sexta y la décima posición, deberías utilizar esta otra consulta: return pq.asList(FetchOptions.Builder.withLimit(5).offset(5)));

Al iterar los resultados mediante PreparedQuery.asIterable() y PreparedQuery.asIterator(), el almacén de datos extrae los resultados en lotes. De forma predeterminada, cada lote contiene 20 resultados, pero puedes cambiar este valor mediante FetchOptions.chunkSize(int). Puedes seguir iterando resultados de consultas hasta que se devuelvan todas o hasta que se agote el tiempo de espera de la solicitud.

Introducción a los índices

Todas las consultas del almacén de datos se basan en un índice, es decir, una tabla con los resultados de la consulta en el orden deseado. Las aplicaciones App Engine definen los índices en un archivo de configuración denominado datastore-indexes.xml. El servidor de desarrollo web añade automáticamente sugerencias a este archivo cuando detecta consultas que todavía no tienen configurado el índice. Puedes ajustar estos índices manualmente modificando el archivo antes de subir la aplicación.

El mecanismo de consulta basado en índices admite la mayoría de los tipos de consulta más extendidos. Sin embargo, es incompatible con algunos tipos de consulta habituales en otras tecnologías de base de datos. A continuación, se describen las restricciones de consulta y la correspondiente explicación.

El almacén de datos dispone de un índice para cada consulta que intenta crear la aplicación. A medida que la aplicación modifica las entidades del almacén de datos, este último actualiza los índices con los resultados correctos. Cuando la aplicación ejecuta una consulta, el almacén de datos extrae los resultados directamente del índice en cuestión.

Para ejecutar una consulta, el almacén de datos realiza los pasos siguientes:

1. El almacén de datos identifica el índice que corresponde con el tipo, las propiedades y los operadores de filtro, así como los criterios de ordenación de la consulta.

2. El almacén de datos empieza a analizar el índice a partir de la primera entidad que reúne todas las condiciones de los filtros mediante los valores de filtro de la consulta.

3. El almacén de datos continúa con la operación, devolviendo cada una de las entidades, hasta que detecta una entidad que no reúne las condiciones de los filtros, llega al final del índice o recaba el número máximo de resultados que exija la consulta.

La tabla de índice contiene columnas para cada una de las propiedades que se utiliza en el filtro o en los criterios de ordenación. Las filas se ordenan según los aspectos siguientes, por orden:

ancestros

Page 39: Google App Engine

valores de propiedad utilizados en filtros de igualdad valores de propiedad utilizados en filtros de desigualdad valores de propiedad utilizados en criterios de ordenación

Con ello, los resultados de cada posible consulta que utiliza el índice se incluyen en filas consecutivas de la tabla.

El índice incluirá una única entidad si este hace referencia a todas las propiedades de la entidad. Si el índice no hace referencia a alguna de las propiedades de la entidad, dicha entidad no aparecerá en el índice y nunca será un resultado de la consulta que utiliza el índice.

Ten en cuenta que el almacén de datos de App Engine distingue entre una entidad que no posee una propiedad y una entidad que posee una propiedad con un valor de null . Si quieres que las entidades de un determinado tipo puedan ser el resultado de una consulta, utiliza una clase de datos JDO o JPA, la cual siempre asigna un valor a todas las propiedades que corresponden a un campo de la clase.

Propiedades no indexadas

Las consultas no encontrarán los valores de aquellas propiedades que no estén indexadas, entre estas, propiedades marcadas como no indexadas o propiedades con valores del tipo de valor de texto largo (Text) o de valor binario largo (Blob).

Una consulta con un filtro o con un criterio de ordenación en una propiedad nunca coincidirá con una entidad cuyo valor para dicha propiedad sea Text o Blob, o que se haya escrito con esa propiedad marcada como no indexada. Las propiedades con estos valores se comportan como si no se hubieran definido en términos de filtros y de criterios de ordenación.

Si tienes una propiedad que sabes que no deberás filtrar ni ordenar por criterios, elimina la propiedad del índice. Con ello, las operaciones de escritura consumirán menos CPU porque el almacén de datos ya no tendrá que mantener las entradas de índice para la propiedad en cuestión. Para que no se indexe una propiedad, utiliza Entity.setUnindexedProperty():DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();Entity tom = new Entity("Person", "tom");tom.setProperty("age", 32);

Entity lucy = new Entity("Person", "lucy");lucy.setUnindexedProperty("age", 29);

datastore.put(Arrays.asList(tom, lucy));

Query query = new Query("Person");query.addFilter("age", FilterOperator.GREATER_THAN, 25);

// Returns tom but not lucy because her age is unindexedList<Entity> results = datastore.prepare(query).asList( FetchOptions.Builder.withDefaults());

Combinación de tipos de valor

Cuando dos entidades contienen propiedades con el mismo nombre, pero los tipos de valor son diferentes, el índice de la propiedad ordena las entidades primero por tipo de valor y, a continuación, por un criterio adecuado para el tipo. Por ejemplo, si hay dos entidades con la propiedad "edad", una con un valor de número entero y la otra con un valor de cadena, la primera siempre aparecerá antes que la segunda cuando se ordene por la propiedad "Edad", independientemente de los valores.

Hay que tener esto en cuenta en el caso de números enteros y números de punto flotante, ya que el almacén de datos los considera dos tipos distintos de valores. Una propiedad que presente un valor de número entero 38 se incluirá antes que otra con un valor de punto flotante 37.5 porque, al ordenar los valores, los números enteros siempre preceden a los números de punto flotante.

Consultas que necesitan índices

App Engine genera índices para diversas consultas sencillas de forma predeterminada. Para otras consultas, la aplicación debe especificar los índices que necesita en un archivo de configuración con el nombre datastore-indexes.xml. Si la aplicación que se ejecuta en App Engine intenta realizar una consulta para la que no existe índice (tanto si se proporciona de forma predeterminada como si se describe a través de datastore-indexes.xml), la consulta generará un error y devolverá la excepción DatastoreNeedIndexException.

App Engine proporciona índices automáticos para los formatos de consulta siguientes:

Page 40: Google App Engine

consultas que únicamente emplean filtros de igualdad y de ancestro, consultas que únicamente emplean filtros de desigualdad (se incluyen en una sola propiedad), consultas sin tipo que solo emplean filtros de ancestro y de clave, consultas sin filtros y un solo criterio de clasificación en una propiedad, ya sea en orden ascendente o descendente, consultas que únicamente emplean filtros de igualdad en propiedades y filtros de desigualdad en filtros de ancestro y de clave.

Existen otros tipos de consultas cuyos índices deben especificarse en datastore-indexes.xml como, por ejemplo:

consultas con varios criterios de ordenación, consultas con un criterio de ordenación en claves, en orden descendente, consultas con uno o varios filtros de desigualdad en una propiedad y con uno o varios filtros de igualdad en otras propiedades, consultas con filtros de desigualdad y filtros de ancestro.

El servidor de desarrollo web permite gestionar fácilmente la configuración de índices. Si, por ejemplo, no se puede ejecutar una consulta porque esta necesita un índice, en lugar de generar un error, el servidor crea la configuración del índice y la consulta se realiza correctamente. Si al probar una aplicación de forma local, esta invoca todas las consultas posibles, es decir, con todas las combinaciones de filtros y de criterios de ordenación, las entradas que se generen corresponderán a un conjunto completo de índices. Si la prueba no ejecuta todas las combinaciones de consulta posibles, revisa y ajusta la configuración del índice antes de subir la aplicación.

Entidades de gran tamaño e índices de ampliación

Tal y como se describe anteriormente, las propiedades (que no presenten los valores Text o Blob) de cada entidad se añaden al menos a una tabla de índice, incluido un índice simple que se proporciona de forma predeterminada, así como los índices descritos en el archivo datastore-indexes.xml de la aplicación que hagan referencia a la propiedad. Para cada entidad que incluya un valor por propiedad, App Engine almacena un valor de propiedad una vez en su índice simple. Asimismo, almacena un valor de propiedad una vez en el índice personalizado por cada referencia que se haga a la propiedad. Estas entradas de índice deben actualizarse cada vez que se modifique el valor de la propiedad. Por lo tanto, cuantos más índices hagan referencia a la propiedad, más tiempo necesitará la CPU del almacén de datos para actualizar la propiedad.

En el almacén de datos existe un límite del número de entradas de índice por entidad. El límite es amplio y no afecta a la mayoría de las aplicaciones, pero hay casos en los que sí. Por ejemplo, una entidad que incluya muchas propiedades de un solo valor podría exceder el límite de entradas de índice.

Las propiedades que incluyen distintos valores almacenan cada uno de estos por separado en el índice. Una entidad que incluya una sola propiedad con muchos valores podría superar el límite de entradas.

Los índices personalizados que hacen referencia a varias propiedades con muchos valores pueden aumentar considerablemente de tamaño, aunque los valores sean pocos. Para poder registrar estas propiedades, la tabla de índice debe incluir una fila por cada cambio que se produzca en los valores de las propiedades del índice.

Por ejemplo, el índice siguiente (descrito en la sintaxis datastore-indexes.xml) incluye las propiedades x e y para las entidades del tipo MyModel:<?xml version="1.0" encoding="utf-8"?><datastore-indexes>    <datastore-index kind="MyModel">        <property name="x" direction="asc" />        <property name="y" direction="asc" />    </datastore-index></datastore-indexes>

El código siguiente crea una entidad con dos valores para la propiedad x y dos valores para la propiedad y:Entity myModel = new Entity("Model");myModel.setProperty("x", Arrays.asList("one", "two"));myModel.setProperty("y", Arrays.asList("three", "four"));ds.put(myModel)

Para representar de forma exacta estos valores, el índice debe almacenar 12 valores de propiedad: dos para cada uno de los valores de x y de y, y uno para cada cambio que se produzca en x y en y. Si el índice contiene muchos valores de propiedades con distintos valores, este deberá almacenar muchas entradas de índice para una sola entidad. Un índice que haga referencia a varias propiedades con distintos valores podría denominarse "índice de ampliación" porque puede aumentar considerablemente de tamaño con muy pocos valores.

Page 41: Google App Engine

Si se ejecuta el método put() y el resultado incluye un número de entradas superior al límite establecido, la invocación genera una excepción de error. Si creas un índice nuevo y el número de entradas supera el límite en cualquiera de las entidades, se produce un error en las consultas que se realizan al índice y este se muestra con un estado de error en la Consola del administrador.

Si no quieres que un índice se amplíe, evita las consultas que requieran un índice personalizado mediante el uso de una propiedad de lista. Tal y como se ha descrito anteriormente, esto incluye consultas con varios criterios de ordenación, una combinación de filtros de igualdad y de desigualdad, y filtros de ancestro.

Cursores de consultas

Los cursores de consultas permiten que una aplicación realice una consulta, recupere un conjunto de resultados y, a continuación, extraiga más resultados para la misma consulta en una solicitud web posterior sin que la desviación de la consulta represente un obstáculo. Una vez que la aplicación haya extraído resultados para la consulta, puede solicitar una cadena codificada que represente la ubicación en el conjunto de resultados después de que se haya extraído el último resultado (el "cursor"). La aplicación puede utilizar el cursor para extraer más resultados en otro momento a partir de ese punto.

Un cursor es una cadena opaca de codificación base64 que representa la siguiente posición de inicio de una consulta después de una operación de extracción. La aplicación puede insertar el cursor en las páginas web como parámetros HTTP GET o POST, o bien puede almacenar el cursor en el almacén de datos, en Memcache o en una carga útil de tareas de una cola de tareas. Un controlador de solicitud posterior puede realizar la misma consulta e incluir el cursor en la consulta para indicar al almacén de datos que debe empezar a devolver resultados desde la ubicación que representa el cursor. Solo la aplicación que haya realizado la consulta original puede utilizar el cursor, y únicamente para continuar con la misma consulta.

Consejo: por lo general, es seguro pasar un cursor del almacén de datos a un cliente, por ejemplo, en formato web, y aceptar el valor del cursor de un cliente. Un cliente no puede cambiar el valor del cursor para acceder a resultados que no pertenezcan a la consulta original. Sin embargo, se puede descodificar el valor de codificación base64 para mostrar información sobre las entidades resultantes como, por ejemplo, la clave (ID de aplicación, tipo, nombre de clave o ID y todas las claves de ancestro) y las propiedades que se han utilizado en la consulta (incluidos filtros y criterios de ordenación). Si no quieres que los usuarios tengan acceso a dicha información, puedes encriptar el cursor o almacenarlo y mostrar al usuario una clave opaca.

Modificación de cursores y de datos

La posición del cursor es la ubicación en la lista de resultados después del último resultado. Un cursor no es una posición relativa en la lista (no es una desviación), sino que es el punto desde el cual el almacén de datos puede empezar a analizar los resultados del índice. Si los resultados de una consulta cambian entre los usos de un cursor, la consulta detecta únicamente los cambios de los resultados que se encuentran después del cursor. Si aparece un resultado nuevo antes de la posición del cursor de esa consulta, no se devolverá cuando se extraigan los resultados después del cursor. Del mismo modo, si una entidad ya no forma parte de los resultados de la consulta pero se había mostrado antes del cursor, los resultados que aparecen después del cursor no cambian. Aunque el último resultado que se devuelve se elimine del conjunto de resultados, el cursor sabe cómo localizar el siguiente resultado.

Un uso interesante que se le puede dar a los cursores es supervisar entidades para cambios no detectados. Si la aplicación establece una propiedad de marca horaria con la correspondiente fecha y hora cada vez que cambia una entidad, dicha aplicación puede utilizar una consulta ordenada de forma ascendente por propiedad de marca de tiempo, con un cursor del almacén de datos para saber cuándo una entidad se coloca al final de la lista de resultados. Si se cambia la marca de tiempo de una entidad, la consulta que incluye el cursor devuelve la entidad modificada. Si no se han modificado entradas desde la última ejecución de la consulta, no se devuelven resultados y el cursor no se mueve.

Para recuperar los resultados de una consulta, puedes utilizar tanto un cursor de inicio como de finalización para devolver continuamente un grupo de resultados del almacén de datos. Cuando utilizas un cursor inicial y otro final para recuperar los resultados, no se garantiza que el tamaño de estos sea el mismo que al generar los cursores. Las entidades se pueden añadir al almacén de datos o bien eliminarse entre el momento en que se generan los cursores y el momento en que se utilizan en una consulta.

En el API de Java de nivel inferior, la aplicación puede usar cursores a través de las interfaces QueryResultList, QueryResultIterable y QueryResultIterator, las cuales se recuperan mediante los métodos PreparedQuery (asQueryResultList(), asQueryResultIterable() y asQueryResultIterator(), respectivamente). Estos objetos de resultado proporcionan un método getCursor(), el cual devuelve el objeto Cursor. La aplicación puede obtener una cadena apta para la Web invocando el método toWebSafeString() del objeto Cursor y, además, crear un objeto Cursor a partir de una cadena apta para web mediante el método estático Cursor.fromWebSafeString(). En el siguiente ejemplo se muestra el uso de cursores para la paginación.import java.io.IOException;

import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;

Page 42: Google App Engine

import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;

import com.google.appengine.api.datastore.Cursor;import com.google.appengine.api.datastore.DatastoreService;import com.google.appengine.api.datastore.DatastoreServiceFactory;import com.google.appengine.api.datastore.Entity;import com.google.appengine.api.datastore.FetchOptions;import com.google.appengine.api.datastore.PreparedQuery;import com.google.appengine.api.datastore.Query;import com.google.appengine.api.datastore.QueryResultList;

public class ListPeopleServlet extends HttpServlet {

    @Override    protected void doGet(HttpServletRequest req, HttpServletResponse resp)          throws ServletException, IOException {

        DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();        Query q = new Query("Person");        PreparedQuery pq = datastore.prepare(q);        int pageSize = 15;

        resp.setContentType("text/html");        resp.getWriter().println("<ul>");

        FetchOptions fetchOptions = FetchOptions.Builder.withLimit(pageSize);        String startCursor = req.getParameter("cursor");

        // If this servlet is passed a cursor parameter, let's use it        if (startCursor != null) {            fetchOptions.startCursor(Cursor.fromWebSafeString(startCursor));        }

        QueryResultList<Entity> results = pq.asQueryResultList(fetchOptions);        for (Entity entity : results) {            resp.getWriter().println("<li>" + entity.getProperty("name") + "</li>");        }        resp.getWriter().println("</ul>");

        String cursor = results.getCursor().toWebSafeString();

        // Assuming this servlet lives at '/people'        resp.getWriter().println(            "<a href='/people?cursor=" + cursor + "'>Next page</a>");    }}

Limitaciones de los cursores

A continuación, te indicamos algunos aspectos que se deben tener en cuenta sobre los cursores:

No se pueden utilizar cursores en consultas que emplean los operadores de filtro IN o !=. Para usar un cursor, la aplicación debe realizar la misma consulta que haya proporcionado el cursor, incluido el tipo, los filtros y

los valores de filtro, el filtro de ancestro y el criterio de ordenación. No hay forma de extraer los resultados mediante un cursor sin configurar la misma consulta.

Una consulta con un cursor no siempre funciona según lo previsto si la consulta incluye un filtro de desigualdad o un criterio de ordenación en una propiedad con valores distintos. La lógica que elimina las propiedades de distintos valores duplicadas de la consulta no perdura en las consultas, por lo que podría devolver un resultado más de una vez.

Los cursores dependen de la configuración del índice que admite la consulta. En los casos en los que las distintas configuraciones del índice admiten la misma consulta, si se modifica la configuración del índice de la consulta, los cursores dejarán de ser válidos. Por ejemplo, una consulta únicamente con filtros de igualdad y ningún criterio de ordenación puede ser compatible con los índices incorporados en la mayoría de los casos; también podría utilizar un índice personalizado, si este se proporciona. Los cursores también dependen de los ajustes de implementación, que pueden variar o anularse con las nuevas

Page 43: Google App Engine

versiones de App Engine. Si una aplicación intenta utilizar un cursor que ha dejado de ser válido, el almacén de datos genera una excepción. En Java, genera IllegalArgumentException (API de bajo nivel), JDOFatalUserException (JDO) o PersistenceException (JPA).

Configuración de la política de lectura y el tiempo límite de la llamada al almacén de datos

Con el fin de incrementar la disponibilidad de los datos, puedes configurar la política de lectura de forma que las operaciones de lectura y las consultas presenten una consistencia eventual. Si bien el API también permite definir de forma explícita una política de consistencia fuerte, en el almacén de datos de replicación con alta disponibilidad, las consultas que no sean de ancestro siempre son consistentes eventualmente. Si en este tipo de almacenes, la política de lectura de consultas que no son de ancestro se configura en una opción de consistencia fuerte, esto no tendrá efecto alguno.

Al establecer la opción de consistencia eventual para una consulta del almacén de datos, a los índices que utiliza la consulta para recabar resultados también se accede mediante consistencia eventual. De vez en cuando, estas consultas devuelven entidades que no coinciden con los criterios de la consulta. Sin embargo, el otro tipo de consultas devuelve siempre resultados coherentes. Puedes utilizar transacciones para garantizar que el conjunto de resultados sea coherente si la consulta incluye un filtro de ancestro. Consulta Aislamiento de transacciones en App Engine para obtener más información sobre cómo se modifican las entidades y los índices.

La especificación de DatastoreServiceConfig te permite seleccionar una de dos opciones de consistencia de lectura. Las opciones son "EVENTUAL" (para lecturas con consistencia eventual) y "STRONG" (para lecturas con consistencia fuerte). El valor predeterminado es "STRONG". La selección de una consistencia fuerte permite obtener datos más recientes, pero se producirán tiempos de espera con más frecuencia que con las lecturas de consistencia eventual. En cambio, las lecturas de consistencia eventual producen tiempos de espera con menos frecuencia, aunque de vez en cuando se obtienen resultados obsoletos. Puedes crear DatastoreServiceConfig mediante la clase DatastoreServiceConfig.Builder. En el siguiente fragmento de código se muestra cómo debe definirse la política de lectura, cómo establecer un tiempo límite de invocación y cómo definir ambos parámetros:double deadline = 5.0;

// Set the read policy with eventual consistency.ReadPolicy policy = new ReadPolicy(ReadPolicy.Consistency.EVENTUAL);

DatastoreServiceConfig eventuallyConsistentConfig = DatastoreServiceConfig.Builder.withReadPolicy(policy);

// Set the call deadline.DatastoreServiceConfig deadlineConfig = DatastoreServiceConfig.Builder.withDeadline(5);

// Set both the read policy and the call deadline.DatastoreServiceConfig datastoreConfig = DatastoreServiceConfig.Builder.withDeadline(deadline).readPolicy(policy);

DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(datastoreConfig);

El tiempo límite de invocación del almacén de datos es de 30 segundos de forma predeterminada. Actualmente, no es posible establecer el tiempo límite en un valor mayor, pero puedes ajustarlo a un valor menor si quieres asegurarte de que una determinada operación falle rápidamente.

Transacciones

El almacén de datos de App Engine admite transacciones. Una transacción es una operación o un conjunto de operaciones que se resuelven correcta o incorrectamente en su totalidad. Una aplicación puede realizar varias operaciones y distintos cálculos en una misma transacción.

Transacciones Acciones permitidas en una transacción Aislamiento y coherencia Usos de las transacciones Colocación en cola de las tareas de transacción

Page 44: Google App Engine

Transacciones

Una transacción es una operación o un conjunto de operaciones del almacén de datos que se resuelven correcta o incorrectamente en su totalidad. Si la transacción se resuelve correctamente, todos los efectos deseados se aplicarán al almacén de datos. Si la transacción no se resuelve correctamente, no se aplicará ninguno de los efectos.

Todas las operaciones de escritura del almacén de datos son atómicas. Es decir, los intentos de crear, modificar o eliminar una entidad se aplican o no se aplican. Una operación puede fallar debido a una tasa de contención demasiado elevada provocada por el hecho de que muchos usuarios estén intentando modificar una entidad al mismo tiempo. También se pueden producir fallos en las operaciones si una aplicación alcanza un límite de cuota o si se produce un error interno en el almacén de datos. En cualquiera de los casos, los efectos de la operación no se aplicarán y el API del almacén de datos generará una excepción.

Las transacciones son una función opcional del almacén de datos, es decir, no es necesario recurrir a ellas para realizar operaciones.

A continuación, se incluye un ejemplo de la actualización de un campo llamado vacationDays en una entidad del tipo Employee denominada Joe:DatastoreService datastore = DatastoreServiceFactory.getDatastoreService()Transaction txn = datastore.beginTransaction();try {    Key employeeKey = KeyFactory.createKey("Employee", "Joe");    Entity employee = datastore.get(employeeKey);    employee.setProperty("vacationDays", 10);

    datastore.put(employee);

    txn.commit();} finally {    if (txn.isActive()) {        txn.rollback();    }}

Ten en cuenta que, para ofrecerte ejemplos más breves, a veces, omitimos el bloque finally que realiza las tareas de recuperación en caso de que la transacción aún permanezca activa. En los códigos de producción es importante asegurarse de que cada transacción se lleve a cabo o se recupere de forma explícita.

Grupos de entidades

Cada entidad pertenece a un grupo de entidades, un conjunto de una o varias entidades que se pueden manipular en una única transacción. Las relaciones de grupos de entidades indican a App Engine que almacene varias entidades de la misma parte de la red distribuida. Una transacción configura las operaciones de almacén de datos para un grupo de entidades, tras lo cual se aplican todas las operaciones o ninguna de ellas (si se genera un error en la transacción).

Cuando la aplicación crea una entidad, puede asignar otra entidad como elemento principal de esa nueva entidad. Al asignar una entidad principal a una entidad nueva, se coloca la entidad nueva en el mismo grupo de entidades que la entidad principal.

Una entidad sin una entidad principal es una entidad raíz. Una entidad que sea la entidad principal de otra puede tener a su vez otra entidad principal. Una cadena de entidades principales de una entidad hasta la raíz es la ruta de la entidad; los miembros de la ruta son los ancestros de la entidad. La entidad principal de una entidad se define cuando se crea la entidad y no se puede modificar posteriormente.

Todas las entidades con una entidad raíz dada como ancestro pertenecen al mismo grupo de entidades. Todas las entidades de un grupo se almacenan en el mismo nodo de almacén de datos. Una única transacción puede modificar varias entidades de un grupo o añadir entidades nuevas al grupo haciendo que la entidad principal de la nueva entidad sea una entidad existente en el grupo. El siguiente fragmento de código incluye transacciones en varios tipos de entidad:DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();Entity person = new Entity("Person", "tom");datastore.put(person);

// Transactions on root entitiesTransaction tx = datastore.beginTransaction();

Entity tom = datastore.get(person.getKey());tom.setProperty("age", 40);

Page 45: Google App Engine

datastore.put(tom);tx.commit();

// Transactions on child entitiestx = datastore.beginTransaction();tom = datastore.get(person.getKey());Entity photo = new Entity("Photo", tom.getKey());

// Create a Photo that is a child of the Person entity named "tom"photo.setProperty("photoUrl", "http://domain.com/path/to/photo.jpg");datastore.put(photo);tx.commit();

// Transactions on entities in different entity groupstx = datastore.beginTransaction();tom = datastore.get(person.getKey());Entity photoNotAChild = new Entity("Photo");photoNotAChild.setProperty("photoUrl", "http://domain.com/path/to/photo.jpg");datastore.put(photoNotAChild);

// Throws IllegalArgumentException because the Person entity// and the Photo entity belong to different entity groups.tx.commit();

Creación de una entidad en un grupo de entidades específico

Cuando la aplicación crea una nueva entidad, puedes asignarla a un grupo de entidades facilitando la clave de otra entidad. En el siguiente ejemplo se crea la clave de la entidad MessageBoard y, a continuación, se usa dicha clave para crear y hacer persistente una entidad Message que se ubica en el mismo grupo de entidades que MessageBoard:DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();

String messageTitle = req.getParameter("title");String messageText = req.getParameter("body");Date postDate = new Date();

Transaction txn = datastore.beginTransaction();

Key messageBoardKey = KeyFactory.createKey("MessageBoard", boardName);

Entity message = new Entity("Message", messageBoardKey);message.setProperty("message_title", messageTitle);message.setProperty("message_text", messageText);message.setProperty("post_date", postDate);datastore.put(message);

txn.commit();

Acciones permitidas en una transacción

El almacén de datos impone una serie de restricciones respecto a las acciones que se pueden realizar en una transacción.

Todas las operaciones del almacén de datos incluidas en una transacción se deben ejecutar en entidades pertenecientes a un mismo grupo. Esto incluye realizar consultas de entidades por ancestro, recuperar entidades por clave, así como modificar y eliminar entidades. Ten en cuenta que cada entidad raíz pertenece a un grupo de entidades distinto, por lo que no es posible crear o realizar una transacción en más de una entidad raíz.

Si bien uno de los usos es aplicar cambios a las entidades de un grupo de entidades, los demás intentos de modificar una entidad del grupo fallan al ejecutarse. Ten en cuenta que, dado este diseño, si se utilizan grupos de entidades, se verá limitado el número de operaciones de escritura concurrentes en cualquiera de las entidades de dicho grupo. Cuando se inicia una transacción, App Engine utiliza el control de concurrencia optimista. Para ello, comprueba la última hora de modificación de un grupo de entidades. Tras ejecutar la transacción de un grupo de entidades, App Engine vuelve a comprobar la última hora de modificación del grupo. Si esta ha cambiado desde la última comprobación, se genera una excepción. Para obtener una explicación de los grupos de entidades, consulta la sección Grupos de entidades.

Page 46: Google App Engine

Una aplicación puede realizar consultas durante una transacción, pero solo si incluye un filtro de ancestro. De hecho, puedes realizar una consulta sin que incluya dicho filtro, pero los resultados no reflejarán un estado de transacción coherente. Una aplicación también puede obtener entidades del almacén de datos por clave durante una transacción. Puedes preparar claves antes de la transacción o bien crear claves dentro de la transacción con nombres de clave o ID.

Aislamiento y coherencia

El nivel de aislamiento del almacén de datos fuera de las transacciones es el más próximo a READ_COMMITTED. Dentro de las transacciones, por el contrario, el nivel de aislamiento es SERIALIZABLE, en concreto una forma de aislamiento de instantánea. Consulta el artículo sobre aislamiento de transacciones para obtener más información sobre los niveles de aislamiento.

Se garantiza que las consultas y los métodos get dentro de una transacción muestren una única instantánea coherente del almacén de datos a partir del inicio de la transacción. En concreto, las entidades y las filas de índice del grupo de entidades de la transacción se actualizan por completo para que las consultas devuelvan el conjunto correcto de entidades, sin falsos positivos ni falsos negativos descritos en el artículo sobre aislamiento de transacciones; estos pueden producirse en consultas fuera de las transacciones.

Esta vista de instantánea global también incluye operaciones de lectura producidas después de operaciones de escritura dentro de las transacciones. A diferencia de la mayoría de las bases de datos, las consultas y los métodos get que se realizan dentro de una transacción del almacén de datos no devuelven los resultados de operaciones de escritura anteriores en esa transacción. En concreto, si se modifica o se elimina una entidad dentro de una transacción, la consulta o el método get devuelve la versión original de la entidad del inicio de la transacción o bien no devuelve ningún resultado si la entidad no existía en ese momento.

Usos de las transacciones

En este ejemplo se ilustra una de las aplicaciones de las transacciones: cambiar el valor de propiedad de una entidad por un valor nuevo relativo a su valor actual. Puesto que el API del almacén de datos no vuelve a intentar las transacciones, se puede añadir lógica para intentar la operación de nuevo en caso de que otra solicitud modifique el mismo mensaje MessageBoard o alguno de sus mensajes al mismo tiempo.int retries = 3;while (true) {    Transaction txn = datastore.beginTransaction();    try {        Key boardKey = KeyFactory.createKey("MessageBoard", boardName);        Entity messageBoard = datastore.get(boardKey);

        long count = (Long) messageBoard.getProperty("count");        ++count;        messageBoard.setProperty("count", count);        datastore.put(messageBoard);

        txn.commit();        break;    } catch (ConcurrentModificationException e) {        if (retries == 0) {            throw e;        }        // Allow retry to occur        --retries;    } finally {        if (txn.isActive()) {            txn.rollback();        }    }}

Advertencia En los ejemplos anteriores se muestra cómo incrementar un contador mediante transacciones únicamente para simplificar los procesos. Si la aplicación incluye contadores que se modifican con frecuencia, no deberías incrementarlos mediante transacciones, tan siquiera en una única entidad. A la hora de trabajar con contadores, se recomienda emplear una técnica denominada partición horizontal de contadores.

Este método requiere una transacción porque el valor lo puede modificar otro usuario después de que este código extraiga el objeto, pero antes de que guarde el objeto modificado. Si no se utiliza una transacción, la solicitud del usuario emplea el valor de count anterior a la modificación del otro usuario y, al guardar, se sobrescribe el valor nuevo. Con una transacción, se informa a la aplicación de la

Page 47: Google App Engine

modificación del otro usuario. Si la entidad se modifica durante la transacción, esta última genera la excepción de error ConcurrentModificationException. La aplicación puede repetir la transacción con el fin de utilizar los datos nuevos.

Otro de los usos que se suele dar a las transacciones es modificar una entidad con una determinada clave o crearla si no existe:Transaction txn = datastore.beginTransaction();try {    Key boardKey = KeyFatory.createKey("MessageBoard", "Foo");    Entity messageBoard = datastore.get(boardKey);} catch (EntityNotFoundException e) {    messageBoard = new Entity("MessageBoard", boardName);    messageBoard.setProperty("count", 0L);    boardKey = datastore.put(messageBoard);}txn.commit();

Al igual que antes, las transacciones son necesarias en caso de que un usuario intente crear o modificar una entidad con el mismo ID de cadena que otro. Sin una transacción, si la entidad no existe y dos usuarios intentan crearla, la operación del segundo sobrescribe la del primero sin saberlo. Si se utiliza una transacción, el segundo intento falla de forma atómica. Si procede, la aplicación puede volver a intentar extraer la entidad y modificarla.

Cuando una transacción no se ejecuta, la aplicación puede volver a intentar la operación hasta que se realice correctamente o bien dejar que los usuarios gestionen el error transfiriéndolo a la interfaz de usuario de la aplicación. No es necesario crear un loop de nuevo intento para cada transacción.

Consejo: las transacciones se deben realizar lo más rápidamente posible para evitar que las entidades que incluye cambien, lo cual provocaría un error en la transacción. En la medida de lo posible, prepara los datos fuera de la transacción y, a continuación, ejecuta la transacción para realizar operaciones del almacén de datos basadas en un estado de coherencia. La aplicación debería preparar las claves para los objetos que se utilizan fuera de la transacción y, a continuación, extraer las entidades de dentro de la transacción.

Por último, puedes utilizar una transacción para leer una instantánea coherente del almacén de datos. Esto puede resultar útil cuando se necesitan varias operaciones de lectura para mostrar una página o exportar datos que deban ser coherentes. A menudo, este tipo de transacciones se denominan de solo lectura, ya que no realizan ninguna operación de escritura. Las transacciones de solo lectura nunca fallan debido a las modificaciones concurrentes, por lo que no es necesario realizar reintentos. Ejecutar y deshacer una transacción de solo lectura no son operaciones opcionales.DatastoreService ds = DatastoreServiceFactory.getDatastoreService();

// Display information about a message board and its first 10 messages.Key boardKey = KeyFactory.createKey("MessageBoard", boardName);

Transaction txn = datastore.beginTransaction();

Entity messageBoard = datastore.get(boardKey);long count = (Long) messageBoard.getProperty("count");

Query q = new Query("Message", boardKey);

// This is an ancestor query.PreparedQuery pq = datastore.prepare(q);List<Entity> messages = pq.asList(FetchOptions.Builder.withLimit(10)));  txn.commit();

Colocación en cola de las tareas de transacción

Puedes poner en cola una tarea como parte de una transacción del almacén de datos, por ejemplo para que solo se ponga en cola la tarea (y se garantice dicha operación), si la transacción se realiza correctamente. Si la transacción se lleva a cabo, se garantiza la puesta en cola de la tarea. Una vez en cola, no se garantiza la ejecución inmediata de la tarea y cualquier operación realizada en la tarea se ejecutará independientemente de la transacción original. Las tarea se procesa varias veces hasta que, finalmente, se lleva a cabo. Esto sucede cada vez que una tarea se pone en cola durante una transacción.

Las tareas de transacción son útiles porque permiten incluir acciones no relacionadas con el almacén de datos en una transacción del almacén de datos (como en el envío de un correo electrónico de confirmación de una compra). También puedes asociar acciones del

Page 48: Google App Engine

almacén de datos a la transacción, como la modificación de grupos de entidades adicionales ajenos a la transacción, solo si la transacción se realiza correctamente.

Una aplicación no puede insertar más de cinco tareas de transacción en las colas de tareas durante una única transacción. Las tareas de transacción no deben tener nombres especificados por el usuario.DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();Queue queue = QueueFactory.getDefaultQueue();Transaction txn = datastore.beginTransaction();// ...

queue.add(TaskOptions.Builder.url("/path/to/queue"));

// ...

txn.commit();

Elección de un almacén de datos (Java)

App Engine offers two data storage options with different reliability and consistency guarantees:

In the High Replication Datastore (HRD), data is replicated across multiple data centers using a system based on the Paxos algorithm. This provides the highest level of availability for reads and writes, at the cost of higher latency on writes due to the propagation of data. Most queries are eventually consistent.

The Master/Slave Datastore designates one data center to hold the master copy of all data. Data written to the master data center is replicated asynchronously to all other (slave) data centers. Since only one data center is the master for writing at any given time, this option offers strong consistency for all reads and queries, at the cost of periods of temporary unavailability during data center issues or planned downtime.

This page compares the two options and describes how to specify one of them for your application.

Data Storage Options Compared Selecting a Data Storage Option

Data Storage Options Compared

Watch a video comparing the High Replication and Master/Slave Datastores

The High Replication Datastore (HRD) is a highly available, highly reliable storage solution that remains available for reads and writes during routine maintenance and unplanned events, and is extremely resilient in the face of catastrophic failure. The Master/Slave Datastore, by contrast, is suitable only for a limited class of applications that

do not require high availability of data can tolerate spikes in Datastore latency

In the HRD, all strongly consistent operations (such as get by key or put) occur within a transaction. Entity groups are a unit of consistency as well as of transactionality: queries that require strongly consistent results must contain an ancestor filter, which restricts the results to a single entity group. Queries that span multiple entity groups are not guaranteed to return up-to-date results. For more information about using ancestor queries in this context, see Using the High Replication Datastore.

The following table summarizes the differences between the High-Resolution and Master/Slave Datastores:High Replication Master/Slave

PerformancePut/delete latency 1/2x–1x 1xGet latency 1x 1xQuery latency 1x 1xConsistencyPut/get/delete Strong StrongMost queries Eventual Strong

Page 49: Google App Engine

Ancestor queries Strong StrongOccasional planned read-only period

No YesUnplanned downtime

Extremely rare; no data loss.

Rare; possible to lose a small % of writes occurring near downtime (recoverable after event)

Selecting a Data Storage Option

The HRD is the default for all new applications. Existing applications must migrate to the HRD by copying entities to a new application. If you prefer to use the Master/Slave Datastore, you can select it as follows:For a new application:

1. On the Create an Application screen, click Edit.2. Select the Master/Slave radio button.3. Click Create Application.

Warning! This option is irreversible; if you choose the Master/Slave Datastore and later want to migrate to the HRD, you'll need to create a new application and copy your current Datastore to the new application.

For an existing application:

1. Create a new application using the steps above.2. Copy your current Datastore to the new application.

Note: Datastore copy is currently available only for Python applications. Java developers will need to perform some additional configuration steps. See A Note for Java Developers for more information.

Uso del almacén de datos de duplicación con alta disponibilidad

El almacén de datos de replicación con alta disponibilidad ofrece mayor disponibilidad para las operaciones de lectura y de escritura porque almacena datos de forma sincronizada en varios centros de datos. Se producen cambios en el servidor, pero no en el API. Utilizarás las mismas interfaces de programación independientemente del almacén de datos que emplees.

El almacén de datos de replicación con alta disponibilidad tiene un coste superior por la replicación adicional (consulta la página sobre facturación para conocer los precios). Debido a que este tipo de almacén es más caro, se recomienda principalmente para aquellos desarrolladores que se dispongan a crear aplicaciones App Engine clave que precisen de la máxima disponibilidad.

Sin embargo, en el almacén de datos de replicación con alta disponibilidad, las consultas que se realizan en grupos de entidades (en otras palabras, consultas que no sean de ancestros) pueden devolver resultados antiguos. Para devolver resultados de consistencia fuerte en el entorno del almacén de datos de replicación con alta disponibilidad, la consulta debe realizarse a un único grupo de entidades. Este tipo de consulta se denomina consulta de ancestro.

Las consultas de ancestro funcionan porque los grupos de entidades son una unidad de coherencia: las operaciones se aplican al grupo en su totalidad. Las consultas de ancestro no devuelven datos hasta que el grupo de entidades completo esté actualizado. Por lo tanto, los datos que devuelven las consultas de ancestro de los grupos de entidades son de consistencia fuerte.

Si tu aplicación se basa en resultados de consistencia fuerte para determinadas consultas, es posible que debas cambiar la forma en que esta almacena las entidades. En esta página se ofrecen recomendaciones para trabajar con datos guardados en el almacén de datos de replicación con alta disponibilidad. Veamos cómo funciona mediante las aplicaciones de libro de visitas de muestra para el almacén de datos principal/secundario y el almacén de datos de replicación con alta disponibilidad, respectivamente.

Almacén de datos principal/secundario

En el almacén de datos principal/secundario, la aplicación de aplicación de libro de visitas de muestra (disponible en /google/apphosting/demos/guestbook/src/guestbook/) crea una entidad raíz nueva para cada entrada del libro:         Entity greeting = new Entity("Greeting");        // No parent key specified, so the Entity is a root.        greeting.setProperty("user", user);

Page 50: Google App Engine

        greeting.setProperty("date", date);        greeting.setProperty("content", content);

A continuación, se realiza una consulta de las diez últimas entradas del libro de visitas:DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();    Query query = new Query("Greeting").addSort("date", Query.SortDirection.DESCENDING);    List<Entity> greetings = datastore.prepare(query).asList(FetchOptions.Builder.withLimit(10));

Este esquema es adecuado porque, de forma predeterminada, el almacén de datos principal/secundario devuelve resultados de consistencia fuerte para todas las consultas. Esto se debe a que, de forma predeterminada, el almacén de datos principal/secundario realiza las operaciones de lectura y de escritura únicamente desde la replicación principal.

Si pruebas a realizar esta consulta en el almacén de datos de replicación con alta disponibilidad, es posible que el centro de datos que ejecuta la consulta no detecte la aplicación Greeting nueva en el momento de la operación.

Almacén de datos de replicación con alta disponibilidad

En el almacén de datos de replicación con alta disponibilidad, la aplicación de libro de visitas de muestra utiliza una clave principal para el tipo Guestbook con guestbookName como nombre de clave y guarda las entradas subsiguientes del libro en el grupo de entidades que ha definido la clave principal.        String guestbookName = req.getParameter("guestbookName");        Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName);        String content = req.getParameter("content");        Date date = new Date();        // Places the greeting in the same entity group as the guestbook        Entity greeting = new Entity("Greeting", guestbookKey);        greeting.setProperty("user", user);        greeting.setProperty("date", date);        greeting.setProperty("content", content);

Las consultas de las entradas del libro utilizan la clave principal Guestbook para realizar una consulta de ancestro, la cual únicamente encontrará entradas Greetings añadidas al libro de visitas en cuestión:    DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();    Key guestbookKey = KeyFactory.createKey("Guestbook", guestbookName);    Query query = new Query("Greeting", guestbookKey).addSort("date", Query.SortDirection.DESCENDING);    query.setAncestor(guestbookKey);    List<Entity> greetings = datastore.prepare(query).asList(FetchOptions.Builder.withLimit(10));

Notas sobre el uso

El código de muestra del almacén de datos de replicación con alta disponibilidad anterior permite realizar operaciones de escritura en un único grupo de entidades para cada libro de visitas. Así pues, los resultados de las consultas que se realizan a un único libro de visitas son de consistencia fuerte. No obstante, la posibilidad de modificaciones en el libro de visitas se reduce a una operación de escritura por segundo (el límite para grupos de entidades). Por lo tanto, las escrituras a un único grupo de entidades por libro de visitas no es lo mejor para aquellas aplicaciones de uso intensivo. Si es probable que tu aplicación reciba un volumen considerable de operaciones de escritura, te recomendamos que utilices otro método. Por ejemplo, podrías incluir las publicaciones recientes en Memcache con una fecha de caducidad y mostrar una combinación de publicaciones recientes de Memcache y de publicaciones del almacén de datos.

Con la consistencia eventual, más del 99,9% de las operaciones de escritura están disponibles para recibir consultas al cabo de unos segundos. El objetivo es encontrar una solución de memoria caché para la aplicación que proporcione datos al usuario durante el tiempo que estés publicando en ella. Esta solución quizás sea un servicio Memcache, una memoria caché en una cookie, algún tipo de estado en la URL o un método completamente distinto. La cuestión es que si la solución proporciona datos al usuario en el contexto de sus publicaciones, probablemente sea suficiente para que la consistencia eventual del almacén de datos de replicación con alta disponibilidad sea totalmente aceptable. Recuerda que si realizas una operación get(), put() o ejecutas una transacción, verás los últimos datos que se hayan escrito.

Consultas de metadatos

Page 51: Google App Engine

Las consultas de metadatos permiten crear expresiones fácilmente que devuelvan metadatos del almacén de datos sobre espacios de nombre, tipos y propiedades. Las consultas devuelven los metadatos en entidades generadas de forma dinámica. Estos metadatos están actualizados según el momento de la consulta.

El uso más extendido de los metadatos es la implementación de funciones administrativas de servidor, entornos de metaprogramación, etc. Por ejemplo, podrías utilizar las consultas de metadatos para crear una Consola del administrador personalizada para tu aplicación.

También encontrarás metadatos sobre la aplicación en la pestaña de administración del almacén de datos de tu Consola del administrador. Sin embargo, los datos que se muestran en dicha pestaña presentan diferencias importantes con respecto a los datos que devuelven las consultas de metadatos:

Datos actualizados: las consultas de metadatos devuelven datos actualizados según el momento de la consulta. Los datos de la pestaña de administración del almacén de datos solo se actualizan una vez al día.

Datos exhaustivos: los datos de la pestaña de administración del almacén de datos son más exhaustivos que los datos que devuelven las consultas de metadatos.

Coste: las consultas de metadatos utilizan más cuota que las consultas normales.

En esta documentación se describen los siguientes aspectos de las consultas de metadatos:

Consultas de metadatos en Java Consultas de espacio de nombre Consultas de tipo Consultas de propiedades Consultas de representación de propiedades Utilización de cuota

Consultas de metadatos en Java

El paquete com.google.appengine.api.datastore.Query proporciona tres tipos especiales que se reservan en el almacén de datos para las consultas de metadatos. Estos tipos no entrarán en conflicto con los tipos personalizados del mismo nombre que ya existan en tu aplicación. Los tipos reservados para las consultas de metadatos son los siguientes:

__namespace__ __kind__ __property__

Si realizas consultas utilizando estos tipos, se devuelven entidades que contienen los metadatos solicitados. Estas entidades se generan dinámicamente en función del estado del almacén de datos en ese momento. Si bien puedes crear objetos com.google.appengine.api.datastore.Entity locales con tipos __namespace__, __kind__ o __property__, no podrás guardarlos en el almacén de datos.

La forma más fácil de acceder a estos tipos especiales es mediante el API del almacén de datos de nivel inferior. Por ejemplo, el código siguiente imprime todos los espacios de nombre de una aplicación: import com.google.appengine.api.datastore.DatastoreService;import com.google.appengine.api.datastore.Entity;import com.google.appengine.api.datastore.Query;

void printAllNamespaces(DatastoreService ds, PrintWriter writer) {    Query q = new Query(Query.NAMESPACE_METADATA_KIND);

    for (Entity e : ds.prepare(q).asIterable()) {        if (e.getKey().getId() != 0) {            writer.println("");        } else {            writer.println(e.getKey().getName());        }    }

Las consultas de metadatos admiten funciones de filtrado limitado por intervalos de nombres de espacios de nombre (__namespace__), intervalos de nombres de tipo (__kind__ y __property__) e intervalos de pares de nombres de tipo o de propiedad (__property__).

Page 52: Google App Engine

Consultas de espacio de nombre

Si tu aplicación utiliza el API de espacios de nombre, puedes usar una consulta de metadatos para buscar todos los espacios de nombre que aparecen en los registros de la entidad. De este modo, podrás realizar actividades entre espacios de nombre con mayor facilidad como, por ejemplo, funciones administrativas.

Las consultas de espacio de nombre devuelven entidades cuya clave es el nombre del espacio de nombre, excepto en el caso del espacio de nombre vacío, el cual tiene una clave con un ID numérico 1 (la cadena vacía no es un nombre de clave válido). Estas entidades no tienen propiedades. Así, pues, las consultas que solo incluyen claves y las que no devuelven la misma información. Las consultas de espacio de nombre solo permiten filtrar por intervalos de __key__.

La sencilla función que se muestra a continuación devuelve una lista de espacios de nombre de una aplicación situados entre dos nombres opcionales: import com.google.appengine.api.datastore.DatastoreService;import com.google.appengine.api.datastore.Entity;import com.google.appengine.api.datastore.Key;import com.google.appengine.api.datastore.KeyFactory;import com.google.appengine.api.datastore.Query;

import java.util.ArrayList;import java.util.List;

Key makeNamespaceKey(String namespace) {    return KeyFactory.createKey(Query.NAMESPACE_METADATA_KIND, namespace);}

List<String> getNamespaces(DatastoreService ds, StringstartNamespace, String endNamespace) {    Query q = new Query(Query.NAMESPACE_METADATA_KIND);    // Limit query to specified namespaces    if (startNamespace != null) {        q.addFilter(Entity.KEY_RESERVED_PROPERTY,                    Query.FilterOperator.GREATER_THAN_OR_EQUAL,                    makeNamespaceKey(startNamespace));    }    if (endNamespace != null) {        q.addFilter(Entity.KEY_RESERVED_PROPERTY,                    Query.FilterOperator.LESS_THAN_OR_EQUAL,                    makeNamespaceKey(endNamespace));    }    // Build namespace list    List<String> results = new ArrayList<String>();    for (Entity e : ds.prepare(q).asIterable()) {        // A non-zero numeric id is used for the default namespace        if (e.getKey().getId() != 0) {             results.add("");        } else {            results.add(e.getKey().getName());        }    }    return results;}

Consultas de tipo

Al igual que ocurre con las consultas de espacio de nombre, las consultas __kind__ devuelven entidades cuya clave es el nombre del tipo, sin propiedades (por lo tanto, las consultas que solo incluyen claves y las que no devuelven la misma información). Del mismo modo, las consultas de tipo solo permiten filtrar por intervalos de __key__. Las consultas de tipo se limitan automáticamente al espacio de nombre existente.

A modo de ejemplo, el código siguiente imprime los tipos cuyo nombre empieza con una letra minúscula: import com.google.appengine.api.datastore.DatastoreService;import com.google.appengine.api.datastore.Entity;import com.google.appengine.api.datastore.Key;

Page 53: Google App Engine

import com.google.appengine.api.datastore.KeyFactory;import com.google.appengine.api.datastore.Query;

import java.io.PrintWriter;

Key makeKindKey(String kind) {    return KeyFactory.createKey(Query.KIND_METADATA_KIND, kind);}

void printSomeKinds(PrintWriter writer, DatastoreService ds) {    Query q = new Query(Query.KIND_METADATA_KIND);    q.addFilter(Entity.KEY_RESERVED_PROPERTY,                Query.FilterOperator.GREATER_THAN_OR_EQUAL,                makeKindKey("a"));    q.addFilter(Entity.KEY_RESERVED_PROPERTY,                Query.FilterOperator.LESS_THAN,                makeKindKey("{"));  // { is character after z    PrintWriter writer = response.getWriter();    writer.println("lower-case kinds:");    for (Entity e : ds.prepare(q).asIterable()) {        writer.println("  " + e.getKey().getName());    }}

Consultas de propiedad

Las consultas de propiedad te permiten devolver todas las propiedades asociadas a un tipo, pero son más complejas que las consultas __namespace__ y __kind__. En primer lugar, describiremos las consultas __property__ más sencillas, que solo contienen claves:

Estas consultas devuelven una clave por cada propiedad P del tipo K, del modo siguiente: o La clave tiene el tipo __property__ y el nombre P. o La clave tiene una clave principal con el tipo __kind__ y el nombre K.

Las propiedades no indexadas no se devuelven en las consultas __property__. Las consultas __property__ admiten consultas de ancestro, donde el ancestro es una clave __kind__ o __property__,

que limita la consulta a un único tipo o propiedad. Las consultas __property__ admite filtros __key__, donde las claves son claves __kind__ o __property__. Las consultas __property__ están implícitamente limitadas al espacio de nombre vigente.

El tipo __key__ filtra por pares tipo-propiedad. Por ejemplo, si tienes:

el tipo A con las propiedades m, q, z, el tipo B con las propiedades c, d, el tipo C con las propiedades a, b.

Entonces, esta consulta: import com.google.appengine.api.datastore.DatastoreService;import com.google.appengine.api.datastore.Entity;import com.google.appengine.api.datastore.Key;import com.google.appengine.api.datastore.KeyFactory;import com.google.appengine.api.datastore.Query;

import java.io.PrintWriter;

Key makePropertyKey(String kind, String property) {    return KeyFactory.createKey(makeKindKey(kind),                                Query.PROPERTY_METADATA_KIND, property);}

void printPropertyRange(PrintWriter writer, DatastoreService ds) {    q = new Query(Query.PROPERTY_METADATA_KIND);    q.addFilter(Entity.KEY_RESERVED_PROPERTY,                Query.FilterOperator.GREATER_THAN_OR_EQUAL,

Page 54: Google App Engine

                makePropertyKey("A", "p"));    q.addFilter(Entity.KEY_RESERVED_PROPERTY,                Query.FilterOperator.LESS_THAN_OR_EQUAL,                makePropertyKey("C", "a"));    for (Entity e : ds.prepare(q).asIterable()) {        writer.println(e.getKey().getParent().getName() + ": " +            e.getKey().getName());    }}

imprime lo siguiente:   A: z  B: c  B: d  C: a

No puedes realizar una sola consulta que devuelva todas las propiedades cuyo nombre esté entre S1 y S2 en algún tipo.

El uso de consultas de ancestro facilita la obtención de todas las propiedades de un tipo concreto, como se muestra en la función siguiente: import com.google.appengine.api.datastore.DatastoreService;import com.google.appengine.api.datastore.Entity;import com.google.appengine.api.datastore.Query;

import java.util.ArrayList;import java.util.List;

List propertiesOfKind(DatastoreService ds, String kind) {    Query q = new Query(Query.PROPERTY_METADATA_KIND);    q.setAncestor(makeKindKey(kind));    ArrayList results = new ArrayList();    for (Entity e : ds.prepare(q).asIterable()) {        results.add(e.getKey().getName());    }    return results; }

Consultas de representación de propiedades

Una consulta __property__ que no contenga solo claves devuelve información adicional sobre las representaciones que utiliza cada par de tipo-propiedades. Las representaciones no son clases de propiedades: varias clases de propiedades se asignan a la misma representación (p. ej., com.google.appengine.api.datastore.Blob y java.lang.String utilizan la representación "STRING"). En la tabla siguiente se indican las asignaciones de las clases Property a las representaciones:

java.lang.String STRINGjava.lang.Byte INT64java.lang.Short INT64java.lang.Integer INT64java.lang.Long INT64java.lang.Float DOUBLEjava.lang.Double DOUBLEjava.lang.Boolean BOOLEANcom.google.appengine.api.users.User USERcom.google.appengine.api.datastore.Key REFERENCEcom.google.appengine.api.datastore.Blob STRINGcom.google.appengine.api.datastore.Text STRINGjava.util.Date INT64com.google.appengine.api.datastore.Link STRING

Page 55: Google App Engine

com.google.appengine.api.datastore.ShortBlob STRINGcom.google.appengine.api.datastore.GeoPt POINTcom.google.appengine.api.datastore.Category STRINGcom.google.appengine.api.datastore.Rating STRINGcom.google.appengine.api.datastore.PhoneNumber STRINGcom.google.appengine.api.datastore.PostalAddress STRINGcom.google.appengine.api.datastore.Email STRINGcom.google.appengine.api.datastore.IMHandle STRINGcom.google.appengine.api.blobstore.BlobKey STRINGjava.util.Collection representación de T

Las entidades que devuelve una consulta __property__ que no contiene solo claves para la propiedad P de tipo K tienen:

la misma clave que la consulta que solo incluye claves, una propiedad property_representation que es java.util.Collection<String>, que contiene una

representación String para dicha propiedad en una entidad del tipo.

La sencilla función que se muestra a continuación busca todas las representaciones de una propiedad concreta en un tipo:import com.google.appengine.api.datastore.DatastoreService;import com.google.appengine.api.datastore.Entity;import com.google.appengine.api.datastore.Query;

import java.util.Collection;

Collection representationsOf(DatastoreService ds, String kind, String property) {    Query q = new Query(Query.PROPERTY_METADATA_KIND);    q.setAncestor(makePropertyKey(kind, property));    Entity propInfo = ds.prepare(q).asSingleEntity();    return (Collection) propInfo.getProperty("property_representation");}

Utilización de cuota

Las consultas de metadatos utilizan más cuota que las consultas normales. Como normal general, una consulta de metadatos que devuelve N entidades tendrá aproximadamente el mismo coste que N consultas que devuelvan una entidad cada una de ellas. Además, las consultas de representación de propiedades (consultas __property__ no solo basadas en claves) resultan más costosas que las consultas __property__ solo basadas en claves. En general, las consultas de metadatos son más lentas que las normales.

Estadísticas del almacén de datos de Java

El almacén de datos contiene estadísticas sobre los datos almacenados de una aplicación como, por ejemplo, la cantidad de entidades de un determinado tipo o el espacio que utilizan los valores de propiedad de un tipo en concreto. Encontrarás estas estadísticas en "Almacén de datos" > "Estadísticas" de la Consola del administrador.

También puedes acceder a estos valores mediante un programa desde la aplicación. Para ello, basta con realizar una consulta de entidades específicas mediante el API del almacén de datos. A cada una de las estadísticas se accede como entidad cuyo nombre de tipo empieza y termina con dos caracteres de subrayado. Por ejemplo, cada aplicación incluye exactamente una entidad del tipo __Stat_Total__ que representa estadísticas de todas las entidades del almacén de datos. Las entidades con estadísticas incluyen las propiedades siguientes:

count, el número de elementos que determina la estadística (número entero largo), bytes, el tamaño total de los elementos de esta estadística (número entero largo), timestamp, la última hora en que se ha modificado la estadística (valor de fecha-hora).

Algunos tipos de estadísticas incluyen también propiedades adicionales, las cuales se enumeran a continuación.

Una aplicación Java puede acceder a las entidades con estadísticas mediante el API del almacén de datos de nivel inferior. Por ejemplo:

Page 56: Google App Engine

import com.google.appengine.api.datastore.DatastoreService;import com.google.appengine.api.datastore.DatastoreServiceFactory;import com.google.appengine.api.datastore.Entity;import com.google.appengine.api.datastore.Query;

// ...DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();Entity globalStat = datastore.prepare(new Query("__Stat_Total__")).asSingleEntity();Long totalBytes = (Long) globalStat.getProperty("bytes");Long totalEntities = (Long) globalStat.getProperty("count");

Cuando el sistema de estadísticas crea nuevas entidades con estadísticas, no elimina las antiguas en seguida. La mejor forma de obtener una visión global de las estadísticas es realizar una consulta de la entidad __Stat_Total__ con la marca de tiempo (timestamp) más reciente y, a continuación, utilizar el valor de esta como filtro a la hora de extraer otras entidades con estadísticas.

Las entidades con estadísticas se incluyen en los valores de estadísticas que se calculan. Estas entidades ocupan espacio relativo al número de tipos y de nombres de propiedad que utiliza la aplicación.

A continuación, se incluye la lista completa de estadísticas disponibles:Estadística Tipo de entidad con estadística Descripción

Todas las entidades __Stat_Total__ Todas las entidades

Entidades de un tipo __Stat_Kind__

Entidades de un tipo; una entidad con estadísticas para cada tipo de entidad almacenado Propiedades adicionales:

kind_name, el nombre del tipo representado (cadena)

Entidades raíz de un tipo

__Stat_Kind_IsRootEntity__

Entidades de un tipo que son entidades raíz del grupo de entidades (sin entidad principal de ancestro); una entidad con estadísticas para cada tipo de entidad almacenado Propiedades adicionales:

kind_name, el nombre del tipo representado (cadena)

Entidades de un tipo sin raíz

__Stat_Kind_NotRootEntity__

Entidades de un tipo que no son entidades raíz del grupo de entidades (con entidad principal de ancestro); una entidad con estadísticas para cada tipo de entidad almacenado Propiedades adicionales:

kind_name, el nombre del tipo representado (cadena)

Propiedades de un tipo __Stat_PropertyType__

Propiedades de un tipo de valor en todas las entidades; una entidad con estadísticas por tipo de valor Propiedades adicionales:

property_type, el nombre del tipo de valor (cadena)

Propiedades de un tipo por tipo

__Stat_PropertyType_Kind__

Propiedades de un tipo de valor en entidades del tipo especificado; una entidad con estadísticas por cada combinación de tipo de propiedad y de tipo Propiedades adicionales:

property_type, el nombre del tipo de valor (cadena)

kind_name, el nombre del tipo representado (cadena)

Propiedades con un nombre

__Stat_PropertyName_Kind__ Propiedades con un determinado nombre en entidades de un tipo específico; una entidad con estadísticas por combinación de nombre de propiedad y tipo exclusivos Propiedades adicionales:

Page 57: Google App Engine

property_name, el nombre de la propiedad (cadena)

kind_name, el nombre del tipo representado (cadena)

Propiedades de un tipo y con un nombre

__Stat_PropertyType_PropertyName_Kind__

Propiedades con un determinado nombre y de un tipo de valor específico en entidades de un tipo determinado; una entidad con estadísticas por combinación de nombre de propiedad, tipo de valor y tipo incluido en el almacén de datos Propiedades adicionales:

property_type, el nombre del tipo de valor (cadena)

property_name, el nombre de la propiedad (cadena)

kind_name, el nombre del tipo representado (cadena)

Algunas estadísticas hacen referencia a los tipos de valor de propiedad del almacén de datos por nombre, en forma de cadenas. Estos son los nombres:

"Blob" "Boolean" "ByteString" "Category" "Date/Time" "Email" "Float" "GeoPt" "Integer" "Key" "Link" "NULL" "PhoneNumber" "PostalAddress" "Rating" "String" "Text" "User"

API de servicio asíncrono del almacén de datos

El API de servicio asíncrono del almacén de datos permite realizar varias invocaciones a la vez, sin que se bloqueen entre sí, al almacén de datos y obtener los resultados de dichas invocaciones más adelante, cuando se procese la solicitud. En este documento se describen los siguientes aspectos del API de servicio asíncrono del almacén de datos:

Uso del servicio asíncrono del almacén de datos Uso de transacciones asíncronas Uso del método Future Cuándo realizar invocaciones del almacén de datos usando el servicio asíncrono Consultas asíncronas

Uso del servicio asíncrono del almacén de datos

A través del API de servicio asíncrono del almacén de datos, puedes realizar invocaciones al almacén de datos usando los métodos de la interfaz de AsyncDatastoreService. Este objeto se obtiene invocando el método getAsyncDatastoreService() de la clase DatastoreServiceFactory. import com.google.appengine.api.datastore.AsyncDatastoreService;import com.google.appengine.api.datastore.DatastoreServiceFactory;

Page 58: Google App Engine

// ...AsyncDatastoreService datastore = DatastoreServiceFactory.getAsyncDatastoreService();

AsyncDatastoreService admite las mismas operaciones que DatastoreService, con la excepción de que la mayoría de los métodos devuelve de forma inmediata un método Future cuyo resultado puedes bloquear posteriormente. Por ejemplo, DatastoreService.get() devuelve Entity, pero AsyncDatastoreService.get() devuelve Future<Entity>. // ...

Key key = KeyFactory.createKey("Employee", "Max");// Async call returns immediatelyFuture<Entity> entityFuture = datastore.get(key);

// Do other stuff while the get operation runs in the background...

// Blocks if the get operation has not finished, otherwise returns instantlyEntity entity = entityFuture.get();

Si tienes un servicio AsyncDatastoreService pero debes ejecutar una operación de forma sincronizada, invoca el método AsyncDatastoreService correspondiente y, acto seguido, bloquea el resultado: // ...

Entity entity = new Employee("Employee", "Alfred");// ... populate entity properties

// Make a sync call via the async interfaceKey key = datastore.put(key).get();

Uso de transacciones asíncronas

Las invocaciones del API de servicio asíncrono del almacén de datos pueden participar en las transacciones, al igual que las invocaciones sincronizadas. A continuación, te indicamos una función que sirve para ajustar el salario de un empleado (Employee) y escribe una entidad SalaryAdjustment adicional en el mismo grupo de entidades que Employee; todo en una sola transacción. void giveRaise(AsyncDatastoreService datastore, Key employeeKey, long raiseAmount)        throws Exception {    Future<Transaction> txn = datastore.beginTransaction();

    // Async call to lookup the Employee entity    Future<Entity> employeeEntityFuture = datastore.get(employeeKey);

    // Create and put a SalaryAdjustment entity in parallel with the lookup    Entity adjustmentEntity = new Entity("SalaryAdjustment", employeeKey);    adjustmentEntity.setProperty("adjustment", raiseAmount);    adjustmentEntity.setProperty("adjustmentDate", new Date());    Future<Key> = datastore.put(adjustmentEntity);

    // Fetch the result of our lookup to make the salary adjustment    Entity employeeEntity = employeeEntityFuture.get();    long salary = (Long) employeeEntity.getProperty("salary");    employeeEntity.setProperty("salary", salary + raiseAmount);

    // Re-put the Employee entity with the adjusted salary.    datastore.put(employeeEntity);    txn.commit(); // could also call txn.commitAsync() here}

En este ejemplo se muestra una diferencia importante entre las invocaciones asíncronas sin transacciones y aquellas con transacciones. Cuando no se utilice una transacción, la única forma de asegurarse de que una invocación asíncrona determinada se haya realizado es extraer el valor del método Future que se devolvió al realizar la invocación. Pero si utilizas una transacción, al invocar Transaction.commit(), se bloquea el resultado de todas las invocaciones asíncronas realizadas, ya que la transacción empezó antes de ejecutarse.

Por lo tanto, en el ejemplo anterior, aunque es posible que la invocación asíncrona para incluir la entidad SalaryAdjustment aún quede pendiente cuando invoques txn.commit(), esta última invocación se realizará una vez que dicha entidad se haya incluido.

Page 59: Google App Engine

Asimismo, si decides invocar txn.commitAsync() en lugar de txn.commit() invocando get() con la cadena Future que devuelve commitAsync(), se obtendrán resultados cuando todas las invocaciones asíncronas hayan finalizado.

Nota: las transacciones van asociadas a un subproceso determinado, no a una instancia concreta de DatastoreService o de AsyncDatastoreService. Esto significa que si inicias una transacción con DatastoreService y, además, realizas una invocación asíncrona con AsyncDatastoreService, esta invocación forma parte de la transacción. O, dicho de modo más resumido, DatastoreService.getCurrentTransaction() y AsyncDatastoreService.getCurrentTransaction() siempre devuelven la misma transacción (Transaction).

Uso del método Future

En la referencia de Java sobre Future se explica casi todo lo que debes saber para usar el método Future que devuelve el API de servicio asíncrono del almacén de datos. Aun así, también debes tener en cuenta los siguientes aspectos relacionados con App Engine:

Cuando invoques Future.get(long timeout, TimeUnit unit), el tiempo de espera es independiente de cualquier tiempo límite que se haya especificado para las invocaciones a procedimientos remotos al crear AsyncDatastoreService. Para obtener más información, consulta la documentación sobre tiempos límite para las invocaciones a procedimientos remotos del almacén de datos.

Cuando invoques Future.cancel(boolean mayInterruptIfRunning) y obtengas true como resultado, el estado de tu almacén de datos no tiene por qué seguir siendo el mismo. Dicho de otro modo, la cancelación de Future no es lo mismo que la cancelación de una transacción.

Cuándo realizar invocaciones del almacén de datos usando el servicio asíncrono

Las operaciones de la interfaz de DatastoreService son sincronizadas. Por ejemplo, al invocar DatastoreService.get(), el código se bloquea hasta que la invocación del almacén de datos finaliza. Si lo único que necesita hacer tu aplicación es mostrar el resultado de get() en HTML, es lógico que se produzca dicho bloqueo. Si, por el contrario, tu aplicación necesita los resultados de get() y de Query para mostrar la respuesta y, además, get() y Query no tienen datos asociados, entonces la espera hasta que get() finalice para iniciar Query no sería necesaria. A continuación, incluimos un ejemplo con código que puede mejorarse mediante el API de servicio asíncrono: DatastoreService datastore = DatastoreServiceFactory.getDatastoreService();Key empKey = KeyFactory.createKey("Employee", "Max");

// Read employee data from the DatastoreEntity employee = datastore.get(empKey); // Blocking for no good reason!

// Fetch payment historyQuery query = new Query("PaymentHistory");PreparedQuery pq = datastore.prepare(query);List<Entity> result = pq.asList(FetchOptions.Builder.withLimit(10));renderHtml(employee, result);

En lugar de esperar a que get() se complete, utiliza una instancia de AsyncDatastoreService para realizar la invocación de forma asíncrona: AsyncDatastoreService datastore = DatastoreServiceFactory.getAsyncDatastoreService();Key empKey = KeyFactory.createKey("Employee", "Max");

// Read employee data from the DatastoreFuture<Entity> employeeFuture = datastore.get(empKey); // Returns immediately!

// Fetch payment history for the employeeQuery query = new Query("PaymentHistory", empKey);PreparedQuery pq = datastore.prepare(query);

// Run the query while the employee is being fetchedList<Entity> result = pq.asList(FetchOptions.Builder.withLimit(10));Entity employee = employeeFuture.get(); // Blocking!renderHtml(employee, result);

Las versiones sincronizadas y asíncronas de este código usan una cantidad de CPU similar (después de todo, ambas llevan a cabo la misma cantidad de trabajo). Sin embargo, dado que la versión asíncrona permite que se ejecuten las dos operaciones del almacén de datos

Page 60: Google App Engine

al mismo tiempo, la versión asíncrona tiene una latencia menor. Por lo general, si debes realizar varias operaciones en el almacén de datos que no estén asociadas a ningún dato, el servicio AsyncDatastoreService puede aumentar la latencia significativamente.

Consultas asíncronas

Actualmente no disponemos de un API de servicio asíncrono específica para las consultas. Sin embargo, cuando invoquesPreparedQuery.asIterable() o PreparedQuery.asIterator(), tanto DatastoreService como AsyncDatastoreService iniciarán la consulta en segundo plano con una invocación asíncrona y se obtendrán resultados inmediatamente. De este modo, la aplicación puede trabajar en paralelo mientras se extrae el primer conjunto de resultados de la consulta. // ...

Query q1 = new Query("Salesperson");q1.addFilter("dateOfHire", FilterOperator.LESS_THAN, oneMonthAgo);

// Returns instantly, query is executing in the background.Iterable<Entity> recentHires = datastore.prepare(q1).asIterable();

Query q2 = new Query("Customer");q2.addFilter("lastContact", FilterOperator.GREATER_THAN, oneYearAgo);

// Also returns instantly, query is executing in the background.Iterable<Entity> needsFollowup = datastore.prepare(q2).asIterable();

schedulePhoneCall(recentHires, needsFollowUp);

Lamentablemente, PreparedQuery.asList() no funciona de la misma manera. Esperamos poder solucionar esto en una próxima versión.

Uso de JDO

En esta sección se describe la implementación de objetos de datos Java (JDO, Java Data Objects) en App Engine. Presenta la siguiente estructura:

Aspectos generales Definición de clases de datos con JDO Creación, obtención y eliminación de datos en JDO Relaciones de entidad en JDO Consultas en JDO

Uso de JDO con App Engine

La interfaz de objetos de datos de Java (JDO) es una interfaz estándar para almacenar objetos con datos en una base de datos. El estándar permite que las interfaces realicen tareas de anotación de objetos Java, de recuperación de objetos con consultas y de interacción con una base de datos mediante transacciones. Una aplicación que utilice la interfaz de JDO puede funcionar con distintos tipos de bases de datos sin usar ningún código específico para estas, incluidas las bases de datos relacionales, jerárquicas y de objetos. Al igual que otros estándares de interfaz, JDO simplifica la transferencia de tu aplicación entre diferentes soluciones de almacenamiento.

El SDK de Java de App Engine incluye JDO 2.3 para el almacén de datos de App Engine. Esta implementación se basa en DataNucleus Access Platform, la implementación de referencia de código abierto para JDO 2.3.

Consulta la documentación de Access Platform 1.1 para obtener más información sobre JDO. En concreto, consulta las secciones "JDO Mapping" (Asignación de JDO) y "JDO API" (API de JDO).

Configuración de JDO Mejora de las clases de datos Obtención de una instancia de PersistenceManager Funciones no disponibles en JDO Inhabilitación de transacciones y transferencia de aplicaciones de JDO existentes

Configuración de JDO

Page 61: Google App Engine

Si quieres usar JDO para acceder al almacén de datos, necesitas lo siguiente para la aplicación de App Engine:

Los archivos de JDO y del plug-in DataNucleus de App Engine deben ubicarse en el directorio war/WEB-INF/lib/ de la aplicación.

Debe haber un archivo de configuración llamado jdoconfig.xml en el directorio war/WEB-INF/classes/META-INF/ de la aplicación cuya configuración solicite a JDO el uso del almacén de datos de App Engine.

El proceso de creación del proyecto requiere una mejora posterior a la compilación en las clases de datos compilados para asociarlas con la implementación JDO.

Si utilizas Google Plugin for Eclipse, el plug-in realiza estos tres pasos por ti. El asistente de nuevos proyectos coloca los archivos JAR de JDO y del plug-in DataNucleus de App Engine en la ubicación correcta y, a continuación, crea el archivo jdoconfig.xml. Durante el proceso de creación se realiza la mejora de forma automática.

Si utilizas Apache Ant para crear tu proyecto, puedes utilizar una tarea de Ant incluida con el SDK para realizar la mejora. Debes copiar los archivos JAR y crear el archivo de configuración cuando configures tu proyecto. Consulta la sección Uso de Apache Ant para obtener más información sobre dicha tarea.

Copia de los archivos JAR

Los archivos JAR de JDO y del almacén de datos se incluyen en el SDK de Java de App Engine; los encontrarás en el directorio appengine-java-sdk/lib/user/orm/.

Copia los archivos JAR en el directorio war/WEB-INF/lib/ de tu aplicación.

Asegúrate de que appengine-api.jar también se encuentre en el directorio war/WEB-INF/lib/ (es posible que ya lo hayas copiado al crear el proyecto). El plug-in DataNucleus de App Engine utiliza este archivo JAR para acceder al almacén de datos.

Creación del archivo jdoconfig.xml.

La interfaz de JDO requiere un archivo de configuración llamado jdoconfig.xml en el directorio war/WEB-INF/classes/META-INF/ de la aplicación. Puedes crear directamente este archivo en dicha ubicación o hacer que se copie desde un directorio determinado durante el proceso de creación.

Debes crear el archivo con el siguiente contenido:<?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>

Configuración de la política de lectura y el tiempo límite de la llamada al almacén de datos

Tal como se describe en los aspectos generales, puedes configurar la política de lectura (consistencia fuerte o consistencia eventual) y el tiempo límite para la invocación al almacén de datos para PersistenceManagerFactory en el archivo jdoconfig.xml. Esta configuración se incluye en el elemento <persistence-manager-factory>. Todas las invocaciones realizadas con una instancia de PersistenceManager determinada usan la configuración especificada cuando PersistenceManagerFactory creó el gestor. También puedes saltarte estas opciones cuando se trate de un objeto Query único.

Page 62: Google App Engine

Para establecer una política de lectura para PersistenceManagerFactory, incluye una propiedad denominada datanucleus.appengine.datastoreReadConsistency. Esta admite los valores EVENTUAL (para lecturas con consistencia eventual) y STRONG (para lecturas con consistencia fuerte). Si no se especifica, el valor predeterminado es STRONG.        <property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />

Puedes definir distintos tiempos límite en las invocaciones del almacén de datos tanto para procesos de lectura como de escritura. Para procesos de lectura, usa la propiedad estándar de JDO javax.jdo.option.DatastoreReadTimeoutMillis; y para procesos de escritura, usa javax.jdo.option.DatastoreWriteTimeoutMillis. El valor es una cantidad de tiempo expresada en milisegundos.        <property name="javax.jdo.option.DatastoreReadTimeoutMillis" value="5000" />        <property name="javax.jdo.option.DatastoreWriteTimeoutMillis" value="10000" />

Puedes tener varios elementos <persistence-manager-factory> en el mismo archivo jdoconfig.xml, con distintos atributos name, para usar instancias de PersistenceManager con diferentes configuraciones en la misma aplicación. Por ejemplo, el siguiente archivo jdoconfig.xml establece dos conjuntos de configuración; uno se denomina "transactions-optional", y el otro "eventual-reads-short-deadlines":<?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>

    <persistence-manager-factory name="eventual-reads-short-deadlines">        <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"/>

        <property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />        <property name="javax.jdo.option.DatastoreReadTimeoutMillis" value="5000" />        <property name="javax.jdo.option.DatastoreWriteTimeoutMillis" value="10000" />    </persistence-manager-factory></jdoconfig>

Consulta la sección Obtención de una instancia de PersistenceManager a continuación para obtener información de cómo crear PersistenceManager seleccionando el nombre de la configuración deseada.

Mejora de las clases de datos

JDO realiza una mejora tras la compilación durante el proceso de creación para asociar las clases de datos a la implementación de JDO.

Si utilizas Eclipse, Google Plugin for Eclipse lleva a cabo esta operación de forma automática durante la creación.

Si usas Apache Ant, el SDK incluye una tarea de Ant para realizar este paso. Consulta la sección Uso de Apache Ant para obtener más información sobre dicha tarea.

Puedes realizar la operación de mejora en clases compiladas desde la línea de comandos con el siguiente comando:java -cp classpath com.google.appengine.tools.enhancer.Enhance class-files

Page 63: Google App Engine

classpath debe contener el archivo JAR appengine-tools-api.jar del directorio appengine-java-sdk/lib/, así como todas tus clases de datos.

Para obtener más información sobre el programa de mejora en bytecode DataNucleus, consultala documentación de DataNucleus.

Obtención de una instancia de PersistenceManager

Una aplicación interactúa con JDO utilizando una instancia de la clase PersistenceManager. Esta instancia se obtiene al reproducir e invocar un método en una instancia de la clase PersistenceManagerFactory. La fábrica utiliza la configuración de JDO para crear instancias de PersistenceManager.

Dado que la instancia de PersistenceManagerFactory tarda un tiempo en iniciarse, las aplicaciones deberían reutilizar siempre la misma instancia. Para garantizar que esto suceda, se genera una excepción cuando la aplicación crea más de una instancia de PersistenceManagerFactory (con el mismo nombre de configuración). Una forma sencilla de gestionar la instancia de PersistenceManagerFactory es crear una clase envoltorio única con una instancia estática. Por ejemplo:

PMF.javaimport 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;    }}

Consejo: "transactions-optional" hace referencia al nombre de la configuración en el archivo jdoconfig.xml. Si tu aplicación usa varias configuraciones, debes ampliar este código para invocar JDOHelper.getPersistenceManagerFactory() como quieras. El código debería incluir en la memoria caché una instancia única de cada PersistenceManagerFactory.

La aplicación utiliza la instancia de la fábrica para crear una instancia de PersistenceManager por cada solicitud que acceda al almacén de datos.import javax.jdo.JDOHelper;import javax.jdo.PersistenceManager;import javax.jdo.PersistenceManagerFactory;

import PMF;

// ...    PersistenceManager pm = PMF.get().getPersistenceManager();

PersistenceManager se usa para almacenar, actualizar y eliminar objetos de datos, así como para realizar consultas al almacén de datos.

Cuando acabes con la instancia de PersistenceManager, debes invocar su método close(). Sería un error usar la instancia de PersistenceManager después de invocar su método close().    try {        // ... do stuff with pm ...    } finally {        pm.close();    }

Funciones no disponibles en JDO

Las siguientes funciones de la interfaz de JDO no se encuentran disponibles en la implementación de App Engine:

Page 64: Google App Engine

Relaciones sin propiedad. Puedes implementar relaciones sin propiedad mediante valores Key explícitos. Es posible que la sintaxis de JDO para relaciones sin propiedad se incluya en próximas versiones.

Relaciones de propiedad multidireccionales. Consultas "Join". No puedes usar el campo de una entidad secundaria en un filtro al realizar una consulta del tipo principal. Ten

en cuenta que puedes probar el campo de relación del elemento principal directamente en las consultas mediante una clave. Consultas de agrupación JDOQL y otras consultas de agrupación conjunta. Consultas polimórficas. No puedes realizar consultas de una clase para obtener instancias de una subclase. Cada clase se

representa mediante un tipo de entidad independiente en el almacén de datos. IdentityType.DATASTORE para la anotación @PersistenceCapable. Solo se admite

IdentityType.APPLICATION. Actualmente, existe un error que evita que haya relaciones de propiedad de uno a varios cuando el elemento principal y el

secundario pertenecen a la misma clase, lo cual dificulta el modelo de las estructuras en árbol. Esto se solucionará en futuras versiones. Como solución provisional, puedes almacenar los valores de Key explícitos para el elemento principal o para el secundario.

Inhabilitación de transacciones y transferencia de aplicaciones de JDO existentes

La configuración de JDO que recomendamos define una propiedad llamada datanucleus.appengine.autoCreateDatastoreTxns con el valor true. Se trata de una propiedad específica de App Engine que ordena a la implementación de JDO que asocie las transacciones del almacén de datos a las transacciones de JDO que se administran en el código de la aplicación. Si creas una nueva aplicación desde el principio, probablemente sea esto lo que buscas. Sin embargo, si tienes una aplicación basada en JDO que quieras ejecutar en App Engine, te recomendamos que uses una configuración de persistencia alternativa que asigne el valor false a esta propiedad:<?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="false"/>    </persistence-manager-factory></jdoconfig>

Si quieres saber por qué esta opción puede resultarte útil, recuerda que solo puedes realizar operaciones sobre objetos que pertenezcan al mismo grupo de entidades dentro de una transacción. Las aplicaciones creadas usando bases de datos tradicionales normalmente requieren el uso de transacciones globales, lo cual te permite actualizar cualquier conjunto de datos dentro de una transacción. Dado que no se puede realizar transacciones globales en el almacén de datos de App Engine, el programa genera excepciones cada vez que el código requiera transacciones globales. En lugar de acceder a la base de código (que puede ser enorme) y eliminar todo el código relativo a la administración de transacciones, simplemente puedes inhabilitar las transacciones del almacén de datos. Con esta solución, el código seguirá gestionando la atomicidad de las modificaciones multiregistro del mismo modo. No obstante, la aplicación seguirá funcionando para que puedas centrarte en la refactorización gradual del código de transacciones según sea necesario en lugar de hacerlo todo a la vez.

Definición de clases de datos con JDO

Puedes usar JDO para almacenar objetos de datos simples de Java (a veces denominados Plain Old Java Objects o POJO) en el almacén de datos. Cada objeto que se haga persistente con PersistenceManager se convertirá en una entidad del almacén de datos. Debes usar anotaciones para informar a JDO cómo almacenar y recrear instancias de tus clases de datos.

Nota: versiones anteriores de JDO usan archivos XML .jdo en lugar de anotaciones de Java. Estas aún funcionan con JDO 2.3. En este documento solo se tratan las anotaciones de Java con las clases de datos.

Anotaciones de clase y de campo Tipos de valor principales Objetos serializables Objetos secundarios y relaciones

Page 65: Google App Engine

Clases insertadas Colecciones Campos de objeto y propiedades de entidad Herencias

Anotaciones de clase y de campo

Cada objeto que guarda JDO se convierte en una entidad del almacén de datos de App Engine. El tipo de la entidad se obtiene a partir del nombre simple de la clase (las clases internas usan la ruta $ sin el nombre del paquete). Cada campo persistente de la clase representa una propiedad de la entidad, con un nombre de propiedad idéntico al nombre del campo (con distinción de mayúsculas y minúsculas).

Para declarar una clase Java como almacenable y recuperable en el almacén de datos con JDO, asigna una anotación @PersistenceCapable a la clase. Por ejemplo:import javax.jdo.annotations.PersistenceCapable;

@PersistenceCapablepublic class Employee {    // ...}

Los campos de la clase de datos que deben guardarse en el almacén de datos han de declararse como campos persistentes. Para declarar un campo como persistente, asígnale una anotación @Persistent:import java.util.Date;import javax.jdo.annotations.Persistent;

// ...    @Persistent    private Date hireDate;

En cambio, para declarar un campo como no persistente (es decir, que no se guarda en el almacén de datos ni puede recuperarse al obtener el objeto), asígnale una anotación @NotPersistent.

Consejo: JDO especifica que los campos de determinados tipos son persistentes de forma predeterminada cuando no se determinan las anotaciones @Persistent y @NotPersistent, y que los campos de los demás tipos no son persistentes. Consulta la documentación de DataNucleus para obtener una descripción detallada de este comportamiento. Dado que no todos los tipos de valor principales del almacén de datos de App Engine son persistentes de forma predeterminada según se especifica en JDO, te recomendamos que anotes los campos como @Persistent o @NotPersistent de forma explícita para que quede claro.

A continuación, se indican los tipos de campo existentes. Encontrarás una descripción detallada de estos más abajo:

uno de los tipos principales admitidos por el almacén de datos, una colección (como java.util.List<...>) o un conjunto de valores de un tipo principal del almacén de datos, una instancia o una colección de instancias de una clase @PersistenceCapable, una instancia o una colección de instancias de una clase serializable, una clase insertada, almacenada como propiedades en la entidad.

Una clase de datos debe tener un campo dedicado al almacenamiento de la clave principal de la entidad correspondiente en el almacén de datos. Puedes elegir entre cuatro tipos de campo de clave distintos, cada uno de los cuales cuenta con un tipo de valor y anotaciones diferentes. Consulta la sección Creación de datos: claves para obtener más información. El tipo de campo de clave más flexible es el objeto Key, que JDO rellena automáticamente con un valor único respecto al resto de las instancias de la clase cuando el objeto se guarda en el almacén de datos por primera vez. Las claves principales del tipo Key requieren anotaciones @PrimaryKey y @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY):

Consejo: haz que todos tus campos sean privados (private) o protegidos (protected), o con protección de paquete, y solo proporciona acceso público a través de métodos de acceso. Es posible que el acceso directo a un campo persistente desde otra clase pueda evitar la mejora de clases de JDO, aunque también puedes crear otras clases @PersistenceAware. Consulta la documentación de DataNucleus para obtener más información.import com.google.appengine.api.datastore.Key;

import javax.jdo.annotations.IdGeneratorStrategy;import javax.jdo.annotations.PrimaryKey;

Page 66: Google App Engine

// ...    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    private Key key;

A continuación, te ofrecemos un ejemplo de clase de datos:import com.google.appengine.api.datastore.Key;

import java.util.Date;import javax.jdo.annotations.IdGeneratorStrategy;import javax.jdo.annotations.PersistenceCapable;import javax.jdo.annotations.Persistent;import javax.jdo.annotations.PrimaryKey;

@PersistenceCapablepublic class Employee {    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    private Key key;

    @Persistent    private String firstName;

    @Persistent    private String lastName;

    @Persistent    private Date hireDate;

    public Employee(String firstName, String lastName, Date hireDate) {        this.firstName = firstName;        this.lastName = lastName;        this.hireDate = hireDate;    }

    // Accessors for the fields.  JDO doesn't use these, but your application does.

    public Key getKey() {        return key;    }

    public String getFirstName() {        return firstName;    }    public void setFirstName(String firstName) {        this.firstName = firstName;    }

    public String getLastName() {        return lastName;    }

    public void setLastName(String lastName) {        this.lastName = lastName;    }

    public Date getHireDate() {        return hireDate;    }    public void setHireDate(Date hireDate) {        this.hireDate = hireDate;    }}

Tipos de valor principales

Page 67: Google App Engine

Para representar una propiedad que contenga un único valor de un tipo principal, incluye un campo del tipo Java y usa la anotación @Persistent:import java.util.Date;import javax.jdo.annotations.Persistent;

// ...    @Persistent    private Date hireDate;

Objetos serializables

Un valor de campo puede contener una instancia de una clase serializable y almacenar el valor serializado de la instancia en un único valor de propiedad del tipo blob. Para que JDO serialice el valor, el campo utiliza la anotación @Persistent(serialized=true). Los valores blob no se indexan y no se pueden utilizar en criterios de ordenación ni en filtros de consultas.

A continuación, ofrecemos un ejemplo de una clase Serializable simple que representa un archivo que incluye el contenido y el nombre de este, así como un tipo MIME. No se trata de una clase de datos JDO, por lo que no hay anotaciones de persistencia.import java.io.Serializable;

public class DownloadableFile implements Serializable {    private byte[] content;    private String filename;    private String mimeType;

    // ... accessors ...}

Para almacenar una instancia de una clase Serializable como valor blob en una propiedad, incluye un campo cuyo tipo coincida con la clase y usa la anotación @Persistent(serialized = "true"):import javax.jdo.annotations.Persistent;import DownloadableFile;

// ...    @Persistent(serialized = "true")    private DownloadableFile file;

Objetos secundarios y relaciones

Un valor de campo que sea una instancia de una clase @PersistenceCapable creará una relación de propiedad de uno a uno entre dos objetos. Sin embargo, un campo que sea una colección de estas referencias creará una relación de propiedad de uno a varios objetos.

Importante: las relaciones de propiedad influyen en las transacciones, en los grupos de entidades y en las eliminaciones en cascada. Consulta las secciones Transacciones y Relaciones para obtener más información.

A continuación, presentamos un ejemplo de relación de propiedad de uno a uno entre dos objetos, Employee y ContactInfo:

ContactInfo.javaimport com.google.appengine.api.datastore.Key;// ... imports ...

@PersistenceCapablepublic class ContactInfo {    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    private Key key;

    @Persistent    private String streetAddress;

    @Persistent    private String city;

    @Persistent

Page 68: Google App Engine

    private String stateOrProvince;

    @Persistent    private String zipCode;

    // ... accessors ...}

Employee.javaimport ContactInfo;// ... imports ...

@PersistenceCapablepublic class Employee {    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    private Key key;

    @Persistent    private ContactInfo myContactInfo;

    // ... accessors ...}

En este ejemplo, si la aplicación crea una instancia de Employee, se rellena el campo myContactInfo con una nueva instancia de ContactInfo. A continuación, se guarda la instancia Employee con pm.makePersistent(...) y el almacén de datos crea dos entidades. Una del tipo "ContactInfo", que representa la instancia de ContactInfo; y la otra, del tipo "Employee". La clave de la entidad ContactInfo tiene la clave de la entidad Employee como grupo de entidades principal.

Clases insertadas

Las clases insertadas te permiten configurar un valor de campo mediante una clase sin necesidad de crear una nueva entidad en el almacén de datos y de establecer una relación. Los campos del valor del objeto se guardan directamente en la entidad del almacén de datos cuando se trata de un objeto contenido.

Cualquier clase de datos @PersistenceCapable puede usarse como objeto insertado en otra clase de datos. Los campos @Persistent de la clase se insertan en el objeto. Si asignas la anotación @EmbeddedOnly a la clase que debe insertarse, la clase solo puede usarse como clase insertada. La clase insertada no requiere un campo de clave principal porque no se almacena como una entidad separada.

A continuación, presentamos un ejemplo de clase insertada. En este ejemplo se convierte la clase insertada en una clase interna de la clase de datos que la usa. Es algo útil, aunque no es necesario para que una clase pueda insertarse.import javax.jdo.annotations.Embedded;import javax.jdo.annotations.EmbeddedOnly;// ... imports ...

@PersistenceCapablepublic class EmployeeContacts {    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    Key key;    @PersistenceCapable    @EmbeddedOnly    public static class ContactInfo {        @Persistent        private String streetAddress;

        @Persistent        private String city;

        @Persistent        private String stateOrProvince;

Page 69: Google App Engine

        @Persistent        private String zipCode;

        // ... accessors ...    }

    @Persistent    @Embedded    private ContactInfo homeContactInfo;}

Los campos de una clase insertada se almacenan como propiedades en la entidad usando el nombre de cada campo y el nombre de la propiedad correspondiente. Si tienes más de un campo en el objeto cuyo tipo es una clase insertada, debes cambiar el nombre de los campos de uno de los campos. De este modo, no entran en conflicto entre sí. Debes especificar nuevos nombres de campo usando argumentos con la anotación @Embedded. Por ejemplo:    @Persistent    @Embedded    private ContactInfo homeContactInfo;

    @Persistent    @Embedded(members = {        @Persistent(name="streetAddress", columns=@Column(name="workStreetAddress")),        @Persistent(name="city", columns=@Column(name="workCity")),        @Persistent(name="stateOrProvince", columns=@Column(name="workStateOrProvince")),        @Persistent(name="zipCode", columns=@Column(name="workZipCode")),    })    private ContactInfo workContactInfo;

Asimismo, los campos de los objetos no deben llamarse igual que los campos de las clases insertadas, salvo que se cambie el nombre de los campos insertados.

Dado que las propiedades persistentes de la clase insertada se guardan en la misma entidad que los otros campos, puedes usar campos persistentes de la clase insertada para filtros y criterios de ordenación de consultas en JDOQL. Puedes hacer referencia al campo insertado usando el nombre del campo externo, un punto (.) y el nombre del campo insertado. Esto funciona independientemente de si se han cambiado los nombres de propiedad de los campos insertados mediante anotaciones @Column. select from EmployeeContacts where workContactInfo.zipCode == "98105"

Colecciones

Una propiedad del almacén de datos puede tener más de un valor. En JDO, esto se representa mediante un único campo del tipo colección en el que la colección es de uno de los tipos de valor principales o de una clase serializable. Se admiten los siguientes tipos de colección:

java.util.ArrayList<...> java.util.HashSet<...> java.util.LinkedHashSet<...> java.util.LinkedList<...> java.util.List<...> java.util.Set<...> java.util.SortedSet<...> java.util.Stack<...> java.util.TreeSet<...> java.util.Vector<...>

Si un campo se declara como List, los objetos que devuelve el almacén de datos tendrán un valor ArrayList. Si un campo se declara como Set, el almacén de datos devuelve un valor HashSet. Si un campo se declara como SortedSet, el almacén de datos devuelve un valor TreeSet.

Por ejemplo, un campo del tipo List<String> puede guardarse sin valores de cadena o con ellos, uno por cada valor de la lista (List).import java.util.List;// ... imports ...

// ...

Page 70: Google App Engine

    @Persistent    List<String> favoriteFoods;

Una colección de objetos secundarios (de clases @PersistenceCapable) crea varias entidades con una relación de uno a varios. Consulta la sección Relaciones.

Las propiedades del almacén de datos con más de un valor se comportan de forma especial para los filtros y los criterios de ordenación de consultas. Puedes obtener información detallada en Consultas e índices: criterios de ordenación y propiedades con varios valores.

Campos de objeto y propiedades de entidad

El almacén de datos de App Engine distingue entre entidades sin propiedad asignada y entidades con valor null como propiedad. En cambio, JDO no hace esta distinción; cada campo de objeto tiene un valor, posiblemente null. Si un campo con un tipo de valor anulable (distinto de un tipo integrado como int o boolean) se configura como null, la propiedad de la entidad resultante tendrá un valor "null" al guardar el objeto.

Si, al cargar una entidad del almacén de datos en un objeto, uno de los campos del objeto no tiene una propiedad y el tipo del campo es un valor único anulable, el campo quedará configurado como null. Cuando el objeto se guarde de nuevo en el almacén de datos, la propiedad null pasará a establecerse con valor "null" en el almacén de datos. Si el tipo de campo no es de valor anulable, se generará una excepción al cargar una entidad sin la propiedad correspondiente. Esto no sucede si la entidad se creó a partir de la misma clase de JDO utilizada para volver a crear la instancia, pero sí cuando la clase de JDO cambia o si la entidad se creó usando el API de nivel inferior en lugar de JDO.

Si el tipo de un campo es una colección de tipo de datos principal o una clase Serializable y, además, la propiedad de la entidad no contiene ningún valor, la colección vacía queda representada en el almacén de datos con un valor nulo único en la propiedad. Si el campo es de tipo Array, se le asigna un conjunto sin ningún elemento. Si el objeto se carga y la propiedad no tiene ningún valor, se le asigna al campo una colección vacía del tipo que corresponda. El almacén de datos detecta la diferencia entre una colección vacía y una colección con un único valor nulo.

Si la entidad tiene una propiedad sin un campo correspondiente en el objeto, no se podrá acceder a dicha propiedad desde el objeto. Si el objeto se vuelve a guardar en el almacén de datos, la propiedad adicional se elimina.

Si una entidad tiene una propiedad cuyo valor es de un tipo distinto al del campo correspondiente en el objeto, JDO intenta asignar dicho valor al tipo de campo. Si el valor no pudiera asignarse, JDO generaría una excepción ClassCastException. En los casos en que haya números (enteros largos y flotantes de ancho doble), no se asigna el valor, sino que se convierte. Si el valor de la propiedad numérica es mayor que el del tipo de campo, la conversión se lleva a cabo sin que se generen excepciones.

Herencia

La creación de clases de datos que utilizan la herencia es algo natural y que puede hacerse con JDO. Antes de seguir leyendo cómo funciona el sistema de herencia de JDO en App Engine, te recomendamos que primero leas la documentación de DataNucleus al respecto. ¿Ya lo has hecho? Muy bien. La herencia de JDO en App Engine funciona tal como se describe en la documentación de DataNucleus, pero con algunas restricciones adicionales. Te hablaremos de estas restricciones y, a continuación, te ofreceremos ejemplos concretos.

El método de herencia "new-table" te permite dividir los datos de un único objeto de datos en varias tablas. Sin embargo, el almacén de datos de App Engine no admite la unión de datos, por lo que esta operación requiere una invocación a procedimiento remoto para cada nivel de herencia. Esto puede llegar a ser muy poco eficaz, por lo que dicho método no es compatible con las clases de datos que no se encuentren en el nivel superior de las jerarquías de herencia.

En segundo lugar, la herencia "superclass-table" te permite almacenar los datos de un objeto de datos en la tabla de su superclase. Aunque se trate de una técnica eficaz en sí misma, no es compatible con App Engine. No obstante, es posible que sea compatible en próximas versiones.

Sin embargo, hay buenas noticias: las técnicas "subclass-table" y "complete-table" funcionan tal como se describe en la documentación de DataNucleus. Además, puedes usar también "new-table" para los objetos de datos que se encuentren en el nivel superior de la jerarquía de herencia. Te ofrecemos un ejemplo a continuación:

Worker.javaimport javax.jdo.annotations.IdGeneratorStrategy;import javax.jdo.annotations.Inheritance;import javax.jdo.annotations.InheritanceStrategy;

Page 71: Google App Engine

import javax.jdo.annotations.PersistenceCapable;import javax.jdo.annotations.Persistent;import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable@Inheritance(strategy = InheritanceStrategy.SUBCLASS_TABLE)public abstract class Worker {    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    private Key key;

    @Persistent    private String department;}

Employee.java// ... imports ...

@PersistenceCapablepublic class Employee extends Worker {    @Persistent    private int salary;}

Intern.javaimport java.util.Date;// ... imports ...

@PersistenceCapablepublic class Intern extends Worker {    @Persistent    private Date internshipEndDate;}

En este ejemplo, hemos añadido una anotación @Inheritance a la declaración de la clase Worker con su atributo strategy> definido como InheritanceStrategy.SUBCLASS_TABLE. Esto hace que JDO guarde todos los campos persistentes de Worker en las entidades del almacén de datos de sus subclases. La entidad del almacén de datos creada como resultado de la invocación de makePersistent() con una instancia de Employee tiene dos propiedades denominadas "department" y "salary". En cambio, la entidad del almacén de datos creada como resultado de la invocación de makePersistent() con una instancia de Intern tiene dos propiedades denominadas "department" e "internshipEndDate". El almacén de datos no contiene ninguna entidad del tipo "Worker".

Ahora, veamos algo un poco más interesante. Supón que, además de tener Employee e Intern, también queremos que una especialización de Employee describa a los empleados que se han marchado de la empresa:

FormerEmployee.javaimport java.util.Date;// ... imports ...

@PersistenceCapable@Inheritance(customStrategy = "complete-table")public class FormerEmployee extends Employee {    @Persistent    private Date lastDay;}

En este ejemplo, hemos añadido una anotación @Inheritance a la declaración de la clase FormerEmployee con su atributo custom-strategy> definido como "complete-table". Esto hace que JDO guarde todos los campos persistentes de FormerEmployee y sus superclases en las entidades del almacén de datos que correspondan a las instancias de FormerEmployee. La entidad del almacén de datos creada como resultado de la invocación de makePersistent() con una instancia de FormerEmployee tiene tres propiedades denominadas "department", "salary" y "lastDay". No hay entidades del tipo "Employee" que se correspondan con FormerEmployee. Sin embargo, si invocas makePersistent() con un objeto cuyo tipo de tiempo de ejecución sea Employee, crearás una entidad del tipo "Employee".

Page 72: Google App Engine

La combinación de relaciones y de herencia es eficaz siempre que los tipos declarados de los campos de tu relación coincidan con los tipos de tiempo de ejecución de los objetos que asignas a dichos campos. Consulta la sección Relaciones polimórficas para obtener más información.

Creación, obtención y eliminación de datos en JDO

Para guardar un objeto de datos JDO en el almacén de datos solo tienes que invocar el método makePersistent() de la instancia de PersistenceManager. La implementación de JDO en App Engine utiliza el campo de clave principal del objeto para realizar un seguimiento de qué entidad del almacén de datos corresponde al objeto de datos. Además, JDO puede generar claves para objetos nuevos de forma automática. Puedes usar claves para recuperar entidades rápidamente y crear claves a partir de valores conocidos (como el identificador de una cuenta).

Cómo crear objetos persistentes Claves Obtención de un objeto mediante una clave Actualización de un objeto Eliminación de un objeto

Cómo crear objetos persistentes

Para guardar un objeto de datos simple en el almacén de datos, debes invocar el método makePersistent() de PersistenceManager y transmitirle la instancia.        PersistenceManager pm = PMF.get().getPersistenceManager();

        Employee e = new Employee("Alfred", "Smith", new Date());

        try {            pm.makePersistent(e);        } finally {            pm.close();        }

La invocación de makePersistent() se lleva a cabo de forma sincronizada y devolverá resultados cuando el objeto se haya guardado y los índices se hayan actualizado.

Para guardar varios objetos en JDO, invoca el método makePersistentAll(...) con una colección de objetos. Este método usará una operación de guardado en lotes de bajo nivel más eficiente que una serie de invocaciones de makePersistent(...) individuales.

Nota: si alguno de los campos persistentes de los objetos de datos hace referencia a otro objeto de datos persistente, y si nunca se ha guardado ni modificado ninguno de esos objetos desde su subida, los objetos a los que se hace referencia también se guardan en el almacén de datos. Consulta la secciónRelaciones.

Claves

Cada entidad cuenta con una clave que es única entre todas las entidades de App Engine. Una clave completa incluye varios datos, como el ID de aplicación, el tipo y un ID de entidad. Las claves también contienen información sobre grupos de entidades. Consulta la sección Transacciones para obtener más información.

La clave de un objeto se guarda en un campo de la instancia. El campo de clave principal se identifica mediante la anotación @PrimaryKey.

La aplicación puede facilitar el fragmento de ID de la clave como cadena cuando se crea el objeto o permitir que el almacén de datos genere un ID numérico de forma automática. La clave completa debe ser única entre todas las entidades del almacén de datos. Es decir, un objeto debe tener un ID que sea único entre todos los objetos de un mismo tipo y con el mismo grupo de entidades principal (si procede). Puedes decidir el comportamiento de la clave mediante el tipo de campo y las anotaciones.

Page 73: Google App Engine

Si la clase se utiliza como clase secundaria en una relación, el tipo de campo de la clave debe ser capaz de representar a un grupo de entidades principal, ya sea la instancia de una clave o el valor de una clave codificado como cadena. Consulta las secciones Transacciones para obtener más información sobre los grupos de entidades y Relaciones para obtener más información sobre relaciones.

Consejo: si la aplicación crea un objeto nuevo y le asigna el mismo ID de cadena de otro objeto del mismo tipo (y perteneciente al mismo grupo de entidades principal), cuando guardes el objeto nuevo, este sobrescribirá al otro objeto en el almacén de datos. Puedes comprobar si un ID de cadena ya está en uso antes de crear un objeto nuevo. Para ello, usa una transacción para obtener una entidad con un ID determinado y, luego, crea el ID si este no existe. Consulta la sección Transacciones.

Existen cuatro tipos de campos de clave principal:Long

Un número entero largo (java.lang.Long), un ID de entidad que genera el almacén de datos de forma automática. Úsalo para objetos sin grupos de entidades principales cuyos ID deba generarlos automáticamente el almacén de datos. El campo de clave largo de una instancia se rellena al guardar dicha instancia.import javax.jdo.annotations.IdGeneratorStrategy;import javax.jdo.annotations.Persistent;import javax.jdo.annotations.PrimaryKey;

// ...    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    private Long id;

String (cadena sin codificar)

Una cadena (java.lang.String), un ID de entidad ("nombre de clave") que facilita la aplicación cuando se crea el objeto. Úsalo para objetos sin grupos de entidades principales cuyos ID deba facilitarlos la aplicación. La aplicación asigna el ID deseado a este campo antes del guardado.import javax.jdo.annotations.PrimaryKey;

// ...    @PrimaryKey    private String name;

Key

Una instancia de Key (com.google.appengine.api.datastore.Key). El valor de la clave incluye la clave del grupo de entidades principal (si procede), así como el ID de cadena asignado por la aplicación o el ID numérico generado por el sistema. Para crear un objeto con un ID de cadena asignado por la aplicación, debes crear el valor de Key con dicho ID y asignar el valor al campo. Para crear el objeto con un ID numérico asignado por el sistema, debes dejar el campo de la clave como "null". Para obtener más información sobre cómo usar grupos de entidades principales, consulta la sección Transacciones.import javax.jdo.annotations.IdGeneratorStrategy;import javax.jdo.annotations.Persistent;import javax.jdo.annotations.PrimaryKey;import com.google.appengine.api.datastore.Key;

// ...    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    private Key key;

    public void setKey(Key key) {        this.key = key;    }

La aplicación puede crear una instancia de Key mediante la clase KeyFactory:import com.google.appengine.api.datastore.Key;import com.google.appengine.api.datastore.KeyFactory;

// ...        Key key = KeyFactory.createKey(Employee.class.getSimpleName(), "[email protected]");        Employee e = new Employee();        e.setKey(key);        pm.makePersistent(e);

Page 74: Google App Engine

Key (cadena codificada)

Es similar a Key, con la excepción de que el valor es la clave en forma de cadena codificada. Las claves de cadena codificada permiten escribir tu aplicación de forma que pueda transferirse y seguir contando con las ventajas de los grupos de entidades del almacén de datos de App Engine.import javax.jdo.annotations.Extension;import javax.jdo.annotations.IdGeneratorStrategy;import javax.jdo.annotations.Persistent;import javax.jdo.annotations.PrimaryKey;

// ...    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    @Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")    private String encodedKey;

La aplicación puede rellenar este valor antes del guardado mediante una clave con un nombre o puede dejarlo como "null". Si el campo de la clave codificada es "null", el sistema genera una clave para el campo al guardar el objeto.

Las instancias de la clave pueden convertirse en representaciones de cadena codificada, o partir de estas representaciones, con los métodos keyToString() y stringToKey() de KeyFactory.

Al usar cadenas de clave codificadas, puedes facilitar el acceso a la cadena de un objeto o al ID numérico con un campo adicional:    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    @Extension(vendorName="datanucleus", key="gae.encoded-pk", value="true")    private String encodedKey;

    @Persistent    @Extension(vendorName="datanucleus", key="gae.pk-name", value="true")    private String keyName;

    // OR:

    @Persistent    @Extension(vendorName="datanucleus", key="gae.pk-id", value="true")    private Long keyId;

Puedes asignarle un nombre de clave a un campo "gae.pk-name" antes de guardar el objeto. Cuando el objeto se haya guardado, el campo de la clave codificada se rellenará con la clave completa, que incluye el nombre de la clave. Debe ser del tipo String.

El campo "gae.pk-id" se rellena al guardar el objeto y no puede modificarse. Debe ser del tipo Long.

Cuando se crea un nuevo objeto con una clave generada (un campo de clave que use valueStrategy = IdGeneratorStrategy.IDENTITY), el valor de su clave empieza como null. El campo de la clave se rellena cuando el objeto se escribe en el almacén de datos. Si usas una transacción, el objeto se escribe cuando esta se produce. De lo contrario, el objeto se escribe cuando se invoca el método makePersistent() durante la creación del objeto, o cuando se invoca el método close() de la instancia de PersistenceManager durante su actualización.

Para obtener más información sobre la creación de claves, consulta Entidades, propiedades y claves.

Obtención de un objeto mediante una clave

Para recuperar un objeto a partir de su clave, utiliza el método getObjectById() de PersistenceManager. El método obtiene la clase del objeto y la clave:        Key k = KeyFactory.createKey(Employee.class.getSimpleName(), "[email protected]");        Employee e = pm.getObjectById(Employee.class, k);

Page 75: Google App Engine

Si la clase utiliza un campo de clave que es un ID de cadena sin codificar (String) o un ID numérico (Long), getObjectByID() puede considerar el valor simple como parámetro de la clave:        Employee e = pm.getObjectById(Employee.class, "[email protected]");

El argumento de la clave puede ser de cualquiera de los tipos de campos de clave admitidos (ID de cadena, ID numérico, valor de clave, cadena de clave codificada) y de un tipo distinto al campo de clave de la clase. App Engine debe ser capaz de obtener la clave completa a partir del nombre de la clase y del valor proporcionado. Los ID numéricos y de cadena son exclusivos, por lo que una invocación que utilice un ID numérico nunca devuelve una entidad con un ID de cadena. Si se emplea un valor de Key o una cadena de clave codificada, la clave debe hacer referencia a una entidad cuyo tipo quede representado por la clase.

Actualización de un objeto

Un modo de actualizar un objeto con JDO es extraer el objeto y, a continuación, modificarlo mientras la interfaz PersistenceManager que ha devuelto el objeto sigue abierta. Los cambios persisten una vez que PersistenceManager se cierre. Por ejemplo: public void updateEmployeeTitle(User user, String newTitle) {    PersistenceManager pm = PMF.get().getPersistenceManager();    try {        Employee e = pm.getObjectById(Employee.class, user.getEmail());        if (titleChangeIsAuthorized(e, newTitle) {            e.setTitle(newTitle);        } else {            throw new UnauthorizedTitleChangeException(e, newTitle);        }    } finally {        pm.close();    }}

Dado que PersistenceManager ha devuelto la instancia de Employee, conoce todas las modificaciones realizadas en los campos persistentes de Employee y actualiza automáticamente el almacén de datos con estas modificaciones al cerrarse. Esto se debe a que la instancia de Employee se encuentra ligada a PersistenceManager.

Puedes modificar un objeto después de que PersistenceManager se haya cerrado declarando la clase como independiente. Para ello, añade el atributo detachable a la anotación @PersistenceCapable:import javax.jdo.annotations.PersistenceCapable;

@PersistenceCapable(detachable="true")public class Employee {    // ...}

De este modo puedes leer y escribir en los campos de un objeto Employee después de que la interfaz PersistenceManager que lo ha cargado se cierre. En el siguiente ejemplo se muestra la utilidad de un objeto independiente:public Employee getEmployee(User user) {    PersistenceManager pm = PMF.get().getPersistenceManager();    Employee employee, detached = null;    try {        employee = pm.getObjectById(Employee.class,            "[email protected]");

        // If you're using transactions, you can call        // pm.setDetachAllOnCommit(true) before committing to automatically        // detach all objects without calls to detachCopy or detachCopyAll.        detached = pm.detachCopy(employee);    } finally {        pm.close();    }    return detached;}

public void updateEmployeeTitle(Employee e, String newTitle) {    if (titleChangeIsAuthorized(e, newTitle) {        e.setTitle(newTitle);        PersistenceManager pm = PMF.get().getPersistenceManager();

Page 76: Google App Engine

        try {            pm.makePersistent(e);        } finally {            pm.close();        }    } else {        throw new UnauthorizedTitleChangeException(e, newTitle);    }}

Los objetos independientes son una buena alternativa a la creación de objetos de transferencia de datos. Para obtener más información sobre los objetos independientes, consulta la documentación de DataNucleus.

Eliminación de un objeto

Para eliminar un objeto del almacén de datos, invoca el método deletePersistent() de PersistenceManager con el objeto:        pm.deletePersistent(e);

Para eliminar varios objetos en JDO, invoca el método deletePersistentAll(...) con una colección de objetos. Este método usará una operación de eliminación en lotes de bajo nivel más eficiente que una serie de invocaciones de deletePersistent(...) individuales.

Si un objeto tiene campos que contengan objetos secundarios que también sean persistentes, también se eliminan los objetos secundarios. Consulta la sección Relaciones para obtener más información.

Para eliminar todos los objetos que coincidan con una consulta, puedes usar la función de eliminación mediante consulta de JDOQL. Consulta la sección Eliminación de entidades mediante consulta para obtener más información.

Relaciones de entidad en JDO

Puedes modelar relaciones entre objetos persistentes mediante campos de tipos de objeto. Una relación entre objetos persistentes puede definirse como una relación de propiedad cuando uno de los objetos no puede existir sin el otro, o como relación sin propiedad cuando ambos objetos pueden existir independientemente de la relación que tengan. La implementación de la interfaz de JDO en App Engine puede modelar relaciones de propiedad de uno a uno y relaciones de propiedad de uno a varios, tanto unidireccional como bidireccionalmente. Aún no se admiten las relaciones sin propiedad con una sintaxis natural, pero puedes administrarlas tú mismo guardando directamente claves del almacén de datos en los campos. App Engine crea entidades relacionadas en grupos de entidades de forma automática para hacer posible la actualización de varios objetos relacionados, aunque la aplicación se encarga de decidir cuándo debe usar las transacciones del almacén de datos.

Relaciones de propiedad de uno a uno Relaciones de propiedad de uno a varios Relaciones sin propiedad Relaciones, grupos de entidades y transacciones Eliminación de elementos secundarios dependientes y en cascada Relaciones polimórficas

Relaciones de propiedad de uno a uno

Se crea una relación unidireccional de propiedad de uno a uno entre dos objetos persistentes mediante un campo cuyo tipo es la clase de la clase relacionada.

En el siguiente ejemplo se define una clase de datos ContactInfo y una clase de datos Employee, con una relación de uno a uno de Employee con ContactInfo.

ContactInfo.javaimport com.google.appengine.api.datastore.Key;// ... imports ...

@PersistenceCapable

Page 77: Google App Engine

public class ContactInfo {    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    private Key key;

    @Persistent    private String streetAddress;

    // ...}

Employee.javaimport ContactInfo;// ... imports ...

@PersistenceCapablepublic class Employee {    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    private Key key;

    @Persistent    private ContactInfo contactInfo;

    ContactInfo getContactInfo() {        return contactInfo;    }    void setContactInfo(ContactInfo contactInfo) {        this.contactInfo = contactInfo;    }

    // ...}

Los objetos persistentes se representan como dos entidades distintas en el almacén de datos, con dos tipos diferentes. La relación se representa mediante una relación de grupo de entidades: la clave del elemento secundario utiliza la clave del elemento principal como su grupo de entidades principal. Cuando la aplicación accede al objeto secundario usando el campo del objeto principal, la implementación de JDO solicita el grupo de entidades principal para obtener el elemento secundario.

La clase secundaria debe tener un campo de clave cuyo tipo pueda contener la información de la clave principal: ya sea Key, o un valor de Key codificado como cadena. Consulta Creación de datos: claves para obtener información sobre los tipos de campo de clave.

Se crea una relación bidireccional de uno a uno mediante campos en ambas clases, con una anotación en el campo de la clase secundaria para declarar que los campos representan una relación bidireccional. El campo de la clase secundaria debe tener una anotación @Persistent con el argumento mappedBy = "...", donde el valor es el nombre del campo en la clase principal. Al rellenar el campo de un objeto, el campo de la referencia correspondiente del otro objeto también se rellena automáticamente.

ContactInfo.javaimport Employee;

// ...    @Persistent(mappedBy = "contactInfo")    private Employee employee;

Los objetos secundarios se cargan desde el almacén de datos cuando se accede a ellos por primera vez. Si no accedes al objeto secundario en un objeto principal, la entidad del objeto secundario nunca se carga. Si quieres cargar el elemento secundario, puedes "tocarlo" antes cerrando PersistenceManager (p. ej., invocando getContactInfo() en el ejemplo anterior) o puedes añadir el campo secundario de forma explícita en el grupo de extracción predeterminado para que se obtenga y se cargue con el elemento principal:

Employee.javaimport ContactInfo;

// ...

Page 78: Google App Engine

    @Persistent(defaultFetchGroup = "true")    private ContactInfo contactInfo;

Relaciones de propiedad de uno a varios

Para crear una relación de uno a varios entre objetos de una clase y varios objetos de otra, usa una colección de la clase relacionada:

Employee.javaimport java.util.List;

// ...    @Persistent    private List<ContactInfo> contactInfoSets;

Una relación bidireccional de uno a varios es similar a una relación de uno a uno, con un campo en la clase principal que usa la anotación @Persistent(mappedBy = "..."), en la que el valor es el nombre del campo de la clase secundaria:

Employee.javaimport java.util.List;

// ...    @Persistent(mappedBy = "employee")    private List<ContactInfo> contactInfoSets;

ContactInfo.javaimport Employee;

// ...    @Persistent    private Employee employee;

Los tipos de colección enumerados en la sección Definición de clases de datos: colecciones admiten las relaciones de uno a varios. Sin embargo, no se admiten conjuntos para relaciones de uno a varios.

App Engine no es compatible con consultas de unión. Es decir, no puedes solicitar una entidad principal usando el atributo de una entidad secundaria. Sin embargo, puedes solicitar la propiedad de una clase insertada, ya que estas clases almacenan propiedades en la entidad principal. Consulta Definición de clases de datos: clases insertadas.

Mantenimiento del orden en colecciones ordenadas

Las colecciones ordenadas, como List<...>, mantienen el orden de los objetos cuando se guarda el objeto principal. JDO requiere que las bases de datos conserven este orden almacenando la posición de cada objeto como una propiedad de este. App Engine almacena esto como una propiedad de la entidad correspondiente, usando un nombre de propiedad igual que el nombre del campo principal seguido de _INTEGER_IDX. Las propiedades de posición no son eficientes. Si se añade, se elimina o se mueve un elemento en la colección, todas las entidades dependientes de la ubicación modificada en la colección deben actualizarse. Puede ser un proceso lento y pueden producirse errores si no se realiza en una transacción.

Si no necesitas conservar un orden arbitrario en una colección pero debes usar un tipo de colección ordenado, puedes especificar un orden basado en las propiedades de los elementos mediante una anotación, una extensión a JDO que ofrece DataNucleus:import java.util.List;import javax.jdo.annotations.Extension;import javax.jdo.annotations.Order;import javax.jdo.annotations.Persistent;

// ...    @Persistent    @Order(extensions = @Extension(vendorName="datanucleus", key="list-ordering", value="state asc, city asc"))    private List<ContactInfo> contactInfoSets = new List<ContactInfo>();

Page 79: Google App Engine

La anotación @Order (usando la extensión list-ordering) especifica el orden de elementos deseado en la colección en forma de cláusula de orden JDOQL. Para la ordenación se utilizan los valores de propiedad de los elementos. Como sucede con las consultas, todos los elementos de una colección deben tener valores para las propiedades usadas en la cláusula de ordenación.

Al acceder a una colección se realiza una consulta. Si la cláusula de ordenación de un campo usa más de un criterio de ordenación, la consulta requiere un índice de almacén de datos. Consulta la sección Consultas e índices para obtener más información sobre los índices.

Por motivos de eficiencia, utiliza una cláusula de ordenación explícita para relaciones de tipos de colección de uno a varios siempre que sea posible.

Relaciones sin propiedad

Además de las relaciones de propiedad, el API de JDO también ofrece una solución para la administración de relaciones sin propiedad. La implementación de JDO en App Engine aún no incluye esta solución, pero no te preocupes, aún puedes administrar estas relaciones con valores Key en lugar de instancias (o colecciones de instancias) de tus objetos modelo. Imagina que el almacenamiento de objetos Key es como modelar una clave externa arbitraria entre dos objetos. El almacén de datos no garantiza la integridad referencial con estas referencias de Key, pero el uso de Key facilita mucho el modelado (y, por tanto, la extracción) de cualquier relación entre dos objetos.

Sin embargo, si eliges esta opción, debes tener en cuenta algo más. En primer lugar, la aplicación se encarga de garantizar que las claves sean del tipo correcto. JDO y el compilador no comprueban los tipos de Key de forma automática. En segundo lugar, todos los objetos deben pertenecer al mismo grupo de entidades para realizar una actualización atómica de los objetos en ambas partes de la relación.

Consejo: en algunos casos, sería recomendable modelar una relación de propiedad como si no tuviera propiedad. Esto se debe a que todos los objetos incluidos en una relación de propiedad se ubican automáticamente en el mismo grupo de entidades, y un grupo de entidades solo puede admitir de una a diez operaciones de escritura por segundo. Por lo tanto, si un objeto principal y un objeto secundario reciben 0,75 operaciones de escritura por segundo, lo más lógico sería modelar esta relación como relación sin propiedad para que tanto el objeto principal como el secundario permanecieran en su grupo de entidades independiente.

Relaciones sin propiedad de uno a uno

Supongamos que el modelo sea la relación entre personas y comida. En ella, una persona solo puede tener una comida favorita, pero esta no pertenece a la persona, ya que también puede ser la comida favorita de otras personas:

Person.java// ... imports ...

@PersistenceCapablepublic class Person {    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    private Key key;

    @Persistent    private Key favoriteFood;

    // ...}

Food.javaimport Person;// ... imports ...

@PersistenceCapablepublic class Food {    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    private Key key;

    // ...}

Page 80: Google App Engine

En este ejemplo, en lugar de asignar a una persona (Person) un miembro del tipo Food en representación de la comida favorita de esa persona, asignamos a Person un miembro de tipo Key en el que Key es el único identificador de un objeto de comida (Food). Ten en cuenta que, a menos que las instancias de Person y de Food a las que Person.favoriteFood hace referencia estén en el mismo grupo de entidades, no es posible actualizar la persona y la comida favorita de esa persona en una sola transacción.

Relaciones sin propiedad de uno a varios

Ahora queremos que una persona tenga varias comidas favoritas. Como en el caso anterior, la comida favorita no pertenece a la persona porque puede ser la comida favorita de un determinado número de personas:

Person.java// ... imports ...

@PersistenceCapablepublic class Person {    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    private Key key;

    @Persistent    private Set<Key> favoriteFoods;

    // ...}

En este ejemplo, en lugar de asignar a una persona un miembro del tipo Set<Food> en representación de las comidas favoritas de esa persona, le asignamos un miembro de tipo Set<Key> en el que el conjunto contiene los identificadores únicos de los objetos de comida Food. Ten en cuenta que, a menos que una instancia de Person y una instancia de Food incluidas en Person.favoriteFoods estén en el mismo grupo de entidades, no será posible actualizar la persona y la comida favorita en cuestión en una sola transacción.

Relaciones de varios a varios

Podemos modelar una relación de varios a varios manteniendo colecciones de claves en ambas partes de la relación. Adaptemos nuestro ejemplo para que Food haga un seguimiento de las personas que la consideren como comida favorita:

Person.javaimport java.util.Set;import com.google.appengine.api.datastore.Key;

// ...    @Persistent    private Set<Key> favoriteFoods;

Food.javaimport java.util.Set;import com.google.appengine.api.datastore.Key;

// ...    @Persistent    private Set<Key> foodFans;

En este ejemplo, Person mantiene un conjunto de valores Key que identifican exclusivamente a los objetos Food que son favoritos, y Food mantiene un conjunto de valores Key que identifican exclusivamente a los objetos Person que la consideran como comida favorita.

Cuando uses valores Key en el modelado de relaciones de varios a varios, ten en cuenta que la aplicación se encarga de mantener ambas partes de la relación:

Album.java

// ...public void addFavoriteFood(Food food) {

Page 81: Google App Engine

    favoriteFoods.add(food.getKey());    food.getFoodFans().add(getKey());}

public void removeFavoriteFood(Food food) {    favoriteFoods.remove(food.getKey());    food.getFoodFans().remove(getKey());}

Recuerda que, a menos que una instancia de Person y una instancia de Food incluidas en Person.favoriteFoods estén en el mismo grupo de entidades, no es posible actualizar la persona y la comida favorita en cuestión en una sola transacción. Si no es posible alojar los objetos en el mismo grupo de entidades, la aplicación debe tener en cuenta que las comidas favoritas de una persona pueden actualizarse sin la actualización correspondiente del conjunto de personas a las que les gusta la comida. También puede suceder lo contrario, que se actualice el conjunto de personas y no el de comidas favoritas.

Relaciones, grupos de entidades y transacciones

Cuando la aplicación guarda un objeto con relaciones de propiedad en el almacén de datos, todos los demás objetos al alcance mediante relaciones y que deban guardarse (son nuevos o se han modificado desde la última vez que se cargaron) se guardan de forma automática. Esto afecta a las transacciones y a los grupos de entidades de manera importante.

Mira el siguiente ejemplo en el que se usa una relación unidireccional entre las clases Employee y ContactInfo anteriores:    Employee e = new Employee();    ContactInfo ci = new ContactInfo();    e.setContactInfo(ci);

    pm.makePersistent(e);

Cuando el nuevo objeto Employee se guarda con el método pm.makePersistent(), el nuevo objeto ContactInfo relacionado se guarda de forma automática. Dado que ambos objetos son nuevos, App Engine crea dos nuevas entidades en el mismo grupo de entidades usando la entidad Employee como elemento principal de la entidad ContactInfo. Del mismo modo, si el objeto Employee ya se ha guardado y el objeto ContactInfo relacionado es nuevo, App Engine crea la entidad ContactInfo usando la entidad Employee existente como elemento principal.

Sin embargo, puedes observar que la invocación de pm.makePersistent() en el ejemplo no utiliza ninguna transacción. Sin una transacción explícita, ambas entidades se crean mediante acciones atómicas distintas. En ese caso, es posible que se cree la entidad Employee, pero también que la creación de la entidad ContactInfo falle. Para garantizar que ambas entidades se creen, o que ninguna lo haga, debes usar una transacción:    Employee e = new Employee();    ContactInfo ci = new ContactInfo();    e.setContactInfo(ci);

    try {        Transaction tx = pm.currentTransaction();        tx.begin();        pm.makePersistent(e);        tx.commit();    } finally {        if (tx.isActive()) {            tx.rollback();        }    }

Si ambos objetos se guardaron antes de que la relación se estableciera, App Engine no podrá mover la entidad ContactInfo existente al grupo de entidades de la entidad Employee porque solo pueden asignarse estos grupos cuando se crean las entidades. App Engine puede establecer la relación con una referencia, pero las entidades relacionadas no estarán en el mismo grupo. En ese caso, las dos entidades no pueden actualizarse ni eliminarse en la misma transacción. Si intentas actualizar o eliminar entidades de distintos grupos en la misma transacción, se generará una excepción JDOFatalUserException.

El hecho de guardar un objeto principal cuyos objetos secundarios se hayan modificado hace que también se guarden los cambios en los objetos secundarios. Resulta conveniente que los objetos principales mantengan la persistencia de todos los objetos secundarios relacionados de este modo, y utilicen transacciones al guardar los cambios.

Page 82: Google App Engine

Eliminación de elementos secundarios dependientes y en cascada

Una relación de propiedad puede ser dependiente; es decir, que el elemento secundario no exista sin su elemento principal. Si se elimina el objeto principal de una relación dependiente, también se eliminarán todos los objetos secundarios. Si rompes una relación de propiedad dependiente asignando un valor nuevo al campo dependiente del elemento principal, también se elimina el elemento secundario anterior. Puedes declarar una relación de propiedad de uno a uno como dependiente añadiendo dependent="true" a la anotación Persistent del campo en el objeto principal que hace referencia al objeto secundario:// ...    @Persistent(dependent = "true")    private ContactInfo contactInfo;

Asimismo, puedes declarar una relación de propiedad de uno a varios como dependiente añadiendo una anotación @Element(dependent = "true") al campo en el objeto principal que hace referencia a la colección secundaria:import javax.jdo.annotations.Element;// ...    @Persistent    @Element(dependent = "true")    private List contactInfos;

Tal como sucede al crear y actualizar objetos, si quieres que cada operación de un proceso de eliminación en cascada se produzca en una sola acción atómica, debes llevar a cabo la eliminación en una transacción.

Nota: la implementación de JDO es la que elimina los objetos secundarios dependientes, no el almacén de datos. Si eliminas una entidad principal usando el API de nivel inferior o la Consola del administrador, no se eliminarán los objetos secundarios relacionados.

Relaciones polimórficas

Aunque la especificación de JDO permita relaciones polimórficas, estas aún no pueden establecerse con la implementación de JDO en App Engine. Esperamos eliminar esta limitación en próximas versiones del producto. Si necesitas hacer referencia a varios tipos de objetos a través de una clase base común, recomendamos la misma estrategia usada para implementar relaciones sin propiedad: el almacenamiento de una referencia Key. Por ejemplo, si tienes una clase base Recipe con especializaciones Appetizer, Entree y Dessert, y quieres modelar la receta favorita (Recipe) de un chef (Chef), puedes hacerlo como se indica a continuación:

Recipe.javaimport javax.jdo.annotations.IdGeneratorStrategy;import javax.jdo.annotations.Inheritance;import javax.jdo.annotations.InheritanceStrategy;import javax.jdo.annotations.PersistenceCapable;import javax.jdo.annotations.Persistent;import javax.jdo.annotations.PrimaryKey;

@PersistenceCapable@Inheritance(strategy = InheritanceStrategy.SUBCLASS_TABLE)public abstract class Recipe {    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    private Key key;

    @Persistent    private int prepTime;}

Appetizer.java// ... imports ...

@PersistenceCapablepublic class Appetizer extends Recipe {// ... appetizer-specific fields}

Entree.java

Page 83: Google App Engine

// ... imports ...

@PersistenceCapablepublic class Entree extends Recipe {// ... entree-specific fields}

Dessert.java// ... imports ...

@PersistenceCapablepublic class Dessert extends Recipe {// ... dessert-specific fields}

Chef.java// ... imports ...

@PersistenceCapablepublic class Chef {    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    private Key key;

    @Persistent(dependent = "true")    private Recipe favoriteRecipe;}

Lamentablemente, si creas una instancia de Entree y la asignas a Chef.favoriteRecipe, obtendrás UnsupportedOperationException cuando intentes que el objeto Chef persista. Esto se debe a que el tipo de tiempo de ejecución del objeto, Entree, no coincide con el tipo del campo de relación declarado, Recipe. Una solución provisional sería cambiar el tipo de Chef.favoriteRecipe de Recipe a Key:

Chef.java// ... imports ...

@PersistenceCapablepublic class Chef {    @PrimaryKey    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)    private Key key;

    @Persistent    private Key favoriteRecipe;}

Dado que Chef.favoriteRecipe ha dejado de ser un campo de relación, puede hacer referencia a un objeto de cualquier tipo. El inconveniente es que, al igual que sucede con una relación sin propiedad, debes gestionar esta relación de forma manual.

Consultas en JDO

Este documento se centra en el uso de consultas con el marco de persistencia de objetos de datos Java (JDO). Para obtener información más general sobre las consultas en App Engine, accede a la página de consultas.

Cada consulta del almacén de datos usa un índice, una tabla que contiene los resultados de la consulta en el orden deseado. Los índices de las aplicaciones de App Engine vienen definidos en un archivo de configuración llamado datastore-indexes.xml. El servidor web de desarrollo genera sugerencias para este archivo de forma automática si encuentra consultas que todavía no tienen configurado ningún índice.

Page 84: Google App Engine

El mecanismo de consultas basadas en el índice admite los tipos de consultas más comunes, pero no algunas de las consultas que puedas haber realizado desde otras bases de datos de distinta tecnología. Para obtener más información sobre los índices de App Engine, consulta la sección Introducción a los índices. A continuación, se describe de forma detallada las restricciones de las consultas.

Introducción a las consultas en JDO Consulta de claves de entidad Eliminación de entidades mediante consulta Uso de cursores de consultas en JDO Configuración de la política de lectura y el tiempo límite de la llamada al almacén de datos

Introducción a las consultas en JDO

Mediante una consulta se obtienen entidades del almacén de datos que reúnan ciertas condiciones. La consulta especifica un tipo de entidad, varias condiciones o ninguna en función de los valores de propiedad de entidades (en ocasiones denominados "filtros") y varias descripciones de criterios de ordenación o ninguna. Cuando la consulta se ejecuta, esta extrae las entidades de un determinado tipo que reúnen todas las condiciones especificadas, según el criterio de ordenación definido.

También es posible que una consulta devuelva solamente las claves de las entidades en lugar de las propias entidades.

JDO puede realizar consultas para entidades que reúnan unos criterios determinados. También puedes utilizar Extent en JDO para representar la colección de cada entidad de un tipo (cada objeto de una clase guardado).

Consultas con JDOQL

JDO incluye un lenguaje de consultas para recuperar objetos que reúnan ciertos criterios. Este lenguaje, denominado JDOQL, hace referencia directamente a las clases de datos y a los campos de JDO. Además, incluye una función de comprobación de tipos para parámetros y resultados de consulta. JDOQL es similar a SQL, aunque resulta más apropiada para las bases de datos orientadas a objetos, como el almacén de datos de App Engine (el almacén de datos de App Engine no admite consultas SQL con la interfaz de JDO).

El API de consultas admite varios tipos de invocación. Puedes especificar una consulta completa en una cadena mediante la sintaxis de cadenas JDOQL. También puedes especificar algunas o todas las partes de una consulta invocando métodos en el objeto de la consulta.

A continuación, presentamos un ejemplo simple de una consulta que usa la invocación como método, con un filtro y un criterio de ordenación, mediante la sustitución de parámetros por el valor usado en el filtro. El método execute() del objeto de la consulta se invoca con los valores que deben sustituirse en la consulta, en el orden que se haya especificado.import java.util.List;import javax.jdo.Query;

// ...

    Query query = pm.newQuery(Employee.class);    query.setFilter("lastName == lastNameParam");    query.setOrdering("hireDate desc");    query.declareParameters("String lastNameParam");

    try {        List<Employee> results = (List<Employee>) query.execute("Smith");        if (!results.isEmpty()) {            for (Employee e : results) {                // ...            }        } else {            // ... no results ...        }    } finally {        query.closeAll();    }

A continuación, incluimos la misma consulta usando la sintaxis de cadenas:    Query query = pm.newQuery("select from Employee " +                              "where lastName == lastNameParam " +                              "parameters String lastNameParam " +                              "order by hireDate desc");

Page 85: Google App Engine

    List<Employee> results = (List<Employee>) query.execute("Smith");

Se pueden combinar estos estilos de definición de consultas. Por ejemplo:    Query query = pm.newQuery(Employee.class,                              "lastName == lastNameParam order by hireDate desc");    query.declareParameters("String lastNameParam");

    List<Employee> results = (List<Employee>) query.execute("Smith");

Puedes reutilizar una única instancia de Query con los distintos valores que se hayan sustituido por los parámetros invocando el método execute() varias veces. Cada invocación realiza la consulta y devuelve los resultados en forma de colección.

La sintaxis de cadenas JDOQL admite valores literales en las cadenas de valores numéricos y de cadena. Incluye las cadenas entre comillas simples (') o entre comillas dobles ("). El resto de tipos de valor debe recurrir a la sustitución de parámetros. A continuación, incluimos un ejemplo en el que se usa un valor literal de cadena:    Query query = pm.newQuery(Employee.class,                              "lastName == 'Smith' order by hireDate desc");

Filtros de consultas

Un filtro especifica un nombre de campo, un operador y un valor. El valor lo debe facilitar la aplicación; no puede hacer referencia a otro campo ni calcularse con términos de otros campos.

El operador de filtro puede ser cualquiera de los siguientes:

< menor que <= menor que o igual a = igual a > mayor que >= mayor que o igual a != no igual a

También se pueden usar los filtros contains() (normalmente conocidos como filtros IN en SQL) con la siguiente sintaxis:    // Give me all employees with lastName equal to Smith or Jones    Query query = pm.newQuery(Employee.class,                              ":p.contains(lastName)");    query.execute(Arrays.asList("Smith", "Jones"));

En realidad, el operador != realiza dos consultas: una donde todos los demás filtros son el mismo y el filtro "no igual a" se sustituye por un filtro "menor que", y otra donde el filtro "no igual a" se sustituye por un filtro "mayor que". Los resultados se fusionan en orden. Como se describe a continuación en la sección sobre los filtros de desigualdad, una consulta solo puede disponer de un filtro "no igual a" y no puede presentar ningún otro filtro de desigualdad.

El operador contains() también realiza varias consultas, una por cada elemento del valor de la lista proporcionada, donde los demás filtros son iguales y el filtro contains() se sustituye por un filtro "igual a". Los resultados se fusionan en el orden de los elementos de la lista. Si una consulta dispone de más de un filtro contains(), esta se realiza como si se tratara de varias consultas, una por cada combinación de valores de los filtros contains().

Una sola consulta que contenga los operadores != o contains() tiene un límite de 30 subconsultas.

Para obtener más información, consulta Queries with != and IN filters (Consultas con filtros "!=" e "IN").

Debido al modo en que el almacén de datos de App Engine ejecuta las consultas, una sola consulta no puede usar filtros de desigualdad (< <= >= > !=) en más de una propiedad. Se permiten varios filtros de desigualdad en la misma propiedad (por ejemplo, al realizar consultas para un intervalo de valores determinado).    query.setFilter("lastName == 'Smith' && hireDate > hireDateMinimum");    query.declareParameters("Date hireDateMinimum");

Page 86: Google App Engine

Una entidad debe coincidir con todos los filtros para ser un resultado. En la sintaxis de cadena JDOQL, puedes separar varios filtros can con || ("or" lógico) y && ("and" lógico). Sin embargo, recuerda que || solo puede usarse cuando separa filtros con el mismo nombre de campo. En otras palabras, || solo es legal en situaciones en las que los filtros que separa pueden combinarse en un único filtro contains():    // legal, all filters separated by || are on the same field    Query query = pm.newQuery(Employee.class,                              "(lastName == 'Smith' || lastName == 'Jones')" +                              " && firstName == 'Harold'");

    // not legal, filters separated by || are on different fields    Query query = pm.newQuery(Employee.class,                              "lastName == 'Smith' || firstName == 'Harold'");

No se admite la negación ("not" lógico).

Criterios de ordenación de consultas

Un criterio de ordenación especifica una propiedad y una dirección, ya sea ascendente o descendente. Los resultados se devuelven ordenados según un criterio determinado, en el orden especificado. Si no se especifica ningún criterio para la consulta, la ordenación de resultados queda sin definir y estos se devuelven según vayan obteniéndose del almacén de datos.

Debido al modo en que el almacén de datos de App Engine ejecuta las consultas, si una consulta especifica filtros de desigualdad en una propiedad y criterios de ordenación en otras propiedades, la propiedad con filtros de desigualdad debe ordenarse antes que el resto de propiedades.    query.setOrdering("hireDate desc, firstName asc");

Intervalos de consultas

Una solicitud puede especificar que se devuelva un intervalo de resultados en la aplicación. El intervalo especifica qué resultados deben devolverse primero, y cuáles en último lugar, mediante índices numéricos en los que un cero corresponde al primer resultado. Por ejemplo, un intervalo de 5, 10 devuelve los resultados sexto, séptimo, octavo, noveno y décimo.

El resultado inicial influye en el rendimiento, ya que el almacén de datos debe recuperar y, a continuación, descartar todos los resultados anteriores a dicho resultado. Por ejemplo, una consulta con un intervalo de 5, 10 devuelve diez resultados del almacén de datos y, a continuación, descarta los primeros cinco y devuelve los cinco restantes en la aplicación.    query.setRange(5, 10);

En cambio, si quieres usar la paginación en el almacén de datos de App Engine, deberías usar las técnicas indicadas en este artículo. Los ejemplos de este artículo están en lenguaje Python, pero también son válidos para Java.

Elementos Extent

En JDO, Extent representa cada objeto de una clase concreta en el almacén de datos.

Puedes iniciar Extent mediante el método getExtent() de PersistenceManager y, a continuación, utilizarlo con la clase de datos. La clase Extent implementa la interfaz iterable para acceder a los resultados. Cuando hayas accedido a los resultados, invoca el método closeAll().En el siguiente ejemplo se procesa una iteración sobre cada objeto Employee del almacén de datos:import java.util.Iterator;import javax.jdo.Extent;

// ...

    Extent extent = pm.getExtent(Employee.class, false);    for (Employee e : extent) {        // ...    }    extent.closeAll();

Con Extent se recuperan resultados en lote, según sea necesario.

Page 87: Google App Engine

Consulta de claves de entidad

Las claves de entidad pueden estar sujetas a un filtro o al criterio de ordenación de una consulta. En JDO, se hace referencia a la clave de la entidad en la consulta mediante el campo de clave principal del objeto. El almacén de datos considera el valor de clave completo para esas consultas, incluida la ruta de la entidad principal de la entidad, el tipo y la cadena de nombre de clave asignada por la aplicación o el ID numérico asignado por el sistema.

Una consulta puede devolver claves de entidades en lugar de entidades completas. Para ello, solo tienes que seleccionar el campo @PrimaryKey en tu consulta:    Query q = pm.newQuery("select id from " + Person.class.getName());    List ids = (List) q.execute();

Nota: las consultas que devuelven claves son más rápidas y consumen menos CPU que las consultas que devuelven entidades ya que las claves en sí ya se encuentran en el índice. Por lo tanto, la consulta no necesita extraer las entidades. Si solo necesitas las claves de los resultados de tu consulta, te recomendamos que realices una consulta de solo claves.

Dado que una clave de entidad es única entre todas las entidades del sistema, las consultas de claves facilitan la recuperación en lote de entidades de un tipo determinado, como en el caso de un volcado en lote del contenido del almacén de datos. Al contrario que los intervalos JDOQL, esto funciona de forma eficaz independientemente del número de entidades.

Las claves se ordenan primero por ruta principal y luego por tipo y por nombre o ID. Los tipos y los nombres de clave son cadenas y se ordenan por valor de byte. Los ID son números enteros y se ordenan numéricamente. Si las entidades que pertenecen a la misma entidad principal y al mismo tipo emplean una combinación de cadenas de nombre de clave y de ID numéricos, las entidades con ID numéricos se consideran inferiores a las entidades con cadenas de nombre de clave. Los elementos de la ruta principal se ordenan de forma parecida: primero por tipo (cadena) y luego por nombre de clave (cadena) o por ID (número).

Las consultas de claves utilizan los índices igual que las consultas de propiedades. Las consultas de claves necesitan disponer de índices personalizados en los mismos casos que las consultas de propiedades, con un par de salvedades: no se necesita un índice personalizado para filtros de desigualdad ni para un criterio de ordenación ascendente en Entity.KEY_RESERVED_PROPERTY, pero sí se requiere un índice de este tipo para un criterio de ordenación descendente en Entity.KEY_RESERVED_PROPERTY. Al igual que sucede con todas las consultas, el servidor de desarrollo web crea entradas de configuración adecuadas en este archivo cuando se prueba una consulta que requiere un índice personalizado.

Eliminación de entidades mediante consulta

Si realizas una consulta con el fin de eliminar todas las entidades que coincidan con el filtro de la consulta, puedes escribir mucho menos código si utilizas la función de eliminación mediante consulta de JDO. El código siguiente elimina a todas las personas que sean de una determinada altura:    Query query = pm.newQuery(Person.class);    query.setFilter("height > maxHeightParam");    query.declareParameters("int maxHeightParam");    query.deletePersistentAll(maxHeight);

Puedes comprobar que la única diferencia es que, en lugar de invocar query.execute(...), invocamos query.deletePersistentAll(...). Todas las reglas y restricciones relacionadas con filtros, criterios de ordenación e índices descritas anteriormente se aplican a las consultas, tanto si seleccionas como si eliminas el conjunto de resultados. Sin embargo, ten en cuenta que, del mismo modo que has eliminado las entidades de persona ("Person") con pm.deletePersistent(...), también se eliminan todos los elementos secundarios dependientes de las entidades eliminadas con la consulta. Para obtener más información sobre elementos secundarios dependientes, consulta Eliminación de elementos secundarios dependientes y en cascada.

Uso de cursores de consultas en JDO

En JDO puedes usar una extensión y la clase JDOCursorHelper para utilizar cursores con las consultas de JDO. Los cursores se utilizan para extraer resultados en forma de lista o mediante un iterador. Para obtener un cursor (Cursor), debes dirigir el resultado List o Iterator al método estático JDOCursorHelper.getCursor():import java.util.HashMap;import java.util.List;import java.util.Map;import javax.jdo.Query;import com.google.appengine.api.datastore.Cursor;import org.datanucleus.store.appengine.query.JDOCursorHelper;

Page 88: Google App Engine

// ...        Query query = pm.newQuery(Employee.class);        query.setRange(0, 20);        // ...

        List<Employee> results = (List<Employee>) query.execute();        // Use the first 20 results...

        Cursor cursor = JDOCursorHelper.getCursor(results);        String cursorString = cursor.toWebSafeString();        // Store the cursorString...

        // ...

        // Query query = the same query that produced the cursor        // String cursorString = the string from storage        Cursor cursor = Cursor.fromWebSafeString(cursorString);        Map<String, Object> extensionMap = new HashMap<String, Object>();        extensionMap.put(JDOCursorHelper.CURSOR_EXTENSION, cursor);        query.setExtensions(extensionMap);        query.setRange(0, 20);

        List<Employee> results = (List<Employee>) query.execute();        // Use the next 20 results...

Para obtener más información, consulta la sección Cursores de consultas.

Configuración de la política de lectura y el tiempo límite de la llamada al almacén de datos

Mediante la configuración puedes establecer la política de lectura (consistencia fuerte o consistencia eventual) y el tiempo límite de todas las invocaciones del almacén de datos realizadas por una instancia de PersistenceManager. También puedes anular estas opciones para un objeto de consulta específico.

Para anular la política de lectura de una única consulta (Query), invoca su método addExtension() tal como se indica a continuación:        Query q = pm.newQuery(Employee.class);        q.addExtension("datanucleus.appengine.datastoreReadConsistency", "EVENTUAL");

Los valores admitidos son "EVENTUAL" (para lecturas con consistencia eventual) y "STRONG" (para lecturas con consistencia fuerte). El valor predeterminado es "STRONG", salvo que se haya especificado otro valor en la configuración del archivo jdoconfig.xml.

Para anular el tiempo límite de las invocaciones del almacén de datos para una sola consulta (Query), invoca su método setTimeoutMillis():q.setTimeoutMillis(3000);

El valor es una cantidad de tiempo expresada en milisegundos.

No hay forma de anular la configuración de estas opciones al extraer entidades por clave.

Si seleccionas la consistencia eventual en una consulta del almacén de datos, los índices que emplea esta consulta para obtener resultados también permiten el acceso con consistencia eventual. A veces, las consultas devuelven entidades que no coinciden con los criterios de la consulta, aunque también sucede con una política de lectura de consistencia fuerte. Puedes usar las transacciones para garantizar un conjunto de resultados coherente si la consulta usa un filtro de ancestro. Consulta la sección Aislamiento de transacciones en App Engine para obtener más información sobre cómo se actualizan las entidades y los índices.

Uso de JPA con App Engine

Page 89: Google App Engine

La interfaz del API de persistencia Java (JPA) es una interfaz estándar para almacenar objetos con datos en una base de datos relacional. El estándar permite que las interfaces realicen tareas de anotación de objetos Java, de recuperación de objetos con consultas y de interacción con una base de datos mediante transacciones. Una aplicación que emplee la interfaz de JPA puede funcionar con distintas bases de datos sin necesidad de usar el código de base de datos del proveedor. JPA simplifica la transferencia de tu aplicación entre los diferentes proveedores de bases de datos.

El SDK de Java de App Engine incluye JPA 1.0 para el almacén de datos de App Engine. La implementación se basa en DataNucleus Access Platform. JPA presenta una interfaz estándar para la interacción con bases de datos relacionales, aunque el almacén de datos de App Engine no sea una base de datos relacional. Por lo tanto, hay funciones de JPA que la implementación de App Engine, sencillamente, no admite. Hemos hecho todo lo posible por hacer referencia a dichas funciones donde corresponde.

Consulta la documentación de Access Platform 1.1 para obtener más información sobre JPA. En concreto, consulta las secciones "JPA Mapping" (Asignación de JPA) y "JPA API" (API de JPA).

Configuración de JPA Mejora de las clases de datos Obtención de una instancia de EntityManager Anotaciones de clases y de campos Herencias Funciones no disponibles en JPA

Configuración de JPA

Si quieres usar JPA para acceder al almacén de datos, necesitas lo siguiente para la aplicación de App Engine:

Los archivos JAR de JPA y del almacén de datos deben encontrarse en el directorio war/WEB-INF/lib/ de la aplicación. Debe haber un archivo de configuración llamado persistence.xml en el directorio war/WEB-INF/classes/META-

INF/ de la aplicación cuya configuración solicita a JPA el uso del almacén de datos de App Engine. El proceso de creación del proyecto requiere una mejora posterior a la compilación en las clases de datos compilados para

asociarlas a la implementación JPA.

Si utilizas Google Plugin for Eclipse, el plug-in realiza el primer y el tercer paso por ti. El asistente de nuevos proyectos coloca los archivos JAR de JPA y del almacén de datos en la ubicación correcta y, a continuación, realiza la mejora de forma automática durante el proceso de creación. Aun así, debes crear el archivo persistence.xml manualmente y guardarlo en war/WEB-INF/classes/META-INF/. El plug-in se actualizará en breve para realizar también este paso de forma automática.

Si utilizas Apache Ant para crear tu proyecto, puedes utilizar una tarea de Ant incluida con el SDK para realizar la mejora. Debes copiar los archivos JAR y crear el archivo de configuración cuando configures el proyecto. Consulta la sección Uso de Apache Ant para obtener más información sobre dicha tarea.

Copia de los archivos JAR

Los archivos JAR de JPA y del almacén de datos se incluyen en el SDK de Java de App Engine; los encontrarás en el directorio appengine-java-sdk/lib/user/orm/.

Copia los archivos JAR en el directorio war/WEB-INF/lib/ de tu aplicación.

Asegúrate de que appengine-api.jar también se encuentre en el directorio war/WEB-INF/lib/ (es posible que ya lo hayas copiado al crear el proyecto). El plug-in DataNucleus de App Engine utiliza este archivo JAR para acceder al almacén de datos.

Creación del archivo persistence.xml

La interfaz de JPA requiere un archivo de configuración llamado persistence.xml en el directorio war/WEB-INF/classes/META-INF/ de la aplicación. Puedes crear directamente este archivo en dicha ubicación o hacer que se copie desde un directorio determinado durante el proceso de creación.

Debes crear el archivo con el siguiente contenido:<?xml version="1.0" encoding="UTF-8" ?><persistence xmlns="http://java.sun.com/xml/ns/persistence"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

Page 90: Google App Engine

    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

    <persistence-unit name="transactions-optional">        <provider>org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider</provider>        <properties>            <property name="datanucleus.NontransactionalRead" value="true"/>            <property name="datanucleus.NontransactionalWrite" value="true"/>            <property name="datanucleus.ConnectionURL" value="appengine"/>        </properties>    </persistence-unit>

</persistence>

Configuración de la política de lectura y el tiempo límite de la llamada al almacén de datos

Tal como se describe en la documentación sobre consultas, puedes establecer la política de lectura (consistencia fuerte o consistencia eventual) y el tiempo límite para la invocación del almacén de datos para EntityManagerFactory en el archivo persistence.xml. Esta configuración se incluye en el elemento <persistence-unit>. Todas las invocaciones realizadas con una instancia de EntityManager determinada usan la configuración especificada cuando EntityManagerFactory creó el gestor. También puedes anular estas opciones para una consulta (Query) determinada, tal como se describe más abajo.

Para establecer una política de lectura, incluye una propiedad denominada datanucleus.appengine.datastoreReadConsistency. Las opciones son EVENTUAL (para lecturas con consistencia eventual) y STRONG (para lecturas con consistencia fuerte). Si no se especifica, el valor predeterminado es STRONG.            <property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />

Puedes definir distintos tiempos límite en las invocaciones del almacén de datos tanto para procesos de lectura como de escritura. Para procesos de lectura, usa la propiedad estándar de JPA javax.persistence.query.timeout; y para procesos de escritura, usa datanucleus.datastoreWriteTimeout. El valor es una cantidad de tiempo expresada en milisegundos.            <property name="javax.persistence.query.timeout" value="5000" />            <property name="datanucleus.datastoreWriteTimeout" value="10000" />

Puedes tener varios elementos <persistence-unit> en el mismo archivo persistence.xml, con distintos atributos name, para usar instancias de EntityManager con diferentes configuraciones en la misma aplicación. Por ejemplo, el siguiente archivo persistence.xml establece dos configuraciones; una se denomina "transactions-optional", y la otra "eventual-reads-short-deadlines":<?xml version="1.0" encoding="UTF-8" ?><persistence xmlns="http://java.sun.com/xml/ns/persistence"    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"    xsi:schemaLocation="http://java.sun.com/xml/ns/persistence        http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">

    <persistence-unit name="transactions-optional">        <provider>org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider</provider>        <properties>            <property name="datanucleus.NontransactionalRead" value="true"/>            <property name="datanucleus.NontransactionalWrite" value="true"/>            <property name="datanucleus.ConnectionURL" value="appengine"/>        </properties>    </persistence-unit>

    <persistence-unit name="eventual-reads-short-deadlines">        <provider>org.datanucleus.store.appengine.jpa.DatastorePersistenceProvider</provider>        <properties>            <property name="datanucleus.NontransactionalRead" value="true"/>            <property name="datanucleus.NontransactionalWrite" value="true"/>            <property name="datanucleus.ConnectionURL" value="appengine"/>

Page 91: Google App Engine

            <property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />            <property name="javax.persistence.query.timeout" value="5000" />            <property name="datanucleus.datastoreWriteTimeout" value="10000" />        </properties>    </persistence-unit></persistence>

Para obtener más información, consulta la documentación sobre consultas.

Consulta la sección Obtención de una instancia de EntityManager a continuación para obtener información de cómo crear EntityManager seleccionando la configuración deseada.

Puedes anular la política de lectura y el tiempo límite de las invocaciones para objetos Query determinados. Para anular la política de lectura de una consulta (Query), invoca su método setHint() tal como se indica a continuación:        Query q = em.createQuery("select from " + Book.class.getName());        q.setHint("datanucleus.appengine.datastoreReadConsistency", "EVENTUAL");

Como antes, los valores que admite son "EVENTUAL" y "STRONG".

Para anular el tiempo de espera de lectura, invoca setHint() como se indica a continuación:        q.setHint("javax.persistence.query.timeout", 3000);

No hay forma de anular la configuración de estas opciones al extraer entidades por clave.

Mejora de las clases de datos

La implementación de DataNucleus en JPA realiza una mejora después de la compilación, durante el proceso de creación, para asociar las clases de datos a la implementación de JPA.

Si usas Apache Ant, el SDK incluye una tarea de Ant para realizar este paso. Consulta la sección Uso de Apache Ant para obtener más información sobre dicha tarea.

Puedes realizar la mejora en clases compiladas desde la línea de comandos utilizando el siguiente comando:java -cp classpath org.datanucleus.enhancer.DataNucleusEnhancer class-files

classpath debe contener los archivos JAR datanucleus-core-*.jar, datanucleus-jpa-*, datanucleus-enhancer-*.jar, asm-*.jar y geronimo-jpa-*.jar (* es el número de versión correspondiente a cada JAR) del directorio appengine-java-sdk/lib/tools/, así como todas tus clases de datos.

Para obtener más información sobre el programa de mejora en bytecode DataNucleus, consultala documentación de DataNucleus.

Obtención de una instancia de EntityManager

Una aplicación interactúa con JPA utilizando una instancia de la clase EntityManager. Esta instancia se obtiene al reproducir e invocar un método en una instancia de la clase EntityManagerFactory. La fábrica utiliza la configuración JPA (identificada como "transactions-optional") para crear instancias de EntityManager.

Dado que una instancia de EntityManagerFactory tarda en iniciarse, lo ideal sería reutilizar la misma instancia tantas veces como sea posible. Una forma sencilla de hacerlo es crear una clase envoltorio única con una instancia estática. Por ejemplo:

EMF.javaimport javax.persistence.EntityManagerFactory;import javax.persistence.Persistence;

public final class EMF {    private static final EntityManagerFactory emfInstance =        Persistence.createEntityManagerFactory("transactions-optional");

    private EMF() {}

Page 92: Google App Engine

    public static EntityManagerFactory get() {        return emfInstance;    }}

Consejo: "transactions-optional" hace referencia al nombre de la configuración en el archivo persistence.xml. Si tu aplicación usa varias configuraciones, debes ampliar este código para invocar Persistence.createEntityManagerFactory() como quieras. El código debería incluir en la memoria caché una instancia única de cada EntityManagerFactory.

La aplicación utiliza la instancia de la fábrica para crear una instancia de EntityManager por cada solicitud que acceda al almacén de datos.import javax.persistence.EntityManager;import javax.persistence.EntityManagerFactory;

import EMF;

// ...    EntityManager em = EMF.get().createEntityManager();

EntityManager se usa para almacenar, actualizar y eliminar objetos de datos, así como para realizar consultas al almacén de datos.

Cuando acabes con la instancia de EntityManager, debes invocar su método close(). Sería un error usar la instancia de EntityManager después de invocar su método close().    try {        // ... do stuff with em ...    } finally {        em.close();    }

Anotaciones de clase y de campo

Cada objeto que guarda JPA se convierte en una entidad del almacén de datos de App Engine. El tipo de la entidad se obtiene a partir del nombre simple de la clase (sin el nombre del paquete). Cada campo persistente de la clase representa una propiedad de la entidad, con un nombre de propiedad idéntico al nombre del campo (con distinción de mayúsculas y minúsculas).

Para declarar una clase Java como almacenable y recuperable en el almacén de datos con JPA, asigna una anotación @Entity a la clase. Por ejemplo:import javax.persistence.Entity;

@Entitypublic class Employee {    // ...}

Los campos de la clase de datos que se vayan a guardar en el almacén de datos deben ser de un tipo que persista de forma predeterminada o que se declare explícitamente como persistente. El comportamiento de persistencia predeterminado de JPA queda reflejado de forma detallada en esta tabla del sitio web de DataNucleus. Para declarar un campo como persistente, asígnale una anotación @Basic:import java.util.Date;import javax.persistence.Enumerated;

import com.google.appengine.api.datastore.ShortBlob;

// ...    @Basic    private ShortBlob data;

A continuación, se indican los tipos de campo existentes.

uno de los tipos principales admitidos por el almacén de datos, una colección (como java.util.List<...>) de valores de un tipo principal del almacén de datos, una instancia o una colección de instancias de una clase @Entity,

Page 93: Google App Engine

una clase insertada, almacenada como propiedades en la entidad.

Una clase de datos debe tener un constructor predeterminado, público o protegido, y un campo dedicado al almacenamiento de la clave principal de la entidad del almacén de datos correspondiente. Puedes elegir entre cuatro tipos de campo de clave distintos, cada uno de los cuales cuenta con un tipo de valor y anotaciones diferentes. Consulta la sección Creación de datos: claves para obtener más información. El campo de clave más simple es el valor de número entero largo, que JPA rellena automáticamente con un valor único respecto al resto de las instancias de la clase cuando el objeto se guarda en el almacén de datos por primera vez. Las claves de números enteros largos usan anotaciones @Id y @GeneratedValue(strategy = GenerationType.IDENTITY):import com.google.appengine.api.datastore.Key;

import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;

// ...    @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)    private Key key;

A continuación, te ofrecemos un ejemplo de clase de datos:import com.google.appengine.api.datastore.Key;

import java.util.Date;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;

@Entitypublic class Employee {    @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)    private Key key;

    private String firstName;

    private String lastName;

    private Date hireDate;

    // Accessors for the fields. JPA doesn't use these, but your application does.

    public Key getKey() {        return key;    }

    public String getFirstName() {        return firstName;    }     public void setFirstName(String firstName) {        this.firstName = firstName;    }

    public String getLastName() {        return lastName;    }     public void setLastName(String lastName) {        this.lastName = lastName;    }

    public String getHireDate() {        return hireDate;    }     public void setHireDate(Date hireDate) {        this.hireDate = hireDate;

Page 94: Google App Engine

    } }

Herencia

JPA admite la creación de clases de datos que utilicen herencia. Antes de seguir leyendo cómo funciona el sistema de herencia de JPA en App Engine, te recomendamos que leas la documentación de DataNucleus al respecto. ¿Ya lo has hecho? Muy bien. La herencia de JPA en App Engine funciona tal como se describe en la documentación de DataNucleus, pero con algunas restricciones adicionales. Te hablaremos de estas restricciones y, a continuación, te ofreceremos ejemplos concretos.

El método de herencia "JOINED" te permite dividir los datos de un único objeto de datos en varias tablas. Sin embargo, el almacén de datos de App Engine no es compatible con la unión de datos, por lo que esta operación requiere una invocación a procedimiento remoto para cada nivel de herencia. Esto resulta muy poco eficiente, ya que el método de herencia "JOINED" no es compatible con las clases de datos.

En segundo lugar, el método de herencia "SINGLE_TABLE" te permite almacenar los datos de un objeto de datos en una sola tabla asociada a la clase persistente en el nivel superior de la jerarquía de herencia. Aunque se trate de una técnica eficaz en sí misma, no es compatible con App Engine. No obstante, es posible que sea compatible en próximas versiones.

Pero la buena noticia es que los métodos "TABLE_PER_CLASS" y "MAPPED_SUPERCLASS" funcionan como se indica en la documentación de DataNucleus. Te ofrecemos un ejemplo a continuación:

Worker.javaimport javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.MappedSuperclass;

@Entity@MappedSuperclasspublic abstract class Worker {    @Id    @GeneratedValue(strategy = GenerationType.IDENTITY)    private Key key;

    private String department;}

Employee.java// ... imports ...

@Entitypublic class Employee extends Worker {    private int salary;}

Intern.javaimport java.util.Date;// ... imports ...

@Entitypublic class Intern extends Worker {    private Date internshipEndDate;}

En este ejemplo, hemos añadido una anotación @MappedSuperclass a la declaración de la clase Worker. Esto hace que JPA guarde todos los campos persistentes de Worker en las entidades del almacén de datos de sus subclases. La entidad del almacén de datos creada como resultado de la invocación de persist() con una instancia de Employee tiene dos propiedades denominadas "department" y "salary". La entidad del almacén de datos creada como resultado de la invocación de persist() con una instancia de Intern tiene dos propiedades denominadas "department" e "inernshipEndDate". No habrá ninguna entidad del tipo "Worker" en el almacén de datos.

Page 95: Google App Engine

Ahora, veamos algo un poco más interesante. Supón que, además de tener Employee e Intern, también queremos que una especialización de Employee describa a los empleados que se han marchado de la empresa:

FormerEmployee.javaimport java.util.Date;import javax.persistence.Inheritance;import javax.persistence.InheritanceType;// ... imports ...

@Entity@Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)public class FormerEmployee extends Employee {    private Date lastDay;}

En este ejemplo, hemos añadido una anotación @Inheritance a la declaración de la clase FormerEmployee con su atributo strategy definido como InheritanceType.TABLE_PER_CLASS. Esto hace que JPA guarde todos los campos persistentes de FormerEmployee y sus superclases en las entidades del almacén de datos que correspondan a las instancias de FormerEmployee. La entidad del almacén de datos creada como resultado de la invocación de persist() con una instancia de FormerEmployee tiene tres propiedades denominadas "department", "salary" y "lastDay". Nunca habrá una entidad del tipo "Employee" correspondiente a FormerEmployee, pero si invocas persist() con un objeto cuyo tipo de tiempo de ejecución sea Employee, crearás una entidad del tipo Employee.

La combinación de relaciones y de herencia es eficaz siempre que los tipos declarados de los campos de tu relación coincidan con los tipos de tiempo de ejecución de los objetos que asignas a dichos campos. Consulta la sección Relaciones polimórficas para obtener más información. Esta sección contiene ejemplos de JDO, pero los conceptos y las restricciones son las mismas para JPA.

Funciones no disponibles en JPA

Las siguientes funciones de la interfaz de JPA no se encuentran disponibles en la implementación de App Engine:

Las relaciones de propiedad multidireccionales y las relaciones sin propiedad. Puedes implementar relaciones sin propiedad mediante valores Key explícitos, aunque la comprobación del tipo no se realiza en el API.

Consultas "Join". No puedes usar el campo de una entidad secundaria en un filtro al realizar una consulta del tipo principal. Ten en cuenta que puedes probar el campo de relación del elemento principal directamente en las consultas mediante una clave.

Consultas de agrupación conjunta (group by, having, sum, avg, max, min). Consultas polimórficas. No puedes realizar consultas de una clase para obtener instancias de una subclase. Cada clase se

representa mediante un tipo de entidad independiente en el almacén de datos.