Using Cloud Datastore with Java

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/2-structured-data directory, enter the following command to start a local web server. Replace [YOUR_PROJECT_ID] with your GCP project ID:

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

  1. Deploy the app.

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

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

When you update your app, you can deploy the updated version using the same command you used when you first deployed the app. 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. All of these app versions and VM instances are billable resources.

You can reduce costs by deleting the non-default versions of your app. 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 has exchanged 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 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
}

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 starts with an IncompleteKey, which gets assigned a value when the entity is added to Cloud Datastore. 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
}

Read from Cloud Datastore

The readBook method converts a book's ID 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 by using get methods for each type

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();
}

Update books

Updating books 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
}

Delete books

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

@Override
public void deleteBook(Long bookId) {
  Key key = keyFactory.newKey(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) {
  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);
  }
}

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. The Cloud Datastore emulator suggests indexes that your app needs.

To create these indexes:

gcloud datastore indexes create index.yaml
Was this page helpful? Let us know how we did:

Send feedback about...