Избранное | Русский | Войти

Использование хранилища данных с JDO

Хранение данных в масштабируемом веб-приложении может быть непростой задачей. В определенный момент времени пользователь может взаимодействовать с любым из десятков веб-серверов, а его следующий запрос может отправиться уже не на тот веб-сервер, который обрабатывал предыдущий. Все веб-серверы должны взаимодействовать с данными, которые также распределены по десяткам компьютеров, возможно расположенным в разных точках планеты.

Благодаря Google App Engine беспокоиться об этом не надо. Инфраструктура App Engine занимается распределением, копированием и регулированием нагрузки данных с помощью простого API, а вы получаете мощный механизм поиска и транзакции.

Хранилище данных App Engine – это одна из нескольких служб, предоставляемых App Engine с помощью двух API: стандартного API и API низкого уровня. С помощью стандартных API можно легко переносить приложение в другие среды размещения и другие технологии баз данных, если нужно. Стандартные API "разъединяют" приложение и службы App Engine. Службы App Engine также предоставляют API низкого уровня, непосредственно предоставляющие возможности службы. API низкого уровня можно использовать для реализации новых адаптерных интерфейсов или просто использовать API прямо в приложении.

App Engine поддерживает два стандарта API для хранилища данных: Объекты данных Java (JDO) и API постоянства Java (JPA). Эти интерфейсы предоставляются платформой DataNucleus Access Platform, реализацией стандартов постоянства Java с открытым исходным кодом с адаптером для хранилища данных App Engine.

Для гостевой книги мы используем интерфейс JDO для получения и отправки сообщений пользователей.

Настройка платформы DataNucleus Access Platform

DataNucleus Access Platform нужен файл конфигурации, который предписывает ему использовать хранилище данных App Engine в качестве сервера для реализации JDO. В окончательном WAR тот файл называется jdoconfig.xml и находится в каталоге war/WEB-INF/classes/META-INF/.

При использовании Eclipse этот файл уже создан в виде src/META-INF/jdoconfig.xml. Этот файл автоматически копируется в war/WEB-INF/classes/META-INF/ при сборке проекта.

Если вы не используете Eclipse, можно прямо создать каталог war/WEB-INF/classes/META-INF/ или создать его с помощью процесса сборки и скопировать файл конфигурации из другого места. Сценарий сборки Ant, описанный в статье Использование Apache Ant копирует этот файл из src/META-INF/.

У файла jdoconfig.xml должно быть следующее содержание:

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

Улучшение класса JDO

При создании классов JDO используются аннотации Java, чтобы описать хранение экземпляров в хранилище данных и их воссоздание при получении их из хранилища данных. Access Platform соединяет классы данных с реализацией с помощью этапа обработки после компиляции, который называется в DataNucleus "улучшением" классов.

При использовании Eclipse и плагина Google, последний выполняет этап улучшения классов JDO автоматически в процессе сборки.

При использовании сценария сборки Ant, описанного в статье Использование Apache Ant, сценарий сборки включает нужны этап улучшения.

Дополнительные сведения об улучшении классов JDO см. в статье Использование JDO.

POJO и аннотации JDO

JDO позволяет хранить объекты Java (иногда называемые простыми старыми объектами Java или POJO) в любом хранилище данных с совместимым с JDO адаптером, например платформе DataNucleus Access Platform. SDK App Engine включает плагин платформы Access Platform для хранилища данных App Engine. Это значит, что можно сохранять экземпляры классов, определенные в хранилище данных App Engine и извлекать их как объекты с помощью JDO API. Инструкции о том, как хранить и восстанавливать экземпляры классов, даются JDO с помощью аннотаций Java.

Создадим класс Greeting, представляющий отдельные сообщения, размещенные в гостевой книге.

Создайте класс под названием Greeting в пакете guestbook. (Для тех, кто не пользуется Eclipse: создайте файл Greeting.java в каталоге src/guestbook/.) Поместите в исходный файл следующее содержание:

package guestbook;

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

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

    @Persistent
    private User author;

    @Persistent
    private String content;

    @Persistent
    private Date date;

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

    public Long getId() {
        return id;
    }

    public User getAuthor() {
        return author;
    }

    public String getContent() {
        return content;
    }

    public Date getDate() {
        return date;
    }

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

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

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

Этот простой класс определяет 3 свойства приветствия: author, content и date. Три этих закрытых поля аннотируются с помощью @Persistent, чтобы DataNucleus сохраняла их как свойства объектов в хранилище данных App Engine.

Этот класс определяет получатели и установщики свойств. Они используются только приложением и не нужны для JDO.

Этот класс также определяет поле id, Long аннотированное и как @Persistent, и как @PrimaryKey. Хранилище данных App Engine знает о ключах элементов и может представлять ключ объекту несколькими способами. В данном случае ключ поля – длинное целое число, которое устанавливается автоматически при сохранении объекта как уникальный численный идентификатор.

Дополнительные сведения об аннотациях JDO см. в статье Определение классов данных.

PersistenceManagerFactory

