Andrew Powell

Into The Mind of A Solutions Architect

Andrew Powell

Bypassing Hibernate's Lazy Loading in BlazeDS with Spring

December 9, 2008 · 10 Comments

The Problem:

When using Hibernate as your persistence layer, it allows you to create lazy collections. This is nice because lazy collections can speed up your application, and in theory, you only access the data you want to access. Simple enough. The problem lies however in talking to Flex applications, specifically via BlazeDS or LiveCycle DS. What happens is the fault of the serializers. When you make a request, via mx:RemoteObject, to the server, and you are accessing a lazy collection, when the serializer attempts to turn the data from Java to AMF, it touches everything. This "unwelcome touching" by the serializer, in turn, triggers all of your lazy loading to fire, therefore rendering your lazy collections and the effort to set them up, useless. So, with that in mind, how do we pass back only the data we want to pass back?

The Solution

Let's consider the following value objects:

package com.universalmind.samples.lazyloadIssue;
   import org.hibernate.annotations.Entity;
   import java.util.UUID;
   /**
    * Copyright (c) 2008 Universal Mind Inc.
    * Created by IntelliJ IDEA.
    * Created By: Andrew Powell
    * Date: Dec 9, 2008
    * Time: 11:44:37 AM
    */
   @Entity
   public class Item implements AMFSerializable {
    private UUID id;
    private String sku;
    private String name;
    private String description;
    public Item() {
    }
    public UUID getId() {
    return id;
    }
    public void setId(UUID id) {
    this.id = id;
    }
    public String getSku() {
    return sku;
    }
    public void setSku(String sku) {
    this.sku = sku;
    }
    public String getName() {
    return name;
    }
    public void setName(String name) {
    this.name = name;
    }
    public String getDescription() {
    return description;
    }
    public void setDescription(String description) {
    this.description = description;
    }
    public Item cloneForAMF() {
    Item item = new Item();
    item.setDescription(description);
    item.setId(id);
    item.setName(name);
    item.setSku(sku);
    return item;
    }
   }

package com.universalmind.samples.lazyloadIssue;
   import org.hibernate.annotations.Table;
   import javax.persistence.Entity;
   import javax.persistence.ManyToMany;
   import javax.persistence.FetchType;
   import java.util.UUID;
   import java.util.Date;
   import java.util.ArrayList;
   /**
    * Copyright (c) 2008 Universal Mind Inc.
    * Created by IntelliJ IDEA.
    * Created By: Andrew Powell
    * Date: Dec 9, 2008
    * Time: 11:43:09 AM
    */
   @Entity
   public class Order implements AMFSerializable {
    private UUID id;
    private Date date;
    private ArrayList items;
    public Order() {
    }
    public UUID getId() {
    return id;
    }
    public void setId(UUID id) {
    this.id = id;
    }
    public Date getDate() {
    return date;
    }
    public void setDate(Date date) {
    this.date = date;
    }
    @ManyToMany(targetEntity=Item.class,fetch= FetchType.LAZY)
    public ArrayList getItems() {
    return items;
    }
    public void setItems(ArrayList items) {
    this.items = items;
    }
    public Order cloneForAMF() {
    Order clone = new Order();
    clone.setDate(date);
    clone.setId(id);
    return clone;
    }
   }

These two value objects are related in the sense that there is a many-to-many relationship between Order and Item. An Order can have many Item instances and a Item can belong to many Order instances. There is another piece in there as well. Both of these implement the AMFSerializable interface, listed below:

package com.universalmind.samples.lazyloadIssue;
   import java.io.Serializable;
   /**
    * Copyright (c) 2008 Universal Mind Inc.
    * Created by IntelliJ IDEA.
    * Created By: Andrew Powell
    * Date: Dec 9, 2008
    * Time: 12:16:37 PM
    */
   public interface AMFSerializable extends Serializable {
    public Object cloneForAMF();
   }

This interface extends java.io.Serializable which is needed for AMF serialization. It also adds one method: cloneForAMF(). This method allows you to create a copy of the object that is free from Hibernate proxies and only contains the data that you want to send back to Flex. The next trick, however, is invoking that method once your business logic completes.

Let's say we have a simple, Hibernate and Spring enabled, data access object:

package com.universalmind.samples.lazyloadIssue;
   import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
   import org.hibernate.criterion.DetachedCriteria;
   import org.hibernate.criterion.Restrictions;
   import java.util.*;
   /**
    * Copyright (c) 2008 Universal Mind Inc.
    * Created by IntelliJ IDEA.
    * Created By: Andrew Powell
    * Date: Dec 9, 2008
    * Time: 11:42:55 AM
    */
   public class OrderDAO extends HibernateDaoSupport {

    @SuppressWarnings("unchecked")
    public ArrayList getTodaysOrders(Date yesterday, Date tomorrow){
    DetachedCriteria criteria = DetachedCriteria.forClass(Order.class);
    criteria.add(Restrictions.gt("date",yesterday));
    criteria.add(Restrictions.lt("date",tomorrow));
    return (ArrayList) this.getHibernateTemplate().findByCriteria(criteria);
    }
   }

The method we're going to call, getTodaysOrders is going to return an ArrayList, given a set of criteria. Pretty simple. Between the time this method executes and the time it hits the serializer, within the RemoteObject call, we want to prep our data with the cloneForAMF() method that we created on our value objects. Since we're using Spring, we can leverage the power of Aspect-Oriented Programming to accomplish this task. In order to accomplish this with AOP, we need to implement advice both before and after the method in question. Implementing this "around advice" is easy using the MethodInterceptor class, as shown below:

package com.universalmind.samples.lazyloadIssue;
   import org.aopalliance.intercept.MethodInterceptor;
   import org.aopalliance.intercept.MethodInvocation;
   import java.util.ArrayList;
   /**
    * Copyright (c) 2008 Universal Mind Inc.
    * Created by IntelliJ IDEA.
    * Created By: Andrew Powell
    * Date: Dec 9, 2008
    * Time: 12:12:37 PM
    */
   public class PreSerializationInterceptor implements MethodInterceptor {
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
    if(methodInvocation.getMethod().getName() == "getTodaysOrders"){
    ArrayList persistentCollection = (ArrayList) methodInvocation.proceed();
    ArrayList serializedCollection = new ArrayList();
    for(Order order : persistentCollection){
    if(order instanceof AMFSerializable) serializedCollection.add(order.cloneForAMF()); }
    return serializedCollection;
    }
    else{
    return methodInvocation.proceed();
    }
    }
   }

This code will first make sure we're executing the method we want to clean the results of, execute the method using methodInvocation.proceed(), and then we will do some "post-processing" of the method results to only return what we want to be serialized to Flex. We must also create a new collection, as the collection that is returned is aware that it's a Hibernate lazy-collection (via proxy). After we create the new collection, if the members of the persistent collection implement the AMFSerializable interface, we will call the cloneForAMF() method and add the resulting object to our new non-persistent collection. This collection then gets returned and serialized by the AMF serializer. There are now lazy-loaded collections triggered because the collection we are passing back to the serializer is blissfully unaware of Hibernate or any lazy-collection.

The drawback to this method is that every time you want to get an object's collection, you must make another round-trip to the server. These trips can be less data passed across, but be aware that this must happen if you want to retrieve the lazy collections. Those calls must also be via another method you create, you cannot just load the same objects up and expect the collections to come back by themselves. That defeats the purpose.

I must admit that this does feel like a bit of a hack instead of a more elegant solution like dpHibernate, but sometimes you don't want to mess around with custom adapters on the Java side and special configs on the Flex side. This allows you to implement Hibernate and bypass lazy loading with minimal impact to your value objects and retrieval methods on the AS side. So, in that sense, I think this solution has its place and can be valuable to you on your Flex / Java projects.

Tags: BlazeDS · Flex · Hibernate · Java · LiveCycle ES · Spring · Universal Mind

10 responses so far ↓

  • 1 Jens Halm // Dec 9, 2008 at 3:54 PM

    I consider this solution as problematic for at least two reasons:<br /><br />First you are kind of polluting the model with functionality that is specific to a particular kind of client that needs access to this model. If there are additional types of clients that also would need some kind of hack in the model it would get messy. Usually it's beneficial to neither hard code how and where the model gets stored nor how it is accessed by clients.<br /><br />Second I'd say that this is a lot of plumbing code just for integration. With Pimento Data Services (http://www.spicefactory.org/pimento/) for example this would boil down to ... well, nothing actually. The bit of configuration you have to add to your server setup must be done once, for the whole application. Your solution must be coded and tested and maintained for every single entity.
  • 2 Andrew Powell // Dec 10, 2008 at 7:24 AM

    @Jens -<br /><br />Like I said in the post, I'm not 100% sure I'm comfortable with this solution. I'm not really sure the cloneForAMF() method is the best way to go, but it works. As far as getting into PDS, well, the crux of this is to make it work with BlazeDS. I realize that there are other remoting solutions on the market for Flex (GraniteDS, WebORB, etc), but a vast majority of applications will be built with either BlazeDS or LCDS, so I feel we need a solution, other than DataManagement with the HibernateAdapter, that addresses these problems, like dpHibernate or this.
  • 3 Darren // Dec 10, 2008 at 5:44 PM

    It would be great if they try to solve this problem with the new Spring version of BlazeDS that SpringSource and Adobe are working on. I know it's not Spring's issue but in the real-world, most users of Spring also use Hibernate and Spring makes a big deal of it's first-class Hibernate support, so it makes sense.
  • 4 Bruno // Dec 11, 2008 at 8:10 AM

    It looks like integration solutions between Hibernate and BlazeDS are becoming more and more popular : in addition to dpHibernate and Pimento, the Gilead library (http://gilead.sourceforge.net) does the same thing (it is known to work with GWT platform since 18 months_ its former name was hibernate4gwt _ and added the BlazeDS support last month).<br /><br />Hope this helps<br />Bruno
  • 5 William Draï // Dec 12, 2008 at 9:10 AM

    Just to add on the various solutions listed, GraniteDS just allows this kind of things completely transparently since more than one year (http://www.graniteds.org). <br />Plus, the entities sent to the client are still managed entities, meaning that you can modify them in Flex, resend them to the server and merge them again in the persistence context and they will behave exactly as normal Hibernate entities.<br />Finally, we also have experimental support for TopLink that can be useful for users of GlassFish.
  • 6 Jens Halm // Dec 12, 2008 at 1:25 PM

    Since the previous comment could be read as if the other solutions do not support entities that are managed in the client, I'd like to add that at least for Pimento this is also fully supported. When sending managed AS3 entities back to the server only the modified properties will be sent. For unindexed collections (Bags/Sets) this also means that only a change set with information about the added and removed elements will be sent. <br /><br />Apart from that Pimento can also be used in Flash without Flex (with the full feature set).<br /><br />Anyway, bottom line is that with this growing number of solutions that specifically deal with Flex-Flash/Java-ORM integration IMHO there shouldn't be the need to build custom workarounds or to wait for Adobe to solve the problem.
  • 7 Andrew Powell // Dec 13, 2008 at 9:40 AM

    GraniteDS is not so much an option because my clients want to stay within the Adobe family of products. Thanks for the suggestion though.
  • 8 William Draï // Dec 16, 2008 at 12:09 PM

    Of course that perfectly makes sense that your customers prefer using Adobe supported products.<br />But I also agree with Jens and it's very unlikely that Adobe decides to bring its data management functionality to the open source BlazeDS and ditches LCDS. So there is in fact no free option on the Adobe side concerning data management, other than using Pimento or GDS or some other solution.<br /><br />In fact, I just wonder if having a GDS plugin inside BlazeDS to provide additional functionality would be better accepted by your customers.<br /><br />Note to Jens: my comment was not targetted at Pimento or other solutions but at the proposed workaround, I'm sorry if you felt it like this. I've had a quick look at Pimento and it looks very good.
  • 9 miluch // Jul 23, 2009 at 6:09 AM

    Hi<br /><br />I also find your solution quite clumsy. The biggest drawback i see is that if you decided to expose your data model to clients you need to provide the sub-set of this model in a per use case manner.<br />What i mean is that in one use case you would like to return items without orders and in other case you need to return items with orders fetched.<br />How deep you navigate your model graph should depend on use case scenario.<br />Using cloneForAMF() method you do not only pollute your model but also create very limited solution. <br />I do not wanna to give any names of projects/frameworks which can deal with the issue easily since i do not know any.<br />My idea is quite similar to yours, but i would ditch cloneForAMF() .In your interceptor I would check if all properties of returned object/objects are initialized or not. You can use Hibernate.isInitialized(your property) to do that.<br />Obviously you would have to check each property of each accociations and forth
  • 10 abeel // Jun 1, 2010 at 4:16 AM

    please can you put a code source of this exemple i need how i can configurate spring aop

Leave a Comment