wtorek, 28 września 2010

Lazy one-to-one inverse relationships in Hibernate

Many people are surprised when lazy loading of inverse (not owning) optional one-to-one relationships using Hibernate does not work for them out of the box.

Let's start with a short example:

@Entity
public class Person {
 private Animal animal;

 @OneToOne(optional = false)
 public Animal getAnimal() {
  return animal;
 }

 public void setAnimals(Animal animal) {
  this.animal = animal;
 }
}

@Entity
public class Animal {
 private Person owner;

 @OneToOne(fetch = FetchType.LAZY, optional = true, mappedBy = "animal")
 public Person getOwner() {
  return owner;
 }

 public void setOwner(Person owner) {
  this.owner = owner;
 }
}

Many developers expect that owner property of the Animal entity will not be loaded until it is accessed for the first time. This is true but only for relationships where the proxy object for the other end of the relationship can be used. In our example it won't work.

There are at least three well known solutions for this problem:
  1. The simplest one is to fake one-to-many relationship. This will work because lazy loading of collection is much easier then lazy loading of single nullable property but generally this solution is very inconvenient if you use complex JPQL/HQL queries.
  2. The other one is to use build time bytecode instrumentation. For more details please read Hibernate documentation: 19.1.7. Using lazy property fetching. Remember that in this case you have to add @LazyToOne(LazyToOneOption.NO_PROXY) annotation to one-to-one relationship to make it lazy. Setting fetch to LAZY is not enough.
  3. The last solution is to use runtime bytecode instrumentation but it will work only for those who use Hibernate as JPA provider in full-blown JEE environment (in such case setting "hibernate.ejb.use_class_enhancer" to true should do the trick: Entity Manager Configuration) or use Hibernate with Spring configured to do runtime weaving (this might be hard to achieve on some older application servers). In this case @LazyToOne(LazyToOneOption.NO_PROXY) annotation is also required.

But what if you don't want to modify the structure of your entities and don't want or cannot use bytecode instumentalization (there are some issues related to this).

There is one more undocumented solution. It requires some modifications in the entities code but thanks to this building and deployment process can remain untouched.

The idea is to fool Hibernate that the entity class which we want to use has been already instrumented. To do this your entity class has to implement FieldHandled or InterceptFieldEnabled interface.

@Entity
public class Animal implements FieldHandled {
 private Person owner;
 private FieldHandler fieldHandler;

 @OneToOne(fetch = FetchType.LAZY, optional = true, mappedBy = "animal")
 @LazyToOne(LazyToOneOption.NO_PROXY)
 public Person getOwner() {
  if (fieldHandler != null) {
   return (Person) fieldHandler.readObject(this, "owner", owner);
  }
  return owner;
 }

 public void setOwner(Person owner) {
  if (fieldHandler != null) {
   this.owner = fieldHandler.writeObject(this, "owner", this.owner, owner);
   return;
  }
  this.owner = owner;
 }

 public FieldHandler getFieldHandler() {
  return fieldHandler;
 }

 public void setFieldHandler(FieldHandler fieldHandler) {
  this.fieldHandler = fieldHandler;
 }
}

If you are using javassist as bytecode provider (default from Hibernate version 3.3.0.CR2) implement org.hibernate.bytecode.javassist.FieldHandled and if you are using CGLib implement net.sf.cglib.transform.impl.InterceptFieldEnabled.

Getters and setters for non-lazy properties requires no changes.

The last thing worth mentioning here is that Hibernate does not support one by one lazy properties loading. This means that if your entity class has more then one lazy properties all of them are going to be loaded during the first access to any of them (I know that this is stupid but this is how it is currently implemented).

czwartek, 16 września 2010

Mapping cyclic object references to XML using JAXB

With JAXB being de facto standard for XML to Java data binding it is hard not to use it in enterprise application. Because of this as a java developer sooner or later you will notice some serious limitations of it. One of the most painful is a lack of support for object cycles and repetitions.

Lets start with a simple java class which we want to serialize to XML using JAXB.

@XmlRootElement
public class Node {
 private List<Node> nodes;
 @XmlAttribute
 private String name;

 public Node() {
 }

 public Node(String name) {
  this.name = name;
 }

 @XmlElementRef 
 public List<Node> getNodes() {
  return nodes;
 }

 public void setNodes(List<Node> nodes) {
  this.nodes = nodes;
 }

 public void addNode(Node node) {
  if (nodes == null) {
   nodes = new ArrayList<Node>();
  }
  nodes.add(node);
 }
}

If we run following test code we will get self explanatory error.

public class Test {
 public static void main(String[] args) throws Exception {
  Node node1 = new Node("node1");
  Node node2 = new Node("node2");
  Node node3 = new Node("node3");
  node1.addNode(node2);
  node1.addNode(node3);
  node2.addNode(node1);
  node2.addNode(node3);
  node3.addNode(node1);
  node3.addNode(node2);
  
  JAXBContext context = JAXBContext.newInstance(Node.class);
  Marshaller marshaller = context.createMarshaller();
  marshaller.marshal(node1, System.out);
 }
}