Все запросы, использующие хранилище данных, создают новые экземпляры класса PersistenceManager. Это делается с помощью экземпляра класса PersistenceManagerFactory.

Экземпляр PersistenceManagerFactory инициализируется некоторое время. К счастью, для приложения нужен только один экземпляр, который можно хранить в статической переменной, которой пользуются многие запросы и классы. Это нетрудно сделать, создав отдельный класс-оболочку для статического экземпляра.

Создайте новый класс PMF в пакете guestbook (файл PMF.java в каталоге src/guestbook/) и поместите туда следующее содержание:

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

Создание и сохранение объектов

После настройки DataNucleus и класса Greeting логика обработки форм может хранить новые приветствия в хранилище данных.

Измените src/guestbook/SignGuestbookServlet.java указанным образом, чтобы он выглядел так:

package guestbook;

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

import guestbook.Greeting;
import guestbook.PMF;

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

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

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

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

        resp.sendRedirect("/intl/ru/guestbook.jsp");
    }
}

Этот код создает новый экземпляр Greeting, вызывая конструктор. Для сохранения экземпляра в хранилище данных он создает PersistenceManager с помощью PersistenceManagerFactory и передает экземпляр методу makePersistent() PersistenceManager. Там его принимают аннотации и улучшение байтового кода. После возврата makePersistent() новый объект сохраняется в хранилище данных.

Запросы с JDOQL

Стандарт JDO определяет механизм опрашивания постоянных объектов под названием JDOQL. JDOQL можно использовать для выполнения запросов элементов в хранилище данных App Engine и возврата результатов в виде объектов, улучшенных в отношении JDO.

Для этого примера мы все упростим, записав код запроса непосредственно в guestbook.jsp. Для большего приложения может быть нужно передать логику запроса в другой класс.

Измените war/guestbook.jsp и добавьте указанные строки, чтобы получить такой результат:

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

<html>
  <body>

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

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

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

  </body>
</html>

Для подготовки запроса вызывается метод newQuery() экземпляра PersistenceManager с текстом запроса в виде строки. Метод возвращает объект запроса. Метод объекта запроса execute() выполняет запрос и возвращает List<> объектов результатов соответствующего типа. Строка запроса должна включать полное название класса, который надо опрашивать, включая название пакета.

Снова соберите проект и перезапустите сервер. Зайдите на http://localhost:8080/. Введите приветствие и отправьте его. Приветствие появляется над формой. Введите еще одно приветствие и отправьте его. Отображаются оба приветствия. Попробуйте выйти и войти с помощью ссылок и попробуйте отправлять сообщения при выполненном входа и без такового.

Совет. В настоящем приложении стоит переводить символы HTML, отображая отправленное пользователями содержание, такое как приветствия в этом приложении. Библиотека стандартных тегов страниц JavaServer (JSTL) включает процедуры для этого. App Engine включает JSTL (и другие связанные с JSP JAR выполнения), поэтому включать их в приложение не нужно. Найдите функцию escapeXml в библиотеке тегов http://java.sun.com/jsp/jstl/functions. Дополнительные сведения см. в Учебнике Sun J2EE 1.4.

Введение в JDOQL

Наша гостевая книга отображает все сообщения, отправленные в систему. Она отображает их в порядке создания. Если в ней будет много сообщений, будет полезно отображать только свежие сообщения, показывая последнее сверху. Это можно сделать, изменив запрос к хранилищу данных.

Запрос выполняется с интерфейсом JDO с помощью JDOQL, сходного с SQL языка запросов для получения объектов данных. На нашей странице JSP следующая строка определяет строку запроса JDOQL:

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

Другими словами, строка запроса JDOQL – это следующее:

select from guestbook.Greeting

Этот запрос спрашивает у базы данных все сохраненные экземпляры класса Greeting.

Запрос может фильтровать результаты, включая критерии, которым должны соответствовать свойства объекта, чтобы он попал в результаты. Например, для получения только объектов Greeting, свойство author которых – alfred@example.com, следует использовать такой запрос:

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

Запрос может указывать (в терминах значений свойств) порядок, в котором следует вернуть результаты. Для получения всех объектов Greeting в порядке, обратном отправке (с самых новых к самым старым) следует использовать следующий запрос:

select from guestbook.Greeting order by date desc

Запрос может ограничивать результаты определенным диапазоном. Для получения 5 последних приветствий следует использовать order by и range вместе, следующим образом:

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

Сделаем это для приложения гостевой книги. В guestbook.jsp, замените определение query следующим:

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

Отправьте в гостевую книгу более 5 сообщений. Отображаются только последние 5, в обратном хронологическом порядке.

Дополнительные сведения о запросах и JDOQL см. в статье Запросы и индексы.

Далее...

Любое веб-приложение возвращает динамически созданный HTML из кода приложения посредством шаблонов или другого механизма. Большинство веб-приложений также должны выводить статическое содержание, например изображения, таблицы CSS-стилей и файлы JavaScript. Для повышения эффективности App Engine обрабатывает статические файлы иначе, чем код и данные приложения. Создадим таблицу стилей CSS для этого приложения в виде статического файла.

Переходите к разделу Использование статических файлов.