Using Cloud Datastore with Java for the App Engine Standard Environment

This part of the Bookshelf tutorial for Java shows how to create, read, update, and delete structured data in Google Cloud Datastore.

This page is part of a multi-page tutorial. To start from the beginning and see instructions for setting up, go to Java Bookshelf App.

Configuring settings

The pom.xml holds configuration information for all parts of the Bookshelf tutorial. For this part of the tutorial, you don't have to add any configuration information. The only requirement is that the bookshelf.storageType property be set to datastore, which is already done for you.

Running the app on your local machine

To run the app locally:

  1. In the getting-started-java/bookshelf-standard/2-structured-data directory, enter this command to start a local web server:

    mvn -Plocal clean appengine:devserver
  2. In your web browser, navigate to http://localhost:8080

Now you can browse the app's web pages and add, edit, and delete books.

Deploying the app to the App Engine standard environment

To deploy to the App Engine standard environment:

  1. Make sure you have fully exercised the Bookshelf application locally, especially creating at least one book and then clicking My Books. This creates a required Cloud Datastore index that is uploaded with Bookshelf. Note that building the application using clean deletes that local index, so be sure to do this on the build of the application that you are going to deploy.
  2. In the getting-started-java/bookshelf-standard/2-structured-data directory, enter this command to deploy the app:
    mvn appengine:update -Dappengine.appId=[YOUR-PROJECT-ID] -Dappengine.version=[YOUR-VERSION]
    Replace [YOUR-PROJECT-ID] with your project ID and [YOUR-VERSION] with your version, for example, 1, or 2, or some other or some other string value you want to use.
  3. In your web browser, enter this address:
    https://[YOUR-PROJECT-ID].appspot.com
    Replace [YOUR-PROJECT-ID] with your project ID.

After you update your app, you can redeploy the updated version by entering the same command you used to deploy the app the first time, specifying the same project ID and version: this overwrites the currently deployed app. If you specify a different version string in the update command line, the new deployment creates a new version of your app and promotes it to be the currently serving version.

You can reduce costs by deleting the non-serving versions of your app.

To delete an app version:

  1. In the GCP Console, go to the App Engine Versions page.

    Go to the Versions page

  2. Click the checkbox next to the non-default app version you want to delete.
  3. Click the Delete button at the top of the page to delete the app version.

For complete information about cleaning up billable resources, see the Cleaning up section in the final step of this tutorial.

Application structure

The following diagram shows the application's components and how they fit together.

Bookshelf app deployment process and structure

Understanding the code

This section walks you through the application code and explains how it works.

Interacting with Cloud Datastore

The app uses an authorized Datastore service object to interact with Cloud Datastore. This is a heavyweight object, as it has exchanged credentials for a token that it can use to access the Datastore APIs. The app also creates a KeyFactory object of kind Book. The book is the only kind of data that this app stores; in general, however, you can store as many kinds of data as you like. Cloud Datastore supports a rich assortment of datatypes.

private DatastoreService datastore;
private static final String BOOK_KIND = "Book2";

public DatastoreDao() {
  datastore = DatastoreServiceFactory.getDatastoreService(); // Authorized Datastore service
}

Creating books

To save a book, the app stores an entity in Cloud Datastore. An entity has a key and a collection of name/value pairs. The createBook method creates a new entity of BOOK kind and sets user-supplied values for author, description, publish date, and title. Notice that the key is assigned to the entity when the entity is added to the data store.

@Override
public Long createBook(Book book) {
  Entity incBookEntity = new Entity(BOOK_KIND);  // Key will be assigned once written
  incBookEntity.setProperty(Book.AUTHOR, book.getAuthor());
  incBookEntity.setProperty(Book.DESCRIPTION, book.getDescription());
  incBookEntity.setProperty(Book.PUBLISHED_DATE, book.getPublishedDate());
  incBookEntity.setProperty(Book.TITLE, book.getTitle());

  Key bookKey = datastore.put(incBookEntity); // Save the Entity
  return bookKey.getId();                     // The ID of the Key
}

Reading from Cloud Datastore

The readBook method takes a book's ID and converts it to a key. Then it passes the key to the getProperty method, which returns an Entity object.

@Override
public Book readBook(Long bookId) {
  try {
    Entity bookEntity = datastore.get(KeyFactory.createKey(BOOK_KIND, bookId));
    return entityToBook(bookEntity);
  } catch (EntityNotFoundException e) {
    return null;
  }
}

The entityToBook method converts the entity returned from Cloud Datastore to a Book object using getProperty methods for each type, similar to the way setProperty is used during entity creation. Note that property names are String values.

public Book entityToBook(Entity entity) {
  return new Book.Builder()                                     // Convert to Book form
      .author((String) entity.getProperty(Book.AUTHOR))
      .description((String) entity.getProperty(Book.DESCRIPTION))
      .id(entity.getKey().getId())
      .publishedDate((String) entity.getProperty(Book.PUBLISHED_DATE))
      .title((String) entity.getProperty(Book.TITLE))
      .build();
}

Updating books

Update just requires calling the put method with an Entity.

@Override
public void updateBook(Book book) {
  Key key = KeyFactory.createKey(BOOK_KIND, book.getId());  // From a book, create a Key
  Entity entity = new Entity(key);         // Convert Book to an Entity
  entity.setProperty(Book.AUTHOR, book.getAuthor());
  entity.setProperty(Book.DESCRIPTION, book.getDescription());
  entity.setProperty(Book.PUBLISHED_DATE, book.getPublishedDate());
  entity.setProperty(Book.TITLE, book.getTitle());

  datastore.put(entity);                   // Update the Entity
}

Deleting with Datastore requires calling the delete method with a Key.

Deleting books

@Override
public void deleteBook(Long bookId) {
  Key key = KeyFactory.createKey(BOOK_KIND, bookId);        // Create the Key
  datastore.delete(key);                      // Delete the Entity
}

Listing books

The listBooks method returns a list of books, ten at a time, along with a URL safe Cursor:

@Override
public Result<Book> listBooks(String startCursorString) {
  FetchOptions fetchOptions = FetchOptions.Builder.withLimit(10); // Only show 10 at a time
  if (startCursorString != null && !startCursorString.equals("")) {
    fetchOptions.startCursor(Cursor.fromWebSafeString(startCursorString)); // Where we left off
  }
  Query query = new Query(BOOK_KIND) // We only care about Books
      .addSort(Book.TITLE, SortDirection.ASCENDING); // Use default Index "title"
  PreparedQuery preparedQuery = datastore.prepare(query);
  QueryResultIterator<Entity> results = preparedQuery.asQueryResultIterator(fetchOptions);

  List<Book> resultBooks = entitiesToBooks(results);     // Retrieve and convert Entities
  Cursor cursor = results.getCursor();              // Where to start next time
  if (cursor != null && resultBooks.size() == 10) {         // Are we paging? Save Cursor
    String cursorString = cursor.toWebSafeString();               // Cursors are WebSafe
    return new Result<>(resultBooks, cursorString);
  } else {
    return new Result<>(resultBooks);
  }
}

Indexing

Datastore automatically creates an index for each of your properties by default, although it is possible to change this behavior. Some queries need more than a single property in the index to complete, such as the Query by user, ordered by Title query above. You can create indexes to support such queries manually or by using the development server to create indexes.

Was this page helpful? Let us know how we did:

Send feedback about...