Java Datastore API für die Google App Engine
1 Kommentar(e). Veröffentlicht von Christian Ullenboom am Dienstag, Juli 28, 2009.Um in der Google Cloud Daten zu speichern bietet Google drei API an: JPA, JDO und eine Low-Level-API. Infos dazu gibt liefert http://code.google.com/intl/de/appengine/docs/java/datastore/. JPA und JDO basieren im Kern auf der Low-Level API, die auf die http://en.wikipedia.org/wiki/BigTable zurückgreift.
Für JPA und JDO gibt es selbst von Google viele Beispiele, aber die Low-Level-API ist nicht so gut dokumentiert und selbst das Beispielprogramm in der JavaDoc enthält Fehler. Zeit daher, ein sehr einfaches Beispiel mit einer 1:n Relationen anzugehen.
Im Mittelpunkt der API steht der DatastoreService, der ein bisschen an den EntityManager von JPA erinnert. Er bietet Methoden für die CRUD-Operationen. Mein Beispiel geht schon ein bisschen “pseudo-ORM” an die Aufgabe ran, einer Person Nachrichten zuordnen zu können:
Die Personen-Klasse:
package com.tutego.server.entity;
import java.util.ArrayList;
import java.util.List;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Query;
public class Person
{
private final static String ENTITY_NAME = "Person";
Entity personEntity;
public enum Gender
{
MALE, FEMALE
}
public Person()
{
personEntity = new Entity( ENTITY_NAME );
}
private Person( Key key )
{
try
{
personEntity = DatastoreServiceFactory.getDatastoreService().get( key );
}
catch ( EntityNotFoundException e )
{
}
}
private Person( Entity entity )
{
personEntity = entity;
}
public static Person get( Key key )
{
return new Person( key );
}
public void setUsername( String username )
{
personEntity.setProperty( "username", username );
}
public String getUsername()
{
return personEntity.getProperty( "username" ).toString();
}
public void setGender( Gender gender )
{
personEntity.setProperty( "gender", gender.toString() );
}
public Gender getGender()
{
return Gender.valueOf( personEntity.getProperty( "gender" ).toString() );
}
public Key put()
{
return DatastoreServiceFactory.getDatastoreService().put( personEntity );
}
public static void deleteAll()
{
Query deleteAllQuery = new Query( ENTITY_NAME );
for ( Entity entity : DatastoreServiceFactory.getDatastoreService().prepare( deleteAllQuery ).asIterable() )
DatastoreServiceFactory.getDatastoreService().delete( entity.getKey() );
}
private static List<Person> executeQuery( Query query )
{
List<Person> result = new ArrayList<Person>();
for ( Entity entity : DatastoreServiceFactory.getDatastoreService().prepare( query ).asIterable() )
result.add( new Person( entity ) );
return result;
}
public static List<Person> findAllPersons()
{
Query query = new Query( ENTITY_NAME );
return executeQuery( query );
}
public static List<Person> findPersonByGender( Gender gender )
{
Query query = new Query( ENTITY_NAME );
query.addFilter( "gender", Query.FilterOperator.EQUAL, gender.toString() );
return executeQuery( query );
}
@Override
public String toString()
{
return String.format( "Person[%s,%s]", getUsername(), getGender() );
}
}
Die Nachrichten-Klasse:
package com.tutego.server.entity;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Query;
public class Message
{
private final static String ENTITY_NAME = "Message";
private Entity messageEntity;
public Message()
{
messageEntity = new Entity( ENTITY_NAME );
}
private Message( Key key )
{
try
{
messageEntity = DatastoreServiceFactory.getDatastoreService().get( key );
}
catch ( EntityNotFoundException e )
{
}
}
private Message( Entity entity )
{
messageEntity = entity;
}
public static Message get( Key key )
{
return new Message( key );
}
public void setText( String text )
{
messageEntity.setProperty( "text", text );
}
public String getText()
{
return messageEntity.getProperty( "text" ).toString();
}
public void setCreationTime( Date d )
{
messageEntity.setProperty( "creationtime", "" + d.getTime() );
}
public Date getCreationTime()
{
return new Date( Long.parseLong( messageEntity.getProperty( "creationtime" ).toString() ) );
}
public void setReceiver( Person p )
{
messageEntity.setProperty( "person_fk", p.personEntity.getKey() );
}
public Key put()
{
return DatastoreServiceFactory.getDatastoreService().put( messageEntity );
}
private static List<Message> executeQuery( Query query )
{
List<Message> result = new ArrayList<Message>();
for ( Entity entity : DatastoreServiceFactory.getDatastoreService().prepare( query ).asIterable() )
result.add( new Message( entity ) );
return result;
}
public static List<Message> findMessagesForPerson( Person p )
{
Query query = new Query( ENTITY_NAME );
query.addFilter( "person_fk", Query.FilterOperator.EQUAL, p.personEntity.getKey() );
return executeQuery( query );
}
@Override
public String toString()
{
return String.format( "Message[%s,%s]", getCreationTime(), getText() );
}
}
Getestet werden soll das ganze in einer einfachen Server-Funktion:
StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter( sw );
// Insert new entity
Person p1 = new Person();
p1.setUsername( "chris" );
p1.setGender( Person.Gender.MALE );
Key key1 = p1.put();
out.println( "* Key für erste Person " + p1 );
out.println( KeyFactory.keyToString( key1 ) );
Person p2 = new Person();
p2.setUsername( "pallas" );
p2.setGender( Person.Gender.FEMALE );
p2.put();
Person p3 = new Person();
p3.setUsername( "tina" );
p3.setGender( Person.Gender.FEMALE );
p3.put();
// Search for entity with a given key
Person p = Person.get( key1 );
out.println( "* Suche mit Schlüssel " + key1 );
out.println( p.getUsername() );
// Query
List<Person> findAll = Person.findAllPersons();
out.println( "* Alle Personen" );
out.println( findAll.toString() );
// Query
List<Person> females = Person.findPersonByGender( Gender.FEMALE );
out.println( "* Alle Frauen:" );
out.println( females.toString() );
Message msg1 = new Message();
msg1.setText( "Hallo Maus" );
msg1.setCreationTime( new Date(1) );
msg1.setReceiver( p );
msg1.put();
Message msg2 = new Message();
msg2.setText( "Hallo Ratte" );
msg2.setCreationTime( new Date(2) );
msg2.setReceiver( p );
msg2.put();
out.println( "* Alle Nachrichten für " + p );
out.println( Message.findMessagesForPerson( p ) );
out.println( "\n" );
// Clean up
Person.deleteAll();
out.flush();
return sw.toString().replace( "\n", "<br/>" );
Als Ergebnis kommt HTML zurück, was der Client zum Testen anschauen kann.
Labels: GWT

Kleine Optimierung noch:
public static void deleteAll()
{
Query deleteAllQuery = new Query( ENTITY_NAME );
List< Key > keys = new ArrayList< Key >();
for ( Entity entity : DatastoreServiceFactory.getDatastoreService().prepare( deleteAllQuery ).asIterable() )
keys.add( entity.getKey() );
DatastoreServiceFactory.getDatastoreService().delete( keys );
}