Beware of this: play framework’s cascaded save() only works on loaded collections

I am currently developing a web application using the really excellent play framework. If you are a web developer with some Java background I strongly encourage you to give it a try.

However, play has taken a little different or let’s better call it an additional approach to control object synchronizations with the underlying database. Whereas JPA manages a loaded object graph and automatically persists any changes upon transaction completion, play has changed this behavior (“Explicit save”) to give the beginner more control over what is going on with the objects. This extension turns the implicit save mechanism into an explicit one where the developer is in control to call save() on the changed objects. This save() will in turn cascade (if for instance the CascadeType.ALL annotation option is present) to the reachable objects.

This might in the first place seem clever because you get control back but I have mixed feelings about it because there are cases where it does just not work as easily as expected.

As an example, imagine a 3-level parent-child object model.

@Entity
public class Item extends Model {

 @OneToMany(cascade = CascadeType.ALL, orphanRemoval=true, mappedBy="item")
       @OrderBy("createdAt")
 public List<Position> positions = new ArrayList<Position>();
}

@Entity
public class Position extends Model {

 public Date createdAt = new Date();
 public int quantity;

 @ManyToOne public Item item;

 @OneToMany(cascade = CascadeType.ALL, orphanRemoval=true, mappedBy="position")
 @OrderBy("createdAt")
 public List<Order> orders = new ArrayList<Order>();
}

@Entity @Table(name="Ordr")
public class Order extends Model { 
 public Date createdAt = new Date();
 public int quantity;

 @ManyToOne public Position position;
}

So far nothing complicated. But now imagine you are loading (for performance reasons) objects from the lowest level (Order) and manipulate the parent object (i.e., the Position).

public void dostuff(Item item) {

 // load orders according to some important criteria
 List<Order> orders = Order.find("position.item = ? order by createdAt", item).fetch();

 // manipulate the parent of the order (the Position)
 Order order = extractImportantOne(orders);
 order.position.quantity += 200;

 item.save();
}

Intuitively I would assume that the manipulated Position object is updated in the database through the Item.positions cascade settings. But this is not the case here! The reason is that the collection Item.positions has not been populated from the database. Thus, play sees an unintialized PersistentCollection and will simply ignore to cascade.

One solution to the problem would be to touch the cascading collection:

 item.positions.size();
 item.save();

This will make sure the ‘positions’ collection will be considered during the following call to save(). With pure JPA on the other hand, such a call to do the trick would never be necessary and also the call to save() could be avoided, because JPA knows all loaded and changed objects and automatically updates the database.

This is why I have very mixed feelings about play’s extended persistence API. It works well in simple cases but for more complex models it might be better to switch back to plain JPA mode.

Advertisements

4 thoughts on “Beware of this: play framework’s cascaded save() only works on loaded collections

  1. interesting observations, you should post that to play framework’s discussion list, I would consider it a bug, simply because, as you stated, is counterintuitive…

  2. Pingback: Bringing JPA Transitive Persistence Back Into Play

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s