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 Cloud Datastore.

This page is part of a multi-page tutorial. To start from the beginning and read the setup instructions, go to Java Bookshelf app.

Configuring settings

The pom.xml file 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 is 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 the following command to start a local web server:

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

Now you can browse the app's web pages to 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.

App structure

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

Bookshelf app deployment process and structure

Understanding the code

This section walks you through the app's code and explains how it works.

Interact with Cloud Datastore

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

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

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

Create 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 type and sets user-supplied values for author, description, publish date, and title. The key is assigned to the entity when the entity is added to Cloud Datastore.

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

Read 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 by using getProperty methods for each type, similar to the way setProperty is used during entity creation. 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();
}

Update books

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

Delete books

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

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

List books

The listBooks method returns a list of books, 10 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);
  }
}

Index

Cloud Datastore creates an index for each of your properties by default, although you can change this behavior. Some queries need more than a single property in the index to complete, such as Query by user and 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...