Dashboard > iBATIS DataMapper > Home > Not Yet Documented > Lazy loading issues
Lazy loading issues
Added by Christian Poitras, last edited by Ian Robertson on May 01, 2007  (view change) show comment
Labels: 
(None)


Enhanced lazy have different issues. 3 of them are discussed here.

  1. When loading an object having a 1:1 relation. If the object on the other side of 1:1 relation is lazy loaded with <discriminator> tag, you will not be able to cast the returned object to the subclass you are expecting. That's because the proxy object returned is based on the method return type (which normaly match the abstract class).
  2. If lazy loaded object return no results, you would expect null to be returned. Instead a proxy object is returned and calling methods on the proxy object will return null.
  3. Its harder to debug cgi enhanced objects using GUI debuggers.

In some cases the problems above will not happen. For example, in case 1, if the return type is the one of a concrete class, there won't be any problem.

Possible solutions for case 1:

  • Change EnhancedLazyResultLoader to not lazy load 1:1 abstract classes.

Possible solutions for case 2:

  • Use a custom ResultObjectFactory to add a method interceptor which would check for the above scenario and return null if required.
  • Lazy load objects with only one "id" column. If column is null, no lazy load will be done and null will be returned.
  • Use a List to stock lazy loaded results and return null or first object in List.

Here is a code sniplet provided by Paul McCormick to correct problem 2. (Thanks a lot Paul!)
See mailing list for this specific email.
http://www.mail-archive.com/user-java@ibatis.apache.org/msg08164.html
Sorry for the awful formatting.

I'll give you some of the sudo code that I used to overcome lazy loading of 1 to 1 relationships. I had to write extra code to allow lazy loaded objects to be serializable and cloneable. The code has nothing to do with lazy loading of lists.

All objects loaded from the database implement the IBaseDomain interface.

IBaseDomain.java
interface IBaseDomain {
    Integer id              // The primary key which is never null
        Integer versionId  // Used of optomistic locking
        boolean setNewCalled // Used when the id and versionId have been set to null by client code.
    }

Here is the ResultObjectFactory used. Quite simple, just enhances anything that implements IBaseDomain.

EmitsResultObjectFactory.java
public final class EmitsResultObjectFactory implements ResultObjectFactory {
    
    public final Object createInstance(String arg0, Class clazz) throws InstantiationException, IllegalAccessException {
    
    if (IBaseDomain.class.isAssignableFrom(clazz)) {
    Enhancer enhancer = new Enhancer();
    enhancer.setSuperclass(clazz);
    enhancer.setCallbackFilter(EnhancedBaseDomainCallbackFilter.instance);
    enhancer.setCallbacks(EnhancedBaseDomainCallbackFilter.callbacks);
    return enhancer.create();
    } else {
    return null;
    }
    
    }
    
    public void setProperty(String arg0, String arg1) {}
    }

-----------------------------------------------------------

EnhancedBaseDomainCallbackFilter.java
public final class EnhancedBaseDomainCallbackFilter implements CallbackFilter, MethodInterceptor, Serializable {
    
    private EnhancedBaseDomainCallbackFilter() {
    
    }
    
    public static final EnhancedBaseDomainCallbackFilter instance = new EnhancedBaseDomainCallbackFilter();
    
    public static final Callback[] callbacks = { NoOpCallback.INSTANCE, instance };
    
    public final int accept(Method method) {
    
    if (method.getName().startsWith("get") && method.getParameterTypes().length == 0 && IBaseDomain.class.isAssignableFrom(method.getReturnType())) {
    return 1; // BaseDomain return types are intercepted.
            }
    return 0; // the No operation interceptor. Does nothing.
        }
    
    /**
    * Allows using the enhancementEnabled ibatis option. This option allows the lazy loading of single objects ( and not just Lists ).
    *
    * e.g the following method can be lazy loaded: public Party getParty();
    *
    * A problem occurs if calling the "public Party getParty()" should return null. Instead of null being returned a 'wrapped null' is returned. This MethodInterceptor fixes this
    * feature/bug.
    */
    public final Object intercept(final Object object, final Method method, final Object[] args, final MethodProxy proxy) throws Throwable {
    
    Object obj = proxy.invokeSuper(object, args);
    if ((obj == null) || (!(obj instanceof IBaseDomain))) {
    return obj;
    }
    
    IBaseDomain baseDomain = (IBaseDomain) obj;
    
    // All Enhanced objects implement Factory.
            if (baseDomain instanceof Factory) {
    // If the primary key is null then the object is null unless the client code called setNew()
                if (baseDomain.getId() == null) {
    try {
    // If baseDomain.setNew() is called then the object is valid.  If not then its a return null instead of the proxy.
                        if (!baseDomain.isSetNewCalled()) {
    return null;
    }
    // There is a bug in cg lib that can cause a Null Pointer to be thrown on a cloned enhanced object.  Don't know why but this is a temp fix.
                    } catch (NullPointerException e) {
    Log log = LogFactory.getLog(EnhancedBaseDomainCallbackFilter.class);
    log.debug("EnhancedBaseDomainCallbackFilter.intercept caught NullPointerException on method " + method.getName() + " object:" + baseDomain.getClass().getName());
    return null;
    }
    }
    }
    
    return baseDomain;
    
    }
    
    private interface NoOpCallback extends NoOp, Serializable {
    /**
    * A thread-safe singleton instance of the <code>NoOpCallback</code> callback.
    */
    public static final NoOpCallback INSTANCE = new NoOpCallback() {
    };
    }
    
    }

-----------------------------------------------------------
I changed the Ibatis class EnhancedLazyResultLoader.
1) I had to make an inner class Serializable. This involved making the field that links to the jdbc connection transient. I then had to put my application code into this class to set this transient field. Thats a hack that you may want to improve on.

2) Changed the EnhancedLazyResultLoaderImpl.loadResult() method to only create proxy objects for IBaseDomain results ( that are not abstract). The reason for not lazy loading abstract classes is because the instanceof does not work as expected when comparing to a concrete class.
Here is the method code. I'll attach a file of the whole class also but most of it is not relevent.

public Object loadResult() throws SQLException {
    if (...)
    ....
    } else if (Collection.class.isAssignableFrom(targetType)) {
    .....
    // This is my custom code for lazy loading 1 to 1 relationships.
          } else if ( IBaseDomain.class.isAssignableFrom(targetType)  && !Modifier.isAbstract(targetType.getModifiers())) {
    return  Enhancer.create(targetType, new Class[] { IBaseDomain.class } , this);
    } else {
    loadObject();
    return resultObject;
    }
    }

I hope this helps,
Paul


Site running on a free Atlassian Confluence Open Source Project License granted to OSS. Evaluate Confluence today.
Powered by Atlassian Confluence, the Enterprise Wiki. (Version: 2.5.5 Build:#811 Jul 25, 2007) - Bug/feature request - Contact Administrators