GAE: Tutorial: Code Lab Exercise 2: Transactions, Memcache and Relationships

Goal

This tutorial will demonstrate the following:

  • Memory Cache using Google Memory Caching Service
  • Handling Transactions
  • Model the relationships across the entities

This code lab is an extension to the previous code lab, adding transactions, caching & relationship management. This exercise builds on the previous product catalog, with entities Customer and Order added to demonstrate a sample store application.

Time for Completion

Approximately 45 minutes.

Prerequisites

Before you begin this tutorial, you should be familiar with:

  • Web application basics and deployment process
  • Servlet basics
  • GAE Datastore
  • Knowledge on XML-HTTP request
  • Working with Eclipse IDE

Index

This tutorial is divided into the following sections:

Code lab overview

  1. Code lab exercise is a web application template. This exercise will take you through the process of using memcache, transactions and modeling relationships.
  2. The idea of template based sample is to get hands-on experience with Google App Engine concepts. This exercise will cover all the steps required to create, deploy and monitor the application on GAE environment.
  3. The user interface is developed using HTML Java script. See the following picture to get an overview.

Setup: Download and import Code Lab Exercise 02

  1. Create the project CodeLabEx02-start as outlined in the exercise1. Make sure to create the package name as com.google.appengine.codelab. Make sure to import the template source for CodeLabEx02-start folder.

Working with the exercise

Note: Follow the instructions provided in CodeLabEx1 to create Product and Customer entities. This exercise will assume Product (eg: Mobile) and Customer (eg: John) entities present in datastore and continues further.

Using Memcache

Memory cache is used in web applications to speed up the performance by caching frequently used entities. Memcache may also be useful for caching temporary values, not backed up persistent store. The app can provide an expiration time when a value is stored. By default, the values get evicted due to memory pressure, and the least recently used get evicted first. Applications should put logic to retrieve data on cache misses.

We will upgrade Util class to using memcache feature.

  1. Modify the following lines to the Util class to get App Engine’s Memory Cache Service
  2. private static MemcacheService keycache = MemcacheServiceFactory.getMemcacheService();
  3. Add the following lines to addToCache() method at the end.
  4. keycache.put(key, entity);
  5. Add the following lines to deleteFromCache(Key key) method
  6. keycache.delete(key);
  7. Replace the return statement with the following line in getFromCache() method
  8. return (Entity) keycache.get(key);
  9. The cache methods are used for searching an entity based on the key property. We add the function getFromCache() in the utility method which is used to search for an entity. Add the following lines in the findEntity() method in Util.java. Replace the return statement.
  10. try {
      Entity entity = getFromCache(key);
      if (entity != null) {
        return entity;
      }
      return datastore.get(key);
    } catch (EntityNotFoundException ex) {
      return null;
    }

Handling transactions

With the App Engine datastore, every attempt to create, update or delete an entity happens in a transaction. A transaction ensures that every change made to the entity is saved to the datastore, or in the case of failure, none of the changes are made. This ensures consistency of data within the entity. Multiple actions can be performed on an entity within a single transaction using the transaction API. Changes to multiple entities within a transaction are done using entity groups.

We will add transaction feature to the application. Add the following lines in persistEntity() method in Util.java to enable transactions.

Key key = entity.getKey();
Transaction txn = datastore.beginTransaction();
try {
  datastore.put(entity);
  txn.commit();
} finally {
  if (txn.isActive()) {
    txn.rollback();
  } else {
    addToCache(key, entity);
  }
}

Observe that, the entities are added to the cache on successful transaction.

Modeling relationships

The following are the model entities in this exercise:

  • Product – Models the type of Product. This is more like a category and has a description. Product has one to many relationship with Item
  • Item – Model of an item. Item has many to one relationship with Product. Item creation requires the Product. To show the relationship, we add the product as a parent to the item. We also set the product name as an attribute in the item which acts as a foreign key to product entity.
  • Customer – Customer, to whom the order will be paced.
  • Order – This is an order for the Customer. Order has one to many relationship with LineItem and Many to one relationship with the Customer. As seen in item, here also we add the Customer as parent to the order and also set the customer name as an attribute to order entity to maintain the relationship.
  • Line Item – Every order has multiple lines. LineItem cannot exist alone. When an order is deleted, line item has to be deleted. This is called cascade delete. Here also, we add order as the parent for Line Item and thus create a hierarchy entity group of Customer – Order – LineItem. Every line item has an item associated with it. Because there can be only one parent for an entity, we use the Item name as an attribute.

In order to create a relationship, we will use

