pátek 28. července 2006

Odložené nahrávání dat v ORM nástrojích

Odložené nahrávání dat, takzvaný lazy fetching, patří k základním vlastnostem pro zvýšení výkonu v ORM nástrojích jako Hibernate či iBatis SQL Maps. Představme si případ kdy máme dvě entiy entity ve vztahu 1:* např. osoba a auto.

public class Osoba {
  private Set<Auto> auta;

  public Set<Auto> getAuta() {
    return auta;
  }

  public void setAuta(Set<Auto> auta) {
    this.auta = auta;
  }  
}

Odložené nahrávání může být v tomto případě použito na kolekce automobilů a to následujícím způsobem. Kolekce automobilů je z databázé dotažena teprve ve chvíli kdy je volána metoda getAuta(). Díky odloženému nahrávání je tak možné data načíst teprve ve chvíli kdy je to opravdu potřeba.

Otázka je jak výše uvedený getter zajistí nahrání dat z databáze? Samozřejmě, že nezajistí, to co ho zajistí se jmenuje dynamický proxy objekt. ORM framework totiž nikdy nevrací přesně objekt dané třídy nýbrž v runtime čase dynamicky vytvořeného potomka dané třídy. Díky dědičnosti je pro vás práce s objektem, v našem případě Auto, transparentní. Potomek pak slouží jako proxy objekt, který realizuje odložené nahrání dat.

Pro lepší znázornění výše popsaného, kód který vznikne semanticky odpovídá následujícímu kódu.

public class OsobaProxy extends Osoba {
  public Set<Auto> getAuta() {
    Set<Auto> auta = ... //volani logiky pro nacteni dat
    setAuta(auta);
    return super.getAuta();
  }  
}

Hibernate tak iBatis používá na vytváření proxy objektů v runtime framework zvaný CGLIB. Jak je jednoduché s CGLIBem pracovat si ukážeme na následujícím příkladu, třeba se vám to bude hodit. Vytvoříme jednoduchou beanu a k ní v runtime proxy objekt, který při každém volání metody naší beany vypíše název této metody.

Vlastní beana

/**
 * Klasicka Java bean
 */
public class Bean implements Serializable{
  
  private int foo;
  
  public int getFoo() {
    return foo;
  }
  
  public void setFoo(int foo) {
    this.foo = foo;
  }  
}

Interceptor, třída která bude zavěšená na volání metod beany. V interceptoru se nejdříve vypíše název volané metody a následně se nechá vykonat původní metoda beany.

import java.lang.reflect.Method;

import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

/**
 * Interceptor, ktery je zavesen na volani jakekoliv metody {@link cz.sweb.pichlik.examples.lazyinit.Bean}
 */
public class Interceptor implements MethodInterceptor {
  
  /**
   * Vypise nazev volane metody
   */
  public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxythrows Throwable {
    System.out.println("Volana metoda:" + method.getName());
    return proxy.invokeSuper(obj, args);
  }

}

Ukázka vytvoření dynamické proxy beany a jejího volání. Všimněte si zavěšení interceptoru.

import net.sf.cglib.proxy.Enhancer;

/**
 * Demonstruje instrumentaci Java beany viz {@link cz.sweb.pichlik.examples.lazyinit.Interceptor}
 *
 */
public class LazyInitExample {


  public static void main(String[] args) {
    Bean instrumentedBean = (BeancreateNewInstrumentedBean(Bean.class);    
    instrumentedBean.setFoo(10);
    instrumentedBean.getFoo();
  }
  
  /**
   * Vytvori rozsirenou instanci objektu dane tridy.
   
   @param clazz trida
   @return instance
   */
  private static Object createNewInstrumentedBean(Class clazz){
    Interceptor interceptor = new Interceptor();
    Enhancer e = new Enhancer();
    e.setSuperclass(clazz);
      e.setCallback(interceptor);
    Object instrumentedBean = e.create();
    return instrumentedBean;
  }

}