My favorites | Português | Sign in

Como usar a JPA com o Google App Engine

A JPA (API persistente Java) é uma interface padrão para armazenar objetos de dados em um banco de dados relacional. O padrão define as interfaces para anotar objetos Java, recuperar objetos com consultas e interagir com um banco de dados usando transações. Um aplicativo que usa a interface JPA pode funcionar com bancos de dados diferentes sem usar qualquer código específico do fornecedor do banco de dados. A JPA facilita o transporte do seu aplicativo entre bancos de dados de fornecedores diferentes.

O SDK para Java do Google App Engine inclui uma implementação da JPA 1.0 para o armazenamento de dados dos Google App Engine. A implementação é baseada no DataNucleus Access Platform. Como a JPA apresenta uma interface padrão para interagir com bancos de dados relacionais e o armazenamento de dados do Google App Engine não é um banco de dados relacional, a implementação do Google App Engine simplesmente não consegue suportar alguns recursos da JPA. Fizemos o possível para chamar a atenção para esses recursos.

Para obter mais informações sobre a JPA, consulte a documentação do Access Platform 1.1. Em especial, consulte "Mapeamento da JPA" e "API da JPA".

Como configurar a JPA

Para usar a JPA para acessar o armazenamento de dados, o Google App Engine precisa do seguinte:

  • A JPA e os JARs do armazenamento de dados devem estar no diretório war/WEB-INF/lib/ do aplicativo.
  • Um arquivo de configuração chamado persistence.xml deve estar no diretório war/WEB-INF/classes/META-INF/ do aplicativo, com uma configuração que diz para a JPA usar o armazenamento de dados do Google App Engine.
  • O processo de criação do projeto deve realizar uma etapa de "aprimoramento" pós-compilatório nas classes de dados compilados para associá-las à implementação JPA.

Se você estiver usando o plug-in do Google para o Eclipse não precisa se preocupar com o primeiro e o terceiro itens. O novo assistente de projetos coloca a JPA e os JARs do armazenamento de dados no lugar certo e o processo de criação realiza a etapa de "aprimoramento" automaticamente. Ainda assim, você deve criar manualmente o persistence.xml e colocá-lo no diretório war/WEB-INF/classes/META-INF/. Logo o plug-in será atualizado para fazer isso automaticamente também.

Se estiver usando o Apache Ant para criar o seu projeto, você pode usar uma tarefa Ant incluída no SDK para realizar a etapa de aprimoramento. Copie os JARs e crie o arquivo de configuração quando definir o seu projeto. Consulte Como usar o Apache Ant para obter mais informações sobre a tarefa Ant.

Como copiar os JARs

A JPA e os JARs do armazenamento de dados estão incluídos no SDK para Java do Google App Engine. Eles estão no diretório appengine-java-sdk/lib/user/orm/.

Copie os JARs para o diretório war/WEB-INF/lib/ do seu aplicativo.

Certifique-se de que o appengine-api.jar também esteja no diretório war/WEB-INF/lib/. (isso já pode ter sido feito ao criar o projeto). O plug-in do Google App Engine para DataNucleus usa esse JAR para acessar o armazenamento de dados.

Criação do arquivo persistence.xml

A interface JPA precisa de um arquivo de configuração chamado persistence.xml no diretório war/WEB-INF/classes/META-INF/ do aplicativo. Ele pode ser criado diretamente nesse local ou o processo de criação pode copiá-lo de um diretório de origem.

Crie o arquivo com o seguinte conteúdo:

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

Como aprimorar as classes de dados

A implementação da JPA do DataNucleus usa uma etapa de "aprimoramento" pós-compilatório no processo de criação para associar classes de dados à implementação JPA.

Se estiver usando o Apache Ant, o SDK inclui uma tarefa Ant para isso. Consulte Como usar o Apache Ant para obter mais informações sobre a tarefa Ant.

A etapa de aprimoramento pode ser realizada em classes compiladas a partir da linha de comando com o seguinte comando:

java -cp classpath org.datanucleus.enhancer.DataNucleusEnhancer class-files

O caminho de classe deve conter os JARs datastore-core-*.jar, datanucleus-enhancer-*.jar, asm-*.jar e geronimo-jpa-*.jar (em que * é o número de versão correto de cada JAR) do diretório appengine-java-sdk/lib/tools/, bem como todas as suas classes de dados.

Para obter mais informações sobre o aprimorador bytecode do DataNucleus, consulte a documentação do DataNucleus.

Como obter uma instância EntityManager