entity.setProperty(java.lang.String propertyName, java.lang.Object Key)
  1. entity – entity participating in the relationship property
  2. propertyName – foreign-key, as a property
  3. Key – entity key, that is part of the relation
  1. Create many to one relationshipOpen com.google.appengine.codelab.Item and replace return statement with the following code into the method createOrUpdateItem().
    Entity product = Product.getProduct(productName);
    Entity item = getSingleItem(itemName);
    if (item == null) {
      item = new Entity("Item", product.getKey());
      item.setProperty("name", itemName);
      item.setProperty("product", productName);
      item.setProperty("price", price);
      } else {
        if (price != null && !"".equals(price)) {
          item.setProperty("price", price);
        }
        if (productName != null && !"".equals(productName)) {
          item.setProperty("product", productName);
        }
      }
    Util.persistEntity(item);
    return item;

    Notice that setProperty method takes the property value and foreign-key. In this example, the product name is the foreign-key, selected from the drop down list in the UI.

    Since Item has many to one relationship with Product, deleting the Item would not affect the Product.

    Similarly, order has many to one relationship with Customer. We use the following statements in order creation to make customer as a parent to the order and also use it as an attribute. We Will add this code in the next section.

    Entity customer = Customer.getSingleCustomer(customerName);
    Key customerkey = customer.getKey();
    Entity order = new Entity("Order", customerkey);
    order.setProperty("customerName", customerName);
  2. Creating one to many relationshipAn Order has more than one line item. Order has one to many relationships with LineItem. For simplicity, in our example we have used only one LineItem per Order.

    As, the line items and orders must be saved at the same time, i.e. if any one of the entities is not saved, we have to delete all entities. So, we shall include a transaction here while saving the order. Add the following lines of code in createOrUpdateOrder() method of Order.java.

    Transaction txn = Util.getDatastoreServiceInstance().beginTransaction();
    try {
      Entity customer = Customer.getSingleCustomer(customerName);
      Key customerkey = customer.getKey();
    
      Entity order = new Entity("Order", customerkey);
      order.setProperty("customerName", customerName);
      order.setProperty("status", "Processed");
      order.setProperty("shipTo", shipTo);
      order.setProperty("city", city);
      order.setProperty("state", state);
      order.setProperty("zip", zip);
      Util.getDatastoreServiceInstance().put(order);
    
      Entity lineItem = new Entity("LineItem", order.getKey());
      // key to string can be inserted instead of name, a better option
      lineItem.setProperty("itemName", itemName);
      lineItem.setProperty("quantity", quantity);
      lineItem.setProperty("price", price);
      lineItem.setProperty("orderID", String.valueOf(order.getKey().getId()));
      Util.getDatastoreServiceInstance().put(lineItem);
      txn.commit();
    } catch (Exception e) {
      logger.log(Info.WARNING, e.getMessage());
    } finally {
      if (txn.isActive()) {
        txn.rollback();
      }
    }

    Notice that, Line Item has one to one relationship with the Item. It has foreign-key to Order as well. When we order, we can have one or more than one item that can be ordered and each item can have different quantities to be shipped. Hence, we include Line Item for every order.

  3. Deleting in one to many relationship – Cascade DeleteDeleting on order should delete the LineItems also and it must be done in single transaction. As we have customer as parent for every order, we should create a key with parent key in it and then delete those records. Here, using the customerName and Order Id we will create the whole key of order and then also find the respective line item keys. We first delete the line items and then the order in a single transaction.

    Add the following lines of code in doDelete() method of OrderServlet.java.

    String orderId = req.getParameter("id");
    String customerName = req.getParameter("parentid");
    
    Transaction txn = datastore.beginTransaction();
    try {
      Key parentKey = KeyFactory.createKey("Customer", customerName);
      Key key = KeyFactory.createKey(parentKey, "Order", Integer.parseInt(orderId));
    
      // CASCADE_ON_DELETE
      Iterable<Entity> entities = Util.listChildKeys("LineItem", key);
      final List<Key> keys = new ArrayList<Key>();
      for (Entity e : entities) {
        keys.add(e.getKey());
      }
      Util.deleteEntity(keys);
      datastore.delete(key);
      txn.commit();
    } finally {
      if (txn.isActive()) {
        txn.rollback();
      }
    }

Run the Application

  1. Refer Appendix AOnce the application is deployed, open a browser and access the following URLhttp://localhost:8888/

    The home screen is shown below.

    Create a product. The create product screen is shown below.

  2. Similarly, create an item for sale and a customer. Once these are created, go ahead and place an order for a specific customer and an item.
  3. The UI for placing the order is shown below.

    If order creation is successful, you can see the order in the list displayed.

Deploy your application to GAE

  1. Refer Appendix B

Summary

  1. Congratulations! You’ve completed the code lab exercise. You can also see the solution to the exercise in folder CodeLabEx02 folder