A API Java Persistence (JPA) é uma interface padrão para aceder a bases de dados em Java, que fornece um mapeamento automático entre classes Java e tabelas de bases de dados. Existe um plug-in de código aberto disponível para usar o JPA com o Datastore, e esta página fornece informações sobre como começar a usá-lo.
Aviso: acreditamos que a maioria dos programadores terá uma melhor experiência com a API Datastore de baixo nível ou uma das APIs de código aberto desenvolvidas especificamente para o Datastore, como o Objectify. O JPA foi concebido para utilização com bases de dados relacionais tradicionais e, por isso, não tem forma de representar explicitamente alguns dos aspetos do Datastore que o tornam diferente das bases de dados relacionais, como grupos de entidades e consultas de antecessores. Isto pode levar a problemas subtis difíceis de compreender e corrigir.
A versão 1.x do plug-in está incluída no SDK Java do App Engine, que implementa a versão 1.0 da JPA. A implementação baseia-se na versão 1.1 da plataforma de acesso DataNucleus.
Nota: as instruções nesta página aplicam-se à versão 1 da JPA, que usa a versão 1.x do plug-in DataNucleus para o App Engine. A versão 2.x do plug-in DataNucleus também está disponível, o que lhe permite usar o JPA 2.0. O plug-in 2.x oferece várias novas APIs e funcionalidades; no entanto, a atualização não é totalmente retrocompatível com a versão 1.x. Se recompilar uma aplicação com a JPA 2.0, tem de atualizar e testar novamente o código. Para mais informações sobre a nova versão, consulte o artigo Usar a JPA 2.0 com o App Engine.
Configurar JPA
Para usar a JPA para aceder ao arquivo de dados, uma app do App Engine precisa do seguinte:
- Os JARs da JPA e do armazenamento de dados têm de estar no diretório
war/WEB-INF/lib/
da app. - Tem de existir um ficheiro de configuração denominado
persistence.xml
no diretóriowar/WEB-INF/classes/META-INF/
da app, com a configuração que indica ao JPA para usar o datastore do App Engine. - O processo de compilação do projeto tem de executar um passo de "melhoramento" pós-compilação nas classes de dados compiladas para as associar à implementação da JPA.
Copiar os JARs
Os JARs da JPA e do armazenamento de dados estão incluídos no SDK Java do App Engine. Pode encontrá-los no diretório appengine-java-sdk/lib/user/orm/
.
Copie os ficheiros JAR para o diretório war/WEB-INF/lib/
da sua aplicação.
Certifique-se de que o appengine-api.jar
também está no diretório war/WEB-INF/lib/
. (Pode já ter copiado este código quando criou o projeto.) O plug-in DataNucleus do App Engine usa este JAR para aceder ao datastore.
Criar o ficheiro persistence.xml
A interface JPA precisa de um ficheiro de configuração denominado
persistence.xml
no diretório
war/WEB-INF/classes/META-INF/
da aplicação. Pode criar este ficheiro
nesta localização diretamente ou fazer com que o processo de compilação copie este ficheiro de um
diretório de origem.
Crie o ficheiro 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>
Política de leitura do armazenamento de dados e prazo da chamada
Conforme descrito na página
Consultas
Datastore, pode definir a política de leitura (consistência forte vs. consistência
eventual) e o prazo da chamada datastore para um
EntityManagerFactory
no ficheiro persistence.xml
.
Estas definições são colocadas no elemento <persistence-unit>
. Todas as chamadas feitas com uma determinada instância EntityManager
usam a configuração selecionada quando o gestor foi criado pelo EntityManagerFactory
. Também pode substituir estas opções para um
indivíduo Query
(descrito abaixo).
Para definir a política de leitura, inclua uma propriedade com o nome
datanucleus.appengine.datastoreReadConsistency
. Os valores possíveis são EVENTUAL
(para leituras com consistência eventual) e STRONG
(para leituras com consistência forte). Se não for especificado, o valor predefinido é STRONG
.
<property name="datanucleus.appengine.datastoreReadConsistency" value="EVENTUAL" />
Pode definir prazos separados de chamadas da base de dados para leituras e escritas. Para leituras, use a propriedade padrão JPA javax.persistence.query.timeout
. Para escritas, use
datanucleus.datastoreWriteTimeout
. O valor é um período,
em milissegundos.
<property name="javax.persistence.query.timeout" value="5000" /> <property name="datanucleus.datastoreWriteTimeout" value="10000" />
Se quiser usar transações entre grupos (XG), adicione a seguinte propriedade:
<property name="datanucleus.appengine.datastoreEnableXGTransactions" value="true" />
Pode ter vários elementos <persistence-unit>
no mesmo ficheiro persistence.xml
, usando diferentes atributos name
, para usar instâncias EntityManager
com diferentes configurações na mesma app. Por exemplo, o seguinte ficheiro persistence.xml
estabelece dois conjuntos de configuração, um denominado "transactions-optional"
e outro denominado "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"/> <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>
Consulte a secção Obter uma instância do EntityManager abaixo para ver informações sobre a criação de um EntityManager
com um conjunto de configurações nomeado.
Pode substituir a política de leitura e o prazo de chamada para um objeto Query
individual. Para substituir a política de leitura de um Query
,
chame o respetivo método setHint()
da seguinte forma:
Query q = em.createQuery("select from " + Book.class.getName()); q.setHint("datanucleus.appengine.datastoreReadConsistency", "EVENTUAL");
Tal como acima, os valores possíveis são "EVENTUAL"
e
"STRONG"
.
Para substituir o limite de tempo de leitura, chame setHint()
da seguinte forma:
q.setHint("javax.persistence.query.timeout", 3000);
Não existe forma de substituir a configuração destas opções quando obtém entidades por chave.
Melhorar as classes de dados
A implementação do DataNucleus da JPA usa um passo de "melhoramento" pós-compilação no processo de compilação para associar classes de dados à implementação da JPA.
Pode executar o passo de melhoramento em classes compiladas a partir da linha de comandos com o seguinte comando:
java -cp classpath org.datanucleus.enhancer.DataNucleusEnhancer class-files
O classpath tem de conter os JARs
datanucleus-core-*.jar
, datanucleus-jpa-*
,
datanucleus-enhancer-*.jar
, asm-*.jar
e
geronimo-jpa-*.jar
(em que *
é o número
da versão adequado de cada JAR) do diretório appengine-java-sdk/lib/tools/
, bem como todas as suas classes de dados.
Para mais informações sobre o melhorador de bytecode do DataNucleus, consulte a documentação do DataNucleus.
Obter uma instância de EntityManager
Uma app interage com a JPA através de uma instância da classe EntityManager
. Obtém esta instância ao instanciar e chamar um método numa instância da classe EntityManagerFactory
. A fábrica usa a configuração JPA (identificada pelo nome "transactions-optional"
) para criar instâncias EntityManager
.
Uma vez que uma instância EntityManagerFactory
demora a ser inicializada, é recomendável reutilizar uma única instância o máximo possível. Uma forma fácil de o fazer é criar uma classe wrapper singleton com uma instância estática, da seguinte forma:
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; } }
Dica: "transactions-optional"
refere-se ao nome do conjunto de configurações no ficheiro persistence.xml
. Se a sua app usar vários conjuntos de configuração, tem de estender este código para chamar Persistence.createEntityManagerFactory()
conforme pretendido. O seu código deve armazenar em cache uma instância singleton de cada EntityManagerFactory
.
A app usa a instância de fábrica para criar uma instância EntityManager
para cada pedido que acede ao armazenamento de dados.
import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import EMF; // ... EntityManager em = EMF.get().createEntityManager();
Usa a EntityManager
para armazenar, atualizar e eliminar objetos de dados, bem como para executar consultas da base de dados.
Quando terminar de usar a instância EntityManager
, tem de chamar o respetivo método close()
. É um erro usar a instância EntityManager
depois de chamar o respetivo método close()
.
try { // ... do stuff with em ... } finally { em.close(); }
Anotações de classe e campo
Cada objeto guardado pelo JPA torna-se uma entidade no arquivo de dados do 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 (com a capitalização preservada).
Para declarar uma classe Java como capaz de ser armazenada e obtida a partir do
armazenamento de dados com JPA, atribua à classe uma anotação @Entity
. Por
exemplo:
import javax.persistence.Entity; @Entity public class Employee { // ... }
Os campos da classe de dados que vão ser armazenados no arquivo de dados têm de ser de um tipo que seja mantido por predefinição ou declarado explicitamente como persistente.
Pode encontrar um gráfico que detalha o comportamento de persistência predefinido da JPA no
Website da DataNucleus. Para declarar explicitamente um campo como persistente, atribua-lhe uma anotação @Basic
:
import java.util.Date; import javax.persistence.Enumerated; import com.google.appengine.api.datastore.ShortBlob; // ... @Basic private ShortBlob data;
O tipo de um campo pode ser qualquer um dos seguintes:
- um dos tipos principais suportados pelo arquivo de dados
- uma coleção (como um
java.util.List<...>
) de valores de um tipo de arquivo de dados principal - uma instância ou uma coleção de instâncias de uma classe
@Entity
- Uma classe incorporada, armazenada como propriedades na entidade
Uma classe de dados tem de ter um construtor predefinido público ou protegido e um campo dedicado ao armazenamento da chave principal da entidade de armazenamento de dados correspondente. Pode escolher entre quatro tipos diferentes de campos de chave, cada um com um tipo de valor e anotações diferentes. (Consulte o artigo Criar dados: chaves para mais informações.) O campo de chave mais simples é um valor inteiro longo que é preenchido automaticamente pela JPA com um valor exclusivo em todas as outras instâncias da classe quando o objeto é guardado no armazenamento de dados pela primeira vez. As chaves de números inteiros longos usam uma anotação @Id
e uma anotação @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;
Segue-se um exemplo de uma classe de dados:
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; @Entity public 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 Date getHireDate() { return hireDate; } public void setHireDate(Date hireDate) { this.hireDate = hireDate; } }
Herança
O JPA suporta a criação de classes de dados que usam a herança. Antes de falarmos sobre como funciona a herança JPA no App Engine, recomendamos que leia a documentação do DataNucleus sobre este assunto e, em seguida, volte. Concluído? OK. A herança de JPA no App Engine funciona conforme descrito na documentação do DataNucleus, com algumas restrições adicionais. Vamos abordar estas restrições e, em seguida, apresentar alguns exemplos concretos.
A estratégia de herança "JOINED" permite dividir os dados de um único objeto de dados em várias "tabelas", mas, uma vez que o datastore do App Engine não suporta junções, a operação num objeto de dados com esta estratégia de herança requer uma chamada de procedimento remoto para cada nível de herança. Isto é potencialmente muito ineficiente, pelo que a estratégia de herança "JOINED" não é suportada em classes de dados.
Em segundo lugar, a estratégia de herança "SINGLE_TABLE" permite-lhe armazenar os dados de um objeto de dados numa única "tabela" associada à classe persistente na raiz da sua hierarquia de herança. Embora não existam ineficiências inerentes nesta estratégia, esta não é suportada atualmente. Podemos rever esta situação em versões futuras.
Agora, as boas notícias: as estratégias "TABLE_PER_CLASS" e "MAPPED_SUPERCLASS" funcionam conforme descrito na documentação do DataNucleus. Vejamos um exemplo:
Worker.java
import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.MappedSuperclass; @Entity @MappedSuperclass public abstract class Worker { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Key key; private String department; }
Employee.java
// ... imports ... @Entity public class Employee extends Worker { private int salary; }
Intern.java
import java.util.Date; // ... imports ... @Entity public class Intern extends Worker { private Date internshipEndDate; }
Neste exemplo, adicionámos uma anotação @MappedSuperclass
à declaração da classe Worker
. Isto indica ao JPA para armazenar todos os campos persistentes de Worker
nas entidades de armazenamento de dados das respetivas subclasses. A entidade da base de dados criada como resultado da chamada
persist()
com uma instância Employee
terá duas propriedades
denominadas "department" e "salary". A entidade da base de dados criada como resultado da chamada persist()
com uma instância Intern
tem duas propriedades denominadas "department" e "internshipEndDate". Não vão existir entidades do tipo "Worker" no arquivo de dados.
Agora, vamos tornar as coisas um pouco mais interessantes. Suponhamos que, além de ter Employee
e Intern
, também queremos uma especialização de Employee
que descreva os funcionários que saíram da empresa:
FormerEmployee.java
import 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; }
Neste exemplo, adicionámos uma anotação @Inheritance
à declaração da classe FormerEmployee
com o respetivo atributo strategy
definido como InheritanceType.TABLE_PER_CLASS
. Isto indica ao JPA que deve armazenar todos os campos persistentes de FormerEmployee
e das respetivas superclasses em entidades de armazenamento de dados correspondentes a instâncias de FormerEmployee
. A entidade da base de dados criada como resultado da chamada
persist()
com uma instância FormerEmployee
terá
três propriedades denominadas "department", "salary" e "lastDay". Nunca vai existir uma entidade do tipo "Employee" que corresponda a um FormerEmployee
, mas se chamar persist()
com um objeto cujo tipo de tempo de execução seja Employee
, vai criar uma entidade do tipo "Employee".
A combinação de relações com a herança funciona desde que os tipos declarados dos campos de relação correspondam aos tipos de tempo de execução dos objetos que está a atribuir a esses campos. Consulte a secção sobre Relações Polimórficas para mais informações. Esta secção contém exemplos de JDO, mas os conceitos e as restrições são os mesmos para JPA.
Funcionalidades não suportadas do JPA 1.0
As seguintes funcionalidades da interface JPA não são suportadas pela implementação do App Engine:
- Relações de muitos-para-muitos pertencentes e relações não pertencentes. Pode implementar relações não pertencentes através de valores-chave explícitos, embora a verificação de tipos não seja aplicada na API.
- Consultas "join". Não pode usar um campo de uma entidade secundária num filtro quando executar uma consulta no tipo principal. Tenha em atenção que pode testar o campo de relação do elemento principal diretamente numa consulta através de uma chave.
- Consultas de agregação (group by, having, sum, avg, max, min)
- Consultas polimórficas. Não pode executar uma consulta de uma classe para obter instâncias de uma subclasse. Cada classe é representada por um tipo de entidade separado no arquivo de dados.