Um aplicativo interage com a JPA usando uma instância da classe EntityManager. Essa instância pode ser obtida ao instanciar e chamar um método em uma instância da classe EntityManagerFactory. A fábrica utiliza a configuração da JPA (identificada pelo nome "transactions-optional") para criar instâncias EntityManager.

Como uma instância EntityManagerFactory demora para ser inicializada, é recomendável reutilizar uma única instância o máximo possível. Uma maneira fácil de fazer isso é criando uma única classe empacotadora com uma instância estática, conforme mostrado abaixo:

EMF.java

import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;

public final class EMF {
    private static final EntityManagerFactory emfInstance =
        Persistence.createEntityManagerFactory("transactions-optional");

    private EMF() {}

    public static EntityManagerFactory get() {
        return emfInstance;
    }
}

O aplicativo usa a instância factory para criar uma instância EntityManager para cada solicitação que acessa o armazenamento de dados.

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;

import EMF;

// ...
    EntityManager em = EMF.get().createEntityManager();

A EntityManager pode ser usada para armazenar, atualizar e excluir objetos de dados, e realizar consultas ao armazenamento de dados

Quando terminar de trabalhar com a instância EntityManager, chame o método close(). É um erro usar a instância EntityManager depois de chamar o seu método close().

    try {
        // ... do stuff with em ...
    } finally {
        em.close();
    }

Anotações de classe e campo

Cada objeto salvo pela JPA se torna uma entidade no armazenamento de dados do Google App Engine. O tipo da entidade é derivado do nome simples da classe (sem o nome do pacote). Cada campo persistente da classe representa uma propriedade da entidade, com o nome da propriedade igual ao nome do campo (mantendo a diferenciação de maiúsculas e minúsculas).

Para declarar que uma classe Java é capaz de ser armazenada e recuperada do armazenamento de dados com a JPA, forneça uma anotação @Entity à classe. Por exemplo:

import javax.persistence.Entity;

@Entity
public class Employee {
    // ...
}

Campos da classe de dados que serão armazenados no armazenamento de dados devem ser de um tipo persistente por padrão ou declarado explicitamente como persistente. O site do DataNucleus possui uma tabela com detalhes sobre o comportamento persistente padrão da JPA. Para declarar um campo explicitamente como persistente, você normalmente forneceria uma anotação @Basic a ele, mas no momento há um bug impedindo que isso funcione. Portanto, para contornar a situação, use uma anotação @Enumerated.

import java.util.Date;
import javax.persistence.Enumerated;

import com.google.appengine.api.datastore.ShortBlob;

// ...
    @Enumerated
    private ShortBlob data;

O tipo de um campo pode ser um dos relacionados abaixo:

  • um dos tipos principais suportados pelo armazenamento de dados
  • uma Collection (como uma java.util.List<...>) de valores de um tipo principal do armazenamento de dados
  • uma instância ou coleção de instâncias de uma classe @Entity
  • uma classe integrada, armazenada na entidade como propriedades

Uma classe de dados deve ter um construtor padrão público ou protegido e um campo dedicado ao armazenamento da chave principal da entidade correspondente do armazenamento de dados. Você pode escolher entre quatro tipos diferentes de campos chave, cada um usando um tipo de valor e anotações diferentes (consulte Como criar dados: chaves) para obter mais informações). O campo de chave mais simples é um valor inteiro long preenchido automaticamente pela JPA com um valor exclusivo em todas as outras instâncias da classe quando o objeto é salvo no armazenamento de dados pela primeira vez. A chaves de inteiros long usam as anotações @Id e @GeneratedValue(strategy = GenerationType.IDENTITY):

import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

// ...
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

Este é um exemplo de classe de dados:

import java.util.Date;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Employee {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String firstName;

    private String lastName;

    private Date hireDate;

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

    public String getId() {
        return id;
    }
    public void setId(String id) {
        this.id = id;
    }

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

Recursos da JPA não suportados

Os recursos abaixo da interface JPA não são suportados pela implementação do Google App Engine:

  • Relacionamentos proprietários de vários-para-vários e relacionamentos não proprietários. Os relacionamentos não proprietários podem ser implementados usando valores Key explícitos, embora a verificação de tipos não seja aplicada na API.
  • Consultas "associadas". Não é possível usar um campo de uma entidade filho em um filtro ao executar uma consulta no tipo pai. Observe que você pode testar o campo de relacionamento do pai diretamente na consulta usando uma chave.
  • Consultas de agregação (group by, having, sum, avg, max, min)
  • Consultas polimórficas. Não é possível consultar uma classe para obter instâncias de uma subclasse. Cada classe é representada no armazenamento de dados por um tipo de entidade separado.