Monday, April 15, 2013

Cloning of entity subgraphs

First of all, I have to introduce myself. I work as a programmer, analyst and a SW architect since 2003. Past experiences with JPA and Hibernate motivated me to write this short article about considerable benefits of entity cloning. I also introduce an opensource project JpaCloner. You can find it on the github link https://github.com/nociar/jpa-cloner (released under the Apache License, Version 2.0).

Deep entity clone

The cloning mechanism itself is nothing new or special. Every semi-skilled programmer knows it (copy constructor in C++ or clone() method in Java). The cloning of a simple object is common and definitely not interesting for most of us. Until... we have to deal with concepts like fetching of entity subgraphs, defensive copying or the entity serialization. It is advisable in many situations to use, pass or return a deep copy of an entity. But the question is: how to define the deep copy? It depends on many factors but mostly on the actual context and requirements. So to be more precise, we often need to clone an entity subgraph. Deep copies of entities have many benefits, some examples:

  • A deep copy can be considered as a fetched entity subgraph.
  • A copy is always detached from a persistence manager. Any change of a copy does not affect the original.
  • A deep copy can be used out of a transaction scope and prevents the throwing of the LazyInitializationException.
  • A copy allows to get rid of JPA proxy classes. A proxy free copy can be serialized by many frameworks (like GWT RPC).

I found couple of projects which deal with similar concepts like Apache Dozer, Gilead (hibernate4gwt) or JpaEntityManager#copy() in EclipseLink. But non of them fulfills my needs, so I decided to do a small project myself. Following sections describe usage of the project in more details.

Entity subgraph

JPA/ORM mapping can be considered as a graph of POJOs. Entities are nodes and relations are edges (singular, collection or map). A deep clone of an entity should also make deep clones of neighboring entities and their neighboring entities, and so on... Such strategy of transitive cloning may easily ends with the whole DB cloned and OutOfMemoryError thrown. We have to clone only a part of the object graph - subgraph. The desired subgraph can be specified by a set of paths from a root entity. After some time I found that the common dot notation (e.g. "company.department.boss.address") is not flexible enough to cover many additional needs like recursive relations and path splitting and joining. So I introduced the GraphExplorer class which generates the paths upon a string pattern. String literals inside the pattern are treated as properties. Operators resemble the syntax of regular expressions:
  • Dot "." separates paths.
  • Plus "+" generates at least one preceding path.
  • Split "|" divides the path into two ways.
  • Terminator "$" ends the preceding path.
  • Parentheses "(", ")" groups the paths.
  • String literals support wildcards: star "*" and question mark "?".
Some examples follow:
  • project.devices.interfaces
  • project.devices.inter??ces
  • project.devices.*
  • school.teachers.lessonToPupils.(key.class|value.parents)
  • company.departments+.(boss|employees).address.(country|city|street)

JPA Cloner

The JpaCloner class allows to clone entity subgraphs specified by the string patterns. After the algorithm is finished, each cloned entity contains basic properties (columns) and cloned relations. Non-cloned relations are left as null. The cloned entities are always created as raw classes (annotated by the @Entity or @Embeddable). So a cloned object will never be a hibernate proxy. The JpaCloner supports relations defined as List, Set, SortedSet, Map and SortedMap. The class contains static methods to shield the programmer from the implementation details. The usage looks like:

MyEntity e = em.find(MyEntity.class, 1234);
MyEntity c = JpaCloner.clone(e, "x.y.z", "aa.(bb.cc)*.dd");

If you need to clone a list or a set of entities, you can use overloaded clone methods:

TypedQuery<MyEntity> query = entityManager.createNamedQuery(...);
List<MyEntity> list = query.getResultList();
list = JpaCloner.clone(list, "x.y.z", "aa.(bb.cc)*.dd");


Conslusion

Working with JPA and ORM may easily become painful (and in some cases a nightmare). This applies especially when it comes to a persisting and merging of complex structures with higher-level semantics. I found that using clones makes my work with ORM again entertaining. Clones can be returned by any transactional method with fetched subgraphs (I use it in my GWT applications). I hope that the project will be useful not only for me :-)