Tuesday, June 15, 2010

GWT And Data Persistence

After trying out the tutorials from the Google Web Toolkit I created some small application where I needed to persist some data on the server. I was searching how to do this for quite some while so I thought it would be a nice idea to share what I found out about it. Basically, you can store data easily using Java Data Objects (JDO) but there are some things that you have to pay attention to.

JDO uses annotations to mark the fields and classes that are persistable:

@PersistenceCapable
public class Book extends Entity {

  @Persistent
  private java.lang.String title;
   
  @Persistent
  private java.lang.String iSBN;

  @Persistent
  private double price;
   
  @Persistent
  private java.lang.String description;

   // ...

}

When you are working with GWT you usually implement services on the Server. This is done by extending RemoteServiceServlet and implementing your service interface. That is the place where I wanted to persist the data which gets changed by service calls. To do this, you need a so-called PersistenceManager. I wrote a helper class which contains a singleton access to a PersistenceManagerFactory:

public final class PMF {
 
  private static final PersistenceManagerFactory pmfInstance =
        JDOHelper.getPersistenceManagerFactory("transactions-optional");

  private PMF() {}

  public static PersistenceManagerFactory get() {
    return pmfInstance;
  }
 
}

Now, creating a new book and making it persistent is as easy as this:

  @Override
  public Book createBook() {
    Book newBook = new Book();
    PersistenceManager pm = PMF.get().getPersistenceManager();
    try {
      pm.makePersistent(newBook);
    } finally {
      pm.close();
     }
    return newBook;
  }

Access to existing data is done using JDOQL and not much harder:

public List getBooks() {
  PersistenceManager pm = PMF.get().getPersistenceManager();
  try {
    Query query = pm.newQuery(Book.class);
    List results = (List) query.execute();
    return new ArrayList(results);
  } finally {
    pm.close();
  }
}

This gives a list of all existing books.

I got into problem when I tried to use iheritence with my JDO data classes. All my data classes inherit from the following class Entity:

@PersistenceCapable
@Inheritance(strategy = InheritanceStrategy.SUBCLASS_TABLE)
public abstract class Entity {
   
    @PrimaryKey
    @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
    private Long fKey;
   
    protected Entity() {
        this(null);
    }
   
    protected Entity(Long key) {
        fKey = key;
    }
   
    public long getKey() {
        return fKey;
    }

    @Override
    public int hashCode() {
        if (fKey == null) return 0;
        return fKey.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
       // ...
    }
}

The important piece here is @Inheritance(strategy = InheritanceStrategy.SUBCLASS_TABLE). This defines how the objects are stored in the tables. As the default store strategy is not implemented by google it is very important to specify this explicitly!

If you want to use the data classes as arguments to the service call you need to give them a no-arg constructor  (which is allowed to be private) and make sure that they inhert from IsSerializable:

  /**
   * Creates a new {@link Book} with all fields set to default.
   */
  public Book() {
    title = "";
    iSBN = "";
    price = 0.0;
    description = "";
  }


2 comments:

  1. When you say "As the default store strategy is not implemented by google it is very important to specify this explicitly!" you mean, not implemented by Google App Engine? Because GWT does not implement anything on the server side. The only you have is RPC. The way you do JDO is your problem.

    ReplyDelete
  2. Yes, this applies only if you deploy your application in the Google App Engine.

    ReplyDelete