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 GNU General Public License).

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. Thus we have to make a compromise (as in all real life). The thing to be done is to clone only a 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 :-)

7 comments:

  1. It's fast and easy to use.
    Thanks for this great project!

    ReplyDelete
  2. it helped very much!!
    This is exactly what i wanted!!

    Thanks Man!!

    ReplyDelete
  3. This comment has been removed by the author.

    ReplyDelete
  4. hi Miroslav,

    I want to know if it is possibile to say to jpaCloner to NOT copy some fields?
    We use openJpa enhancer and the enhancer adds all its logic during compile.. it adds even other fileds like:

    protected transient StateManager pcStateManager;
    private transient Object pcDetachedState;

    We do not want to copy this fields bc when we try to persist the entity this exception is throwed "Caused by: org.apache.openjpa.persistence.EntityExistsException: Attempt to persist detached object ... If this is a new instance, make sure any version and/or auto-generated primary key fields are null/default when persisting."
    We use PropertyFilter filter = PropertyFilters.getAnnotationFilter(Id.class, Transient.class);
    so the id is null but the error continue to persist since the enhancer controls the pcDetachedState... We cannot set to null this fields in our code bc it is added during compile time...

    Using
    BeanUtils.copyProperties(copy, orig);
    pcStateManager and pcDetachedState are set to null so it persist well on a single element but not on a graph...

    So can we say to jpaCloner using patterns or somethine else to not copy this fields when it clones the objects??

    Thank you
    Arbri

    ReplyDelete
    Replies
    1. Hello, you can also provide a custom PropertyFilter implementation for clone methods. For example:

      PropertyFilter f1 = PropertyFilters.getAnnotationFilter(Id.class, Transient.class);

      PropertyFilter f2 = new PropertyFilter() {
      public boolean test(Object entity, String property) {
      return !"pcStateManager".equals(property)
      || !"pcDetachedState".equals(property);
      }
      }

      You can also mix the filters like:

      PropertyFilter mixed = PropertyFilters.getComposedFilter(f1, f2);

      I hope that this answers your question...

      BR/
      Miro

      Delete
  5. Hi Miroslav,

    ti works :-)
    thank you very much, this utility of yours is a very good one. Compliments ;-)
    This spare us a lot of time.
    You need just to add some more documentation/examples

    ps: the test condition must be
    return !"pcStateManager".equals(property) && !"pcDetachedState".equals(property);


    Thank you again
    Arbri

    ReplyDelete