JEE: Solving the eager fetch or Open-Session-In-View problem

In the JEE architecture entities are used to transfer the data between database and business logic. An object relational mapper like hibernate or toplink the handles the transport of the data form the database table to the entity. The ORM does this not only for the requested object, also all referenced objects could be handled automatically.

The most popular ORMs have very good and popular feature: lazy loading. Using this feature it is possible to fetch an entity by the ORM and referenced objects load their data transparent to the developer during the first access. This feature is great for having a good performance without the need of implementing a specially tailored fetch method for every different case of needed object trees. For example the order object is loaded with the ORM. The referenced customer object is only loaded, when accessing the customer attribute of the order. Same for the different addresses of the customer, the order positions and the data of the ordered items.

This works great using the order-object inside of the business logic in the business layer. But what happens when the order is returned by a service to the view layer? The view needs to access subobjects of the order which not have been needed by the getOrder method of the business layer. Because they have not been accessed they are not initialized yet. Because we have a clean architecture we have defined our transaction boundary above the service layer. When accessing now a not initialized subobject in the view layer the transaction is already closed and you get a LazyInitializationException.

There are three common solutions of the problem:

  • Open-Session-In-View pattern
  • Eager fetch pattern
  • DTO pattern

You can find descriptions of the patterns around in the net e.g. here

The Open-Session-In-View pattern is from the architectural view point very ugly. Loading of data is done in the view layer. Additionally it is very easy to get mysterious effects. For example if an attribute of an displayed object has a value, which is not expected by the view layer and therefor replaced by a default value, it could happen, that during the view of the object an update on the database is triggered.

The Eager fetch pattern means when fetching the order all subobjects are also fetched. This is not possible by declaration in all cases and usually absolutely unnecessary and tends to bad performance.

The DTO pattern converts all entities to DTOs with the same name and attributes when leaving the service. Inside the business layer the entities and lazy loading is used. This works well, but is a lot of stupid code to implement and to maintain.

The ideal solution would be to have an automatic conversation from entities to DTOs when leaving the service. It would be nice to define for each service operation which object tree is guaranteed to be initialized. So lets do it!

First we define an Initialized annotation:

1 package com.oc.common.annotations; 2 3 @Target(ElementType.METHOD) 4 @Retention(RetentionPolicy.RUNTIME) 5 public @interface Initialized { 6 7 String[] initializedProperties(); 8 9 } 10

This annotation is used for operations this way:

1 @Initialized(initializedProperties = { "addresses", "addresses.persons", "addresses.persons.connections" }) 2 public Partner findByIdInitialized(final long id) { 3 ... 4

Now we can define on each operation which parts of the object tree is guaranteed to be initialized. The view layer using this operation can rely on the initialization.  If the view needs in an other case an other combination of initialized objects we  can create a second operation with a different annotation.

In the next step we implement a piece of code, which handles the Initialized annotation and loads the needed objects. Here is an example of an implementation as spring AfterReturningAdvice:

1 package com.oc.common.interceptor; 2 3 import java.lang.reflect.InvocationTargetException; 4 import java.lang.reflect.Method; 5 import org.springframework.aop.AfterReturningAdvice; 6 import com.oc.common.annotations.Initialized; 7 8 public class LazyInitialitationVerifyer implements AfterReturningAdvice { 9 10 private static void initialize(final Object object) { 11 if (object instanceof Object[]) { 12 final Object[] objects = (Object[]) object; 13 for (int i = 0; i < objects.length; i++) { 14 LazyInitialitationVerifyer.initialize(objects[i]); 15 } 16 } else if (object instanceof Iterable) { 17 for (final Object subObject : (Iterable) object) { 18 LazyInitialitationVerifyer.initialize(subObject); 19 } 20 } else { 21 object.toString(); 22 } 23 } 24 25 private static String[] calcGetMethodNames(final String property) { 26 String[] result = null; 27 result = new String[1]; 28 result[0] = "get" + ("" + property.charAt(0)).toUpperCase() + property.substring(1); 29 return result; 30 } 31 32 private static void initializePath(final Object object, final String[] initializationPath, final int startIndex) { 33 final String property = initializationPath[startIndex]; 34 Object pathObject = null; 35 final String[] methodNames = LazyInitialitationVerifyer.calcGetMethodNames(property); 36 for (int i = 0; (pathObject == null) && (i < methodNames.length); i++) { 37 Method method = null; 38 try { 39 method = object.getClass().getMethod(methodNames[0], new Class[0]); 40 } catch (final SecurityException e) { 41 // Access denied trying other method 42 } catch (final NoSuchMethodException e) { 43 // Wrong name trying other method 44 } 45 if (method != null) { 46 try { 47 pathObject = method.invoke(object, new Object[0]); 48 } catch (final IllegalArgumentException e) { 49 // Wrong method name trying other method 50 } catch (final IllegalAccessException e) { 51 // Access denied trying other method 52 } catch (final InvocationTargetException e) { 53 // Access denied trying other method 54 } 55 } 56 57 } 58 if (pathObject != null) { 59 if (startIndex < initializationPath.length - 1) { 60 if (pathObject instanceof Iterable) { 61 for (final Object subObject : (Iterable) pathObject) { 62 LazyInitialitationVerifyer.initializePath(subObject, initializationPath, startIndex + 1); 63 } 64 } else if (pathObject instanceof Object[]) { 65 final Object[] subObjects = (Object[]) pathObject; 66 for (int i = 0; i < subObjects.length; i++) { 67 LazyInitialitationVerifyer.initializePath(subObjects[i], initializationPath, startIndex + 1); 68 } 69 } else { 70 LazyInitialitationVerifyer.initializePath(pathObject, initializationPath, startIndex + 1); 71 } 72 } else { 73 LazyInitialitationVerifyer.initialize(pathObject); 74 } 75 } else { 76 // TODO error 77 } 78 } 79 80 public static void initialize(final Object object, final String[] initializedProperties) { 81 for (final String initializedProperty : initializedProperties) { 82 final String[] path = initializedProperty.split("\\."); 83 if (object instanceof Iterable) { 84 for (final Object entry : (Iterable) object) { 85 LazyInitialitationVerifyer.initializePath(entry, path, 0); 86 } 87 } else if (object instanceof Object[]) { 88 final Object[] entries = (Object[]) object; 89 for (int i = 0; i < entries.length; i++) { 90 LazyInitialitationVerifyer.initializePath(entries[i], path, 0); 91 } 92 } else { 93 LazyInitialitationVerifyer.initializePath(object, path, 0); 94 } 95 } 96 } 97 98 public void afterReturning(final Object returnValue, final Method method, final Object[] args, final Object target) { 99 final Initialized in = method.getAnnotation(Initialized.class); 100 if ((in != null) && (returnValue != null)) { 101 LazyInitialitationVerifyer.initialize(returnValue, in.initializedProperties()); 102 } 103 } 104 105 } 106

Finally we have to attach the LazyInitialitationVerifyer to every (spring-)service:

1 <bean id="lazyInitialiationInterceptor" class="com.oc.common.interceptor.LazyInitialitationVerifyer"/> 2 3 <bean id="abstractService" abstract="true" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> 4 <property name="transactionManager" ref="transactionManager"/> 5 <property name="transactionAttributes" ref="transactionAttributes"/> 6 <property name="postInterceptors"> 7 <list> 8 <ref bean="lazyInitialiationInterceptor"/> 9 </list> 10 </property> 11 </bean>

Bernhard Mähr @ OPITZ-CONSULTING published at http://thecattlecrew.wordpress.com/

Leave a Reply