Exception in thread "main" javax.xml.bind.MarshalException
 - with linked exception:
[com.sun.istack.internal.SAXException2: 
A cycle is detected in the object graph.
This will cause infinitely deep XML: 
node.Node@1e9cb75 -> node.Node@c5c3ac -> node.Node@1e9cb75]

After spending a few hours on searching web for solution for this problem I haven't found any but I was able to came up with my own idea how to fix it.

First of all we need simple dummy XML adapter.

public class AnyTypeAdapter extends XmlAdapter<Object, Object> {
 public Object marshal(Object value) {
  return value;
 }

 public Object unmarshal(Object value) {
  return value;
 }
}

We need also to create wrapper class which will enable us to break cycles.

@XmlRootElement
public class ObjectWrapper {
 private static AtomicInteger nextId = new AtomicInteger(0);
 
 @XmlAttribute
 @XmlID
 private String id = String.valueOf(nextId.addAndGet(1));

 @XmlAttribute
 @XmlIDREF
 private ObjectWrapper ref;

 @XmlJavaTypeAdapter(AnyTypeAdapter.class)
 @XmlAnyElement(lax = true)
 private Object value;

 public ObjectWrapper() {
 }

 public ObjectWrapper(Object value) {
  this.value = value;
 }

 public Object getValue() {
  return value;
 }
 
 public void update(ObjectWrapper ref) {
  this.ref = ref;
  this.id = null;
  this.value = null;
 }

 public Object resolve() {
  return value == null ? 
    (ref == null ? null : ref.value) : 
    value;
 }
}

In order to make its usage easier we need to create another XML adapter.

public class ObjectWrapperAdapter extends 
  XmlAdapter<ObjectWrapper, Object> {
 public ObjectWrapper marshal(Object value) {
  return value == null ? 
    null : new ObjectWrapper(value);
 }

 public Object unmarshal(ObjectWrapper wrapper) {
  return wrapper == null ? 
    null : wrapper.resolve();
 }
}

Last part of the solution is a marshaller listener which will find repetitions in the object graph and transform them to XML id references (I'm using IdentityHashMap here but in some cases HashMap does make sense as well).

public class CycleListener extends Marshaller.Listener {
 private Map<Object, ObjectWrapper> objectMap = 
   new IdentityHashMap<Object, ObjectWrapper>();
 
 public void beforeMarshal(Object source) {
  if (source instanceof ObjectWrapper) {
   ObjectWrapper wrapper = (ObjectWrapper) source;
   if (objectMap.containsKey(wrapper.getValue())) {
    if (wrapper != objectMap.get(wrapper.getValue())) {
     wrapper.update(objectMap.get(wrapper.getValue()));
    }
   } else {
    objectMap.put(wrapper.getValue(), wrapper);
   }
  }
 }
}

With those four classes we need to change the Node class a little bit by applying ObjectWrapperAdapter to it. No other changes to the serialized class are needed.

@XmlJavaTypeAdapter(ObjectWrapperAdapter.class)
@XmlRootElement
public class Node {
 private List<Node> nodes;
 @XmlAttribute
 private String name;

 public Node() {
 }

 public Node(String name) {
  this.name = name;
 }

 @XmlElementRef 
 public List<Node> getNodes() {
  return nodes;
 }

 public void setNodes(List<Node> nodes) {
  this.nodes = nodes;
 }

 public void addNode(Node node) {
  if (nodes == null) {
   nodes = new ArrayList<Node>();
  }
  nodes.add(node);
 }
}

Finally we can rerun our test code. The only change we have to make is to register marshaller listener and wrap Node instance with ObjectWrapper (this is not needed if we know that the root object does not occur in any cycle).

public class Test {
 public static void main(String[] args) throws Exception {
  Node node1 = new Node("node1");
  Node node2 = new Node("node2");
  Node node3 = new Node("node3");
  node1.addNode(node2);
  node1.addNode(node3);
  node2.addNode(node1);
  node2.addNode(node3);
  node3.addNode(node1);
  node3.addNode(node2);

  JAXBContext context = JAXBContext.newInstance(
    Node.class, ObjectWrapper.class);
  Marshaller marshaller = context.createMarshaller();
  marshaller.setListener(new CycleListener());
  marshaller.marshal(new ObjectWrapper(node1), System.out);
 }
}

As a result we will get following XML.

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<objectWrapper id="1">
 <node name="node1">
  <objectWrapper id="2">
   <node name="node2">
    <objectWrapper ref="1" />
    <objectWrapper id="4">
     <node name="node3">
      <objectWrapper ref="1" />
      <objectWrapper ref="2" />
     </node>
    </objectWrapper>
   </node>
  </objectWrapper>
  <objectWrapper ref="4" />
 </node>
</objectWrapper>

As we can see each node element is wrapped with objectWrapper element which has an id and a node or a reference to other objectWrapper's id.

This approach works also with more complex object structures. Simply add @XmlJavaTypeAdapter(ObjectWrapperAdapter.class) annotation to all classes which can cause cycles or you want to remain unique after deserialization.