Using Cloud Datastore with Java

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/2-structured-data directory, enter this command to start a local web server:

    mvn -Plocal clean jetty:run-exploded -DprojectID=[YOUR-PROJECT-ID]
  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 flexible environment

  1. Enter this command to deploy the app:

    mvn appengine:deploy -DprojectID=YOUR-PROJECT-ID
    
  2. In your web browser, enter this address. Replace [YOUR_PROJECT_ID] with your project ID:

    https://[YOUR_PROJECT_ID].appspot-preview.com
    

If you update your app, you can deploy the updated version by entering the same command you used to deploy the app the first time. The new deployment creates a new version of your app and promotes it to the default version. The older versions of your app remain, as do their associated VM instances. Be aware that all of these app versions and VM instances are billable resources.

You can reduce costs by deleting the non-default 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 obect 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 Datastore datastore;
private KeyFactory keyFactory;

public DatastoreDao() {
  datastore = DatastoreOptions.getDefaultInstance().getService(); // Authorized Datastore service
  keyFactory = datastore.newKeyFactory().setKind("Book2");      // Is used for creating keys later
}

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 starts with an IncompleteKey, which gets assigned a value when the entity is added to the data store. The code builds the entity according to the Builder design pattern by making repeated calls to the set method.

@Override
public Long createBook(Book book) {
  IncompleteKey key = keyFactory.newKey();          // Key will be assigned once written
  FullEntity<IncompleteKey> incBookEntity = Entity.newBuilder(key)  // Create the Entity
      .set(Book.AUTHOR, book.getAuthor())           // Add Property ("author", book.getAuthor())
      .set(Book.DESCRIPTION, book.getDescription())
      .set(Book.PUBLISHED_DATE, book.getPublishedDate())
      .set(Book.TITLE, book.getTitle())
      .build();
  Entity bookEntity = datastore.add(incBookEntity); // Save the Entity
  return bookEntity.getKey().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 get method, which returns an Entity object.

@Override
public Book readBook(Long bookId) {
  Entity bookEntity = datastore.get(keyFactory.newKey(bookId)); // Load an Entity for Key(id)
  return entityToBook(bookEntity);
}

The entityToBook method converts the entity returned from Cloud Datastore to a Book object using get methods for each type, like set, take a String as a property name.

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

Updating books

Update just requires calling the update method with an Entity.

@Override
public void updateBook(Book book) {
  Key key = keyFactory.newKey(book.getId());  // From a book, create a Key
  Entity entity = Entity.newBuilder(key)         // Convert Book to an Entity
      .set(Book.AUTHOR, book.getAuthor())
      .set(Book.DESCRIPTION, book.getDescription())
      .set(Book.PUBLISHED_DATE, book.getPublishedDate())
      .set(Book.TITLE, book.getTitle())
      .build();
  datastore.update(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.newKey(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) {
  Cursor startCursor = null;
  if (startCursorString != null && !startCursorString.equals("")) {
    startCursor = Cursor.fromUrlSafe(startCursorString);    // Where we left off
  }
  Query<Entity> query = Query.newEntityQueryBuilder()       // Build the Query
      .setKind("Book2")                                     // We only care about Books
      .setLimit(10)                                         // Only show 10 at a time
      .setStartCursor(startCursor)                          // Where we left off
      .setOrderBy(OrderBy.asc(Book.TITLE))                  // Use default Index "title"
      .build();
  QueryResults<Entity> resultList = datastore.run(query);   // Run the query
  List<Book> resultBooks = entitiesToBooks(resultList);     // Retrieve and convert Entities
  Cursor cursor = resultList.getCursorAfter();              // Where to start next time
  if (cursor != null && resultBooks.size() == 10) {         // Are we paging? Save Cursor
    String cursorString = cursor.toUrlSafe();               // 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. The Datastore emulator will suggest indexes that your app will need.

Create these indexes now:

gcloud datastore create-indexes index.yaml

Send feedback about...