GWT Custom Field Serializers Versus Hibernate’s LazyInitializationException

One fine day, I ran into the same problem that everyone using any combination of GWT and Hibernate come up with: the dreaded LazyInitializationException. As soon as I tried to pass data back across RPC, it would hit.

The root cause? When using a List or Set in the Hibernate realm, the actual implementations used are PersistentBag (for Lists) or PersistentSet. Sadly, these classes are NOT serializable. Try to pass them back via RPC and the whole thing blows up.

My first attempt to solve the problem was via Dozer. Dozer describes itself as a bean-to-bean mapper, which recursively copies fields from a source bean to a destination bean. However, it didn’t always work perfectly for me. Also, it was yet another addition to an already bloated pom.xml file.

I really don’t remember how I stumbled across Custom Field Serializers, but the concept is simple enough. When an object implements Serializable or GWT’s IsSerializable interface, GWT’s default serializer takes care of passing the data across RPC. Of course, if the member of the class is using a data type that is NOT serializer, kaboom.

A custom field serializer allows you to control how the data is serialized and deserialized. Like, say, rewriting a non-serializable type… anyways, an example is better than me rattling on.

Here’s a class from my Mapmaker project, Location.java (complete with Hibernate mapping annotations):

@SuppressWarnings("unused")
@Entity
@Table(name="LOCATION")
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Location implements Serializable, IsSerializable, Cloneable {

    private Long id;
    private String geoId;
    private String name;
    private Double internalLat;
    private Double internalLng;
    private List<BorderPoint> borderPointList;
    private List<Feature> featureList;  // transient... do not map!

    public Location() {
    }

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="LOCATIONID")
    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    @Column
    public String getGeoId() {
        return geoId;
    }

    public void setGeoId(String geoId) {
        this.geoId = geoId;
    }

    @Column(name="locationName")
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Column(name="INTERNALLAT")
    public Double getInternalLat() {
        return internalLat;
    }

    public void setInternalLat(Double internalLat) {
        this.internalLat = internalLat;
    }

    @Column(name="INTERNALLNG")
    public Double getInternalLng() {
        return internalLng;
    }

    public void setInternalLng(Double internalLng) {
        this.internalLng = internalLng;
    }

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "location")
    @LazyCollection(LazyCollectionOption.FALSE)
    public List<BorderPoint> getBorderPointList() {
        return borderPointList;
    }

    public void setBorderPointList(List<BorderPoint> borderPointList) {
        this.borderPointList = borderPointList;
    }

    @Transient
    public List<Feature> getFeatureList() {
        return featureList;
    }

    public void setFeatureList(List<Feature> featureList) {
        this.featureList = featureList;
    }
}

All in all, this is a pretty simple class with a couple of lists mapped via @OneToMany. I also used @LazyCollection but will explain why below.

A GWT custom field serializer needs to be named in the format of (classname)_CustomFieldSerializer.java. The class needs to have two methods: serialize and deserialize. It should be obvious what each method does…

Here is Location’s custom field serializer, Location_CustomFieldSerializer.java:

@SuppressWarnings("unused")
public class Location_CustomFieldSerializer {

    public static void serialize(SerializationStreamWriter writer, Location instance) throws SerializationException {
        writer.writeLong(instance.getId());
        writer.writeString(instance.getGeoId());
        writer.writeString(instance.getName());
        writer.writeDouble(instance.getInternalLat());
        writer.writeDouble(instance.getInternalLng());
        if (instance.getBorderPointList() == null) {
            writer.writeObject(new ArrayList<BorderPoint>());
        } else {
            writer.writeObject(new ArrayList<BorderPoint>(instance.getBorderPointList()));
        }
        if (instance.getFeatureList() == null) {
            writer.writeObject(new ArrayList<Feature>());
        } else {
            writer.writeObject(new ArrayList<Feature>(instance.getFeatureList()));
        }

    }

    @SuppressWarnings("unchecked")
    public static void deserialize(SerializationStreamReader reader, Location instance) throws SerializationException {
        instance.setId(reader.readLong());
        instance.setGeoId(reader.readString());
        instance.setName(reader.readString());
        instance.setInternalLat(reader.readDouble());
        instance.setInternalLng(reader.readDouble());
        instance.setBorderPointList((ArrayList<BorderPoint>) reader.readObject());
        instance.setFeatureList((ArrayList<Feature>) reader.readObject());
    }
}

In the serialize method, I needed to write each field to the SerializationStreamWriter IN ORDER since somewhere in this mess, reflection is involved. The deserialize method required setting each field of the Location class from the SerializationStreamReader.

However, since you are here to figure out how to solve the problem with LazyInitializationException, here’s the practical application part as a code snippet from serialize():

        if (instance.getBorderPointList() == null) {
            writer.writeObject(new ArrayList<BorderPoint>());
        } else {
            writer.writeObject(new ArrayList<BorderPoint>(instance.getBorderPointList()));
        }

If the instance object’s borderPointList is null, I write out an empty ArrayList. If it is NOT null, I write out a new ArrayList, passing the original list (which was actually Hibernate PersistentBag) as a constructor argument. This has the effect of killing the LazyInitializationException.

However: There Is No Such Thing As A Free Lunch (as Heinlein told us). Remember this snippet from the original class:

    @OneToMany(cascade = CascadeType.ALL, mappedBy = "location")
    @LazyCollection(LazyCollectionOption.FALSE)
    public List<BorderPoint> getBorderPointList() {
        return borderPointList;
    }

I had to use the @LazyCollection(LazyCollectionOption.FALSE), which means that I lost the advantages of Lazy Loading. I figured this was kind of a toss-up anyways, since I was going to have to pass everything back via RPC and would have to instantiate the list, but it’s a definite consideration. Also, the BorderPoint class required its own custom field serializer class.

About these ads
Post a comment or leave a trackback: Trackback URL.

Comments

  • Davor  On March 7, 2012 at 8:40 am

    didi u get any problems on deployment because i treid this solution, but when deploeyed on jboss it cannot find the policy for serialization

    • dartmanx  On March 13, 2012 at 6:12 am

      I wrote this using Jetty as the container, so I can’t speak to any problems JBoss would have.

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

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: