Combining SOAP and ORM - Part 2

By Scott Balmos
Posted by Jack Vaughan
In my previous blog entry, we looked at a simple way to start combining SOAP and object relational persistence. In this entry, I expand on the example to show pitfalls with Maps and lazy fetching, and the way I work around them.

Here’s the entities again, with the addition of a Map to the User for a collection of user preferences, along with some additional code in the Group entity. I’ll explain below…
package test;

@Entity
public class User implements Serializable {
    private Long id;
    private String userName;
    private String password;
    private String firstName;
    private String lastName;
    private Map preferences = new HashMap();

    @Id
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

    public String getUserName() { return userName; }
    public void setUserName(String userName) { this.userName = userName; }

    public String getPassword() { return password; }
    public void setPassword(String password) { this.password = password; }

    public String getFirstName() { return firstName; }
    public void setFirstName(String firstName) { this.firstName = firstName; }

    public String getLastName() { return lastName; }
    public void setLastName(String lastName) { this.lastName = lastName; }

    @CollectionOfElements @XmlJavaTypeAdapter(StringStringMapAdapter.class)
    public Map getPreferences() { return preferences; }
    public void setPreferences(Map preferences) { this.preferences = preferences; } }

package test;

@Entity
public class Group implements Serializable {
    private Long id;
    private String name;
    private List members = new ArrayList();

    @Id
    public Long getId() { return id; }
    public void setId(Long id) { this.id = id; }

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

    @OneToMany @XmlTransient
    public List getMembers() { return members; }
    public void setMembers(List members) { this.members = members; }

    @Transient @XmlElement(name = “members”)
    private List getMembers_()
    {
        List vals = new ArrayList(members.size());
        for (User member : members)
        {
            User entry = new User();
            entry.setId(member.getId());
            vals.add(entry);
        }
        return vals;
    }
    private void setMembers_(List members) { this.members = members; } }

So we have a few new things here… In the User class, we have a Map of preferences, with an XmlJavaTypeAdapter pointing to a StringStringMapAdapter class. More on that in a few moments.

Let’s look in the Group class now. We have a few new annotations for the members collection. On the existing getMembers(), we have added XmlTransient. This prevents the XML marshaller in the SOAP engine from returning the full members collection. This is because of lazy fetching, on by default in all mapped collections in JPA.

Lazy fetching keeps the load down on our database servers by not loading all the details of each member entity in a collection. Such as in our Group entity, lazy fetching keeps the database server from having to load more details about the User entities in a group other than just the entity IDs. Other details for each member entity in a collection are retrieved in realtime if they are accessed. Normally this is all well and good. But think about the SOAP XML marshaller. It walks all fields in an object graph. So it will always touch each member in a collection, causing the ORM layer to constantly ask the database to load all details of that member. This completely negates the usefulness of lazy fetching, and can severely degrade your application’s performance.

So in collections that are lazily fetched, I use XML attribute pairing, where the only entity get/set methods available to my SOAP backend application are the original ones, which is within the ORM context of lazy fetching like I want. But now I have a pair of private get/set methods, which is what I want the XML marshaller to use. I mark it with the Transient JPA annotation so the ORM layer doesn’t scream about an unmatched entity field. And I use XmlElement to tell the XML marshaller that this is the get/set method pair I want to use when dealing with that collection. I write the get method so that we build an identical collection to return with member entities that only contain the IDs set.
It doesn’t matter that they’re private, since the XML marshaller uses reflection and ignores method access modifiers.

Now, I will get back a Group entity that I expect, with only a collection of User IDs, just like I would get in a lazily-fetched Group object in my normal Java application. If I wanted more data for the Users, I might have a separate endpoint method that takes a List of IDs, say List getUsersByIDs(List userIDs).

Back to Maps and the User class now… The SOAP spec, oddly, has no base class for any type of key-value Map (whether you call it a Map, Hash, Dictionary, whatever). A few SOAP stacks automatically, in the background, create their own custom Map type. But I’ve lost track of which ones do and which don’t. Most times, they’re incompatible across other stacks.

Here, I use an XmlJavaTypeAdapter to specify a custom XML marshaller / unmarshaller, when dealing with Maps. The code for StringStringMapAdapter is below.
public class StringStringMapAdapter extends XmlAdapter> {
    public Map unmarshal(MapEntry[] mapEntries) throws Exception
    {
        Map map = new HashMap();
        for (MapEntry entry : mapEntries)
        { map.put(entry.getKey(), entry.getValue()); }
        return map;
    }

    public MapEntry[] marshal(Map map) throws Exception
    {
        MapEntry[] values = (MapEntry String>[])Array.newInstance(MapEntry.class, map.size());
        int i = 0;
        for (Map.Entry entry : map.entrySet())
        {
            values[i] = new MapEntry();
            values[i].setKey(entry.getKey());
            values[i].setValue(entry.getValue());
            i++;
        }
        return values;
    }
}
and the code for MapEntry, which is a simple generic custom type for a key-value pair, is below.

public class MapEntry
{
    private K key;
    private V value;

    public K getKey() { return key; }
    public void setKey(K key) { this.key = key; }

    public V getValue() { return value; }
    public void setValue(V value) { this.value = value; } }

XmlAdapter is a base class that deals with XML marshalling and unmarshalling. The first generic type is the XML value type you want to marshal to. The second is the native Java type in your application you want to unmarshal from. Most of it is self-explanatory, after reading my explanation of the attribute pairs above for lazy fetching. I do a little bit of reflective nastiness in marshal(), in order to get around the fact that you can’t directly declare a generic array in Java (e.g. MapEntry[] values = new MapEntry[5] for example).

One annoying part about XmlAdapter when dealing with Maps is the fact that you need to write separate classes for each different key-value type pair. Personally, I have one for Map, one for Map, and a few others.

I seem to remember a bit of rumbling in the JAXB forums on possibly integrating automatic Map marshalling / unmarshalling, to remove the need for custom XmlAdapters. And I’m not sure whether .Net has auto-marshalling for Maps. I write much more Java than C#. I’d be interested to hear what other stacks handle Maps automagically, and how they end up looking in SOAP messages on the wire.

One Response to “Combining SOAP and ORM - Part 2”

  1. Rudi Says:

    Why don’t you use true message type like they do in the web service factory (Microsoft)? It seems to solve are your problems of mapping and different internal and external entities keeping your domain model clear?

    Greetings,

    Rudi


Leave a Reply