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.























10 responses so far ↓
1 Jens Halm // Dec 9, 2008 at 3:54 PM
2 Andrew Powell // Dec 10, 2008 at 7:24 AM
3 Darren // Dec 10, 2008 at 5:44 PM
4 Bruno // Dec 11, 2008 at 8:10 AM
5 William Draï // Dec 12, 2008 at 9:10 AM
6 Jens Halm // Dec 12, 2008 at 1:25 PM
7 Andrew Powell // Dec 13, 2008 at 9:40 AM
8 William Draï // Dec 16, 2008 at 12:09 PM
9 miluch // Jul 23, 2009 at 6:09 AM
10 abeel // Jun 1, 2010 at 4:16 AM
Leave a Comment