SimpleDS 1.0 is released

This week we are releasing version 1.0 of SimpleDS. SimpleDS is a simple persistence framework for Google AppEngine that provides an alternative to JDO or JPA.

This release brings a lot of new features:

Cached queries

This is the star feature of this release. Starting with 1.0, all query results can be cached. To maximize cache performance only the returned primary keys will be cached, so you still have to use @Cacheable to cache the entity itself.

There are cases were this feature is killer, e.g. to retrieve a User given the email:

User user = entityManager.createQuery(User.class)
  .equal("email", googleUser.getEmail())
  .withCacheSeconds(3600)
  .asSingleResult()
  ;

DEBUG CacheManagerImpl - Level 2 cache hit: qdata{kind=User,pred=[email = test@example.com]}
DEBUG CacheManagerImpl - Level 2 cache hit: User(5)
DEBUG Level1Cache - Level 1 cache hit: User(5)

Cached queries work with FetchOptions (cursors, limit and offset) and support only count(), asSingleEntity(), asList() and asPagedList(). Any invocation to asIterable() / asIterator() will ignore the cache. To clear cached data, just prepare the same query and invoke clearCache().

More about cache here.

Better fluent interfaces

The syntax has been simplified from this (older version):

SimpleQuery query = entityManager.createQuery(User.class).equal("name", "foo");
return entityManager.find(query);

To this:

return entityManager.createQuery(User.class)
  .equal("email", email)
  .equal("enabled", true)
  .sortAsc("email")
  .asList()
  ;

Now we rarely use more than one line for most queries. In this example, if email is null it will just be ignored.

Several methods have been added: asList(), asSingleResult(), asIterator(), asIterable() and PagedQuery.asPagedList(). The old EntityManager methods have been deprecated.

More about queries here.

New Functions

We are also adding some new Functions for transforming persistent entities. Some examples of use:

// save space for your relationships by storing Set instead of Set
Set userIds = user.getFriends();
Collection userKeys = Collections2.transform(userIds, new IdToKeyFunction(User.class));
Collection users = entityManager.get(userKeys);

// transform back
CompositeFunction func = new CompositeFunction(
 new EntityToKeyFunction(User.class), 
 new KeyToIdFunction()
);
user.setFriends(Collections2.transform(users, func));

// just return a collection of email addresses
return Collections2.transform(users, new EntityToPropertyFunction(User.class, "email"));

More about functions and transformations here.

Added JRebel support

I personally use JRebel for development, which means that I rarely restart my development server. This was a problem with SimpleDS, which was unable to detect changes such as new persistent attributes, etc. SimpleDS can now be used with JRebel just by adding this to your startup code:

if (SystemProperty.environment.get().equals("Development"))
   ClassMetadataReloader.register();

Then in eclipse ("Go to your launcher config -> Arguments -> VM Arguments")

-javaagent:/usr/local/java/appengine-java-sdk/lib/agent/appengine-agent.jar -noverify
"-javaagent:${env_var:REBEL_HOME}/jrebel.jar"

Minor changes

  • Added @Property.converter to override the default converter for one persistent property. You can, for example, store a String attribute as Text.

  • Added new methods SimpleQuery.withReadPolicy(ReadPolicy) and SimpleQuery.withDeadline(double)

  • Added new methods SimpleQuery.withStartCursor(Cursor) and SimpleQuery.withEndCursor(Cursor)

  • Cache settings will be ignored when invoked within a transaction.

  • We have moved from commons-logging to slf4j. This may break binary compatibility, but is a huge boost in performance.

Let me get off-topic for a while

Last week this very same blog reached the 400-subscribers mark! It's awesome to find so many people interested, and I find extremely rewarding the contacts I receive every now and then, sometimes just to say hi. Last week it came from Australia! (hey Ángel).

So this is me, taking a small detour to say thanks. And yes, I will try to post more often